codekingpro/portable-devtools
114k
1from collections import defaultdict, deque2from itertools import filterfalse3 4 5def unique_everseen(iterable, key=None):6 "List unique elements, preserving order. Remember all elements ever seen."7 # unique_everseen('AAAABBBCCDAABBB') --> A B C D8 # unique_everseen('ABBCcAD', str.lower) --> A B C D9 seen = set()10 seen_add = seen.add11 if key is None:12 for element in filterfalse(seen.__contains__, iterable):13 seen_add(element)14 yield element15 else:16 for element in iterable:17 k = key(element)18 if k not in seen:19 seen_add(k)20 yield element21 22 23# copied from more_itertools 8.824def always_iterable(obj, base_type=(str, bytes)):25 """If *obj* is iterable, return an iterator over its items::26 27 >>> obj = (1, 2, 3)28 >>> list(always_iterable(obj))29 [1, 2, 3]30 31 If *obj* is not iterable, return a one-item iterable containing *obj*::32 33 >>> obj = 134 >>> list(always_iterable(obj))35 [1]36 37 If *obj* is ``None``, return an empty iterable:38 39 >>> obj = None40 >>> list(always_iterable(None))41 []42 43 By default, binary and text strings are not considered iterable::44 45 >>> obj = 'foo'46 >>> list(always_iterable(obj))47 ['foo']48 49 If *base_type* is set, objects for which ``isinstance(obj, base_type)``50 returns ``True`` won't be considered iterable.51 52 >>> obj = {'a': 1}53 >>> list(always_iterable(obj)) # Iterate over the dict's keys54 ['a']55 >>> list(always_iterable(obj, base_type=dict)) # Treat dicts as a unit56 [{'a': 1}]57 58 Set *base_type* to ``None`` to avoid any special handling and treat objects59 Python considers iterable as iterable:60 61 >>> obj = 'foo'62 >>> list(always_iterable(obj, base_type=None))63 ['f', 'o', 'o']64 """65 if obj is None:66 return iter(())67 68 if (base_type is not None) and isinstance(obj, base_type):69 return iter((obj,))70 71 try:72 return iter(obj)73 except TypeError:74 return iter((obj,))75 76 77# Copied from more_itertools 10.378class bucket:79 """Wrap *iterable* and return an object that buckets the iterable into80 child iterables based on a *key* function.81 82 >>> iterable = ['a1', 'b1', 'c1', 'a2', 'b2', 'c2', 'b3']83 >>> s = bucket(iterable, key=lambda x: x[0]) # Bucket by 1st character84 >>> sorted(list(s)) # Get the keys85 ['a', 'b', 'c']86 >>> a_iterable = s['a']87 >>> next(a_iterable)88 'a1'89 >>> next(a_iterable)90 'a2'91 >>> list(s['b'])92 ['b1', 'b2', 'b3']93 94 The original iterable will be advanced and its items will be cached until95 they are used by the child iterables. This may require significant storage.96 97 By default, attempting to select a bucket to which no items belong will98 exhaust the iterable and cache all values.99 If you specify a *validator* function, selected buckets will instead be100 checked against it.101 102 >>> from itertools import count103 >>> it = count(1, 2) # Infinite sequence of odd numbers104 >>> key = lambda x: x % 10 # Bucket by last digit105 >>> validator = lambda x: x in {1, 3, 5, 7, 9} # Odd digits only106 >>> s = bucket(it, key=key, validator=validator)107 >>> 2 in s108 False109 >>> list(s[2])110 []111 112 """113 114 def __init__(self, iterable, key, validator=None):115 self._it = iter(iterable)116 self._key = key117 self._cache = defaultdict(deque)118 self._validator = validator or (lambda x: True)119 120 def __contains__(self, value):121 if not self._validator(value):122 return False123 124 try:125 item = next(self[value])126 except StopIteration:127 return False128 else:129 self._cache[value].appendleft(item)130 131 return True132 133 def _get_values(self, value):134 """135 Helper to yield items from the parent iterator that match *value*.136 Items that don't match are stored in the local cache as they137 are encountered.138 """139 while True:140 # If we've cached some items that match the target value, emit141 # the first one and evict it from the cache.142 if self._cache[value]:143 yield self._cache[value].popleft()144 # Otherwise we need to advance the parent iterator to search for145 # a matching item, caching the rest.146 else:147 while True:148 try:149 item = next(self._it)150 except StopIteration:151 return152 item_value = self._key(item)153 if item_value == value:154 yield item155 break156 elif self._validator(item_value):157 self._cache[item_value].append(item)158 159 def __iter__(self):160 for item in self._it:161 item_value = self._key(item)162 if self._validator(item_value):163 self._cache[item_value].append(item)164 165 yield from self._cache.keys()166 167 def __getitem__(self, value):168 if not self._validator(value):169 return iter(())170 171 return self._get_values(value)172 