summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRaymond Hettinger <rhettinger@users.noreply.github.com>2023-12-25 22:26:04 (GMT)
committerGitHub <noreply@github.com>2023-12-25 22:26:04 (GMT)
commitb5dc0f83adda0bde1b176c3a84cc31cd94254e73 (patch)
treeeaa9d4184cbdbac37da81fdf33426d3ec3a4c5d2
parent48c49739f5502fc7aa82f247ab2e4d7b55bdca62 (diff)
downloadcpython-b5dc0f83adda0bde1b176c3a84cc31cd94254e73.zip
cpython-b5dc0f83adda0bde1b176c3a84cc31cd94254e73.tar.gz
cpython-b5dc0f83adda0bde1b176c3a84cc31cd94254e73.tar.bz2
Misc minor improvements to the itertools recipes (gh-113477)
-rw-r--r--Doc/library/itertools.rst164
1 files changed, 83 insertions, 81 deletions
diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst
index c016fb7..5c8cc98 100644
--- a/Doc/library/itertools.rst
+++ b/Doc/library/itertools.rst
@@ -803,11 +803,11 @@ which incur interpreter overhead.
import random
def take(n, iterable):
- "Return first n items of the iterable as a list"
+ "Return first n items of the iterable as a list."
return list(islice(iterable, n))
def prepend(value, iterable):
- "Prepend a single value in front of an iterable"
+ "Prepend a single value in front of an iterable."
# prepend(1, [2, 3, 4]) --> 1 2 3 4
return chain([value], iterable)
@@ -825,15 +825,15 @@ which incur interpreter overhead.
return starmap(func, repeat(args, times))
def flatten(list_of_lists):
- "Flatten one level of nesting"
+ "Flatten one level of nesting."
return chain.from_iterable(list_of_lists)
def ncycles(iterable, n):
- "Returns the sequence elements n times"
+ "Returns the sequence elements n times."
return chain.from_iterable(repeat(tuple(iterable), n))
def tail(n, iterable):
- "Return an iterator over the last n items"
+ "Return an iterator over the last n items."
# tail(3, 'ABCDEFG') --> E F G
return iter(collections.deque(iterable, maxlen=n))
@@ -848,7 +848,7 @@ which incur interpreter overhead.
next(islice(iterator, n, n), None)
def nth(iterable, n, default=None):
- "Returns the nth item or a default value"
+ "Returns the nth item or a default value."
return next(islice(iterable, n, None), default)
def quantify(iterable, pred=bool):
@@ -856,7 +856,7 @@ which incur interpreter overhead.
return sum(map(pred, iterable))
def all_equal(iterable):
- "Returns True if all the elements are equal to each other"
+ "Returns True if all the elements are equal to each other."
g = groupby(iterable)
return next(g, True) and not next(g, False)
@@ -873,6 +873,30 @@ which incur interpreter overhead.
# first_true([a,b], x, f) --> a if f(a) else b if f(b) else x
return next(filter(pred, iterable), default)
+ def unique_everseen(iterable, key=None):
+ "List unique elements, preserving order. Remember all elements ever seen."
+ # unique_everseen('AAAABBBCCDAABBB') --> A B C D
+ # unique_everseen('ABBcCAD', str.casefold) --> A B c D
+ seen = set()
+ 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
+
+ def unique_justseen(iterable, key=None):
+ "List unique elements, preserving order. Remember only the element just seen."
+ # unique_justseen('AAAABBBCCDAABBB') --> A B C D A B
+ # unique_justseen('ABBcCAD', str.casefold) --> A B c A D
+ if key is None:
+ return map(operator.itemgetter(0), groupby(iterable))
+ return map(next, map(operator.itemgetter(1), groupby(iterable, key)))
+
def iter_index(iterable, value, start=0, stop=None):
"Return indices where a value occurs in a sequence or iterable."
# iter_index('AABCADEAF', 'A') --> 0 1 4 7
@@ -893,31 +917,17 @@ which incur interpreter overhead.
except ValueError:
pass
- def iter_except(func, exception, first=None):
- """ Call a function repeatedly until an exception is raised.
-
- Converts a call-until-exception interface to an iterator interface.
- Like builtins.iter(func, sentinel) but uses an exception instead
- of a sentinel to end the loop.
-
- Examples:
- iter_except(functools.partial(heappop, h), IndexError) # priority queue iterator
- iter_except(d.popitem, KeyError) # non-blocking dict iterator
- iter_except(d.popleft, IndexError) # non-blocking deque iterator
- iter_except(q.get_nowait, Queue.Empty) # loop over a producer Queue
- iter_except(s.pop, KeyError) # non-blocking set iterator
-
- """
- try:
- if first is not None:
- yield first() # For database APIs needing an initial cast to db.first()
- while True:
- yield func()
- except exception:
- pass
+ def sliding_window(iterable, n):
+ "Collect data into overlapping fixed-length chunks or blocks."
+ # sliding_window('ABCDEFG', 4) --> ABCD BCDE CDEF DEFG
+ it = iter(iterable)
+ window = collections.deque(islice(it, n-1), maxlen=n)
+ for x in it:
+ window.append(x)
+ yield tuple(window)
def grouper(iterable, n, *, incomplete='fill', fillvalue=None):
- "Collect data into non-overlapping fixed-length chunks or blocks"
+ "Collect data into non-overlapping fixed-length chunks or blocks."
# grouper('ABCDEFG', 3, fillvalue='x') --> ABC DEF Gxx
# grouper('ABCDEFG', 3, incomplete='strict') --> ABC DEF ValueError
# grouper('ABCDEFG', 3, incomplete='ignore') --> ABC DEF
@@ -932,16 +942,9 @@ which incur interpreter overhead.
case _:
raise ValueError('Expected fill, strict, or ignore')
- def sliding_window(iterable, n):
- # sliding_window('ABCDEFG', 4) --> ABCD BCDE CDEF DEFG
- it = iter(iterable)
- window = collections.deque(islice(it, n-1), maxlen=n)
- for x in it:
- window.append(x)
- yield tuple(window)
-
def roundrobin(*iterables):
- "roundrobin('ABC', 'D', 'EF') --> A D E B F C"
+ "Visit input iterables in a cycle until each is exhausted."
+ # roundrobin('ABC', 'D', 'EF') --> A D E B F C
# Recipe credited to George Sakkis
num_active = len(iterables)
nexts = cycle(iter(it).__next__ for it in iterables)
@@ -964,11 +967,43 @@ which incur interpreter overhead.
return filterfalse(pred, t1), filter(pred, t2)
def subslices(seq):
- "Return all contiguous non-empty subslices of a sequence"
+ "Return all contiguous non-empty subslices of a sequence."
# subslices('ABCD') --> A AB ABC ABCD B BC BCD C CD D
slices = starmap(slice, combinations(range(len(seq) + 1), 2))
return map(operator.getitem, repeat(seq), slices)
+ def iter_except(func, exception, first=None):
+ """ Call a function repeatedly until an exception is raised.
+
+ Converts a call-until-exception interface to an iterator interface.
+ Like builtins.iter(func, sentinel) but uses an exception instead
+ of a sentinel to end the loop.
+
+ Priority queue iterator:
+ iter_except(functools.partial(heappop, h), IndexError)
+
+ Non-blocking dictionary iterator:
+ iter_except(d.popitem, KeyError)
+
+ Non-blocking deque iterator:
+ iter_except(d.popleft, IndexError)
+
+ Non-blocking iterator over a producer Queue:
+ iter_except(q.get_nowait, Queue.Empty)
+
+ Non-blocking set iterator:
+ iter_except(s.pop, KeyError)
+
+ """
+ try:
+ if first is not None:
+ # For database APIs needing an initial call to db.first()
+ yield first()
+ while True:
+ yield func()
+ except exception:
+ pass
+
def before_and_after(predicate, it):
""" Variant of takewhile() that allows complete
access to the remainder of the iterator.
@@ -980,12 +1015,12 @@ which incur interpreter overhead.
>>> ''.join(remainder) # takewhile() would lose the 'd'
'dEfGhI'
- Note that the first iterator must be fully
- consumed before the second iterator can
- generate valid results.
+ Note that the true iterator must be fully consumed
+ before the remainder iterator can generate valid results.
"""
it = iter(it)
transition = []
+
def true_iterator():
for elem in it:
if predicate(elem):
@@ -993,41 +1028,8 @@ which incur interpreter overhead.
else:
transition.append(elem)
return
- def remainder_iterator():
- yield from transition
- yield from it
- return true_iterator(), remainder_iterator()
- def unique_everseen(iterable, key=None):
- "List unique elements, preserving order. Remember all elements ever seen."
- # unique_everseen('AAAABBBCCDAABBB') --> A B C D
- # unique_everseen('ABBcCAD', str.lower) --> A B c D
- seen = set()
- if key is None:
- for element in filterfalse(seen.__contains__, iterable):
- seen.add(element)
- yield element
- # For order preserving deduplication,
- # a faster but non-lazy solution is:
- # yield from dict.fromkeys(iterable)
- else:
- for element in iterable:
- k = key(element)
- if k not in seen:
- seen.add(k)
- yield element
- # For use cases that allow the last matching element to be returned,
- # a faster but non-lazy solution is:
- # t1, t2 = tee(iterable)
- # yield from dict(zip(map(key, t1), t2)).values()
-
- def unique_justseen(iterable, key=None):
- "List unique elements, preserving order. Remember only the element just seen."
- # unique_justseen('AAAABBBCCDAABBB') --> A B C D A B
- # unique_justseen('ABBcCAD', str.lower) --> A B c A D
- if key is None:
- return map(operator.itemgetter(0), groupby(iterable))
- return map(next, map(operator.itemgetter(1), groupby(iterable, key)))
+ return true_iterator(), chain(transition, it)
The following recipes have a more mathematical flavor:
@@ -1562,16 +1564,16 @@ The following recipes have a more mathematical flavor:
>>> list(unique_everseen('AAAABBBCCDAABBB'))
['A', 'B', 'C', 'D']
- >>> list(unique_everseen('ABBCcAD', str.lower))
+ >>> list(unique_everseen('ABBCcAD', str.casefold))
['A', 'B', 'C', 'D']
- >>> list(unique_everseen('ABBcCAD', str.lower))
+ >>> list(unique_everseen('ABBcCAD', str.casefold))
['A', 'B', 'c', 'D']
>>> list(unique_justseen('AAAABBBCCDAABBB'))
['A', 'B', 'C', 'D', 'A', 'B']
- >>> list(unique_justseen('ABBCcAD', str.lower))
+ >>> list(unique_justseen('ABBCcAD', str.casefold))
['A', 'B', 'C', 'A', 'D']
- >>> list(unique_justseen('ABBcCAD', str.lower))
+ >>> list(unique_justseen('ABBcCAD', str.casefold))
['A', 'B', 'c', 'A', 'D']
>>> d = dict(a=1, b=2, c=3)