Source code for pyams_utils.list

#
# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#

"""PyAMS_utils list module

This module is dedicated to lists and iterators management. It provides function to extract
unique values from a list or iterator in their original order, or to iterate over an iterator in
random order; it also provides a "boolean_iter" function (usable as TALES extension) to check if
an iterator returns at least one value, without consuming this iterator (the function returns a
tuple containing a boolean value to specify if iterator is empty or not, and the original
iterator).
"""

from itertools import filterfalse, tee
from random import random, shuffle

from zope.interface import Interface

from pyams_utils.adapter import ContextRequestViewAdapter, adapter_config
from pyams_utils.interfaces.tales import ITALESExtension


__docformat__ = 'restructuredtext'


[docs]def unique(seq, key=None): """Extract unique values from list, preserving order :param iterator seq: input list :param callable key: an identity function which is used to get 'identity' value of each element in the list :return: list; a new list containing only unique elements of the original list in their initial order. Original list is not modified. >>> from pyams_utils.list import unique >>> mylist = [1, 2, 3, 2, 1] >>> unique(mylist) [1, 2, 3] >>> mylist = [3, 2, 2, 1, 4, 2] >>> unique(mylist) [3, 2, 1, 4] You can also set an 'id' function applied on each element: >>> mylist = [1, 2, 3, '2', 4] >>> unique(mylist, key=str) [1, 2, 3, 4] >>> mylist = ['A', 'B', 'b', '2', 4] >>> unique(mylist, key=lambda x: str(x).lower()) ['A', 'B', '2', 4] """ seen = set() seen_add = seen.add result = [] if key is None: for element in filterfalse(seen.__contains__, seq): seen_add(element) result.append(element) else: for element in seq: k = key(element) if k not in seen: seen_add(k) result.append(element) return result
[docs]def unique_iter(iterable, key=None): """Iterate over iterator values, yielding only unique values :param iterator iterable: input iterator :param callable key: an identity function which is used to get 'identity' value of each element in the list :return: an iterator of unique values >>> from pyams_utils.list import unique_iter >>> mylist = [1, 2, 3, 2, 1] >>> list(unique_iter(mylist)) [1, 2, 3] >>> mylist = [3, 2, 2, 1, 4, 2] >>> list(unique_iter(mylist)) [3, 2, 1, 4] You can also set an 'id' function applied on each element: >>> mylist = [1, 2, 3, '2', 4] >>> list(unique_iter(mylist, key=str)) [1, 2, 3, 4] >>> mylist = ['A', 'B', 'b', '2', 4] >>> list(unique_iter(mylist, key=lambda x: str(x).lower())) ['A', 'B', '2', 4] """ seen = set() seen_add = seen.add if key is None: for element in filterfalse(seen.__contains__, iterable): seen_add(element) yield element else: for element in iterable: k = key(element) if k not in seen: seen_add(k) yield element
[docs]def random_iter(iterable, limit=1): """Get items randomly from an iterator >>> from pyams_utils.list import random_iter >>> mylist = [1, 2, 3, 2, 1] >>> list(random_iter(mylist, 2)) [..., ...] """ selected = [None] * limit for index, item in enumerate(iterable): if index < limit: selected[index] = item else: selected_index = int(random() * (index+1)) if selected_index < limit: selected[selected_index] = item shuffle(selected) return iter(selected)
[docs]def boolean_iter(iterable): """Check if an iterable returns at least one value, without consuming it. The function returns a tuple containing a boolean flag indicating if the original iterator is empty or not, and the original un-consumed iterator. >>> from pyams_utils.list import boolean_iter >>> def empty(input): ... yield from input >>> mylist = empty(()) >>> check, myiter = boolean_iter(mylist) >>> check False >>> list(myiter) [] >>> mylist = empty((1,2,3)) >>> check, myiter = boolean_iter(mylist) >>> check True >>> list(myiter) [1, 2, 3] >>> list(myiter) [] """ def inner_check(): check, items = tee(iterable) try: next(check) except StopIteration: yield False else: yield True yield from items values = inner_check() return next(values), values
[docs]@adapter_config(name='boolean_iter', context=(Interface, Interface, Interface), provides=ITALESExtension) class IterValuesCheckerExpression(ContextRequestViewAdapter): """TALES expression used to handle iterators The expression returns a tuple containing a boolean flag indicating if the original iterator is empty or not, and the original un-consumed iterator. """
[docs] def render(self, context=None): """Render TALES extension; see `ITALESExtension` interface""" if context is None: context = self.context return boolean_iter(context)