Skip to content

Commit 8a95500

Browse files
[3.12] Misc minor improvements to the itertools recipes (gh-113477) (gh-113478)
1 parent d58a5f4 commit 8a95500

File tree

1 file changed

+83
-81
lines changed

1 file changed

+83
-81
lines changed

Doc/library/itertools.rst

Lines changed: 83 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -795,11 +795,11 @@ which incur interpreter overhead.
795795
import random
796796

797797
def take(n, iterable):
798-
"Return first n items of the iterable as a list"
798+
"Return first n items of the iterable as a list."
799799
return list(islice(iterable, n))
800800

801801
def prepend(value, iterable):
802-
"Prepend a single value in front of an iterable"
802+
"Prepend a single value in front of an iterable."
803803
# prepend(1, [2, 3, 4]) --> 1 2 3 4
804804
return chain([value], iterable)
805805

@@ -817,15 +817,15 @@ which incur interpreter overhead.
817817
return starmap(func, repeat(args, times))
818818

819819
def flatten(list_of_lists):
820-
"Flatten one level of nesting"
820+
"Flatten one level of nesting."
821821
return chain.from_iterable(list_of_lists)
822822

823823
def ncycles(iterable, n):
824-
"Returns the sequence elements n times"
824+
"Returns the sequence elements n times."
825825
return chain.from_iterable(repeat(tuple(iterable), n))
826826

827827
def tail(n, iterable):
828-
"Return an iterator over the last n items"
828+
"Return an iterator over the last n items."
829829
# tail(3, 'ABCDEFG') --> E F G
830830
return iter(collections.deque(iterable, maxlen=n))
831831

@@ -840,15 +840,15 @@ which incur interpreter overhead.
840840
next(islice(iterator, n, n), None)
841841

842842
def nth(iterable, n, default=None):
843-
"Returns the nth item or a default value"
843+
"Returns the nth item or a default value."
844844
return next(islice(iterable, n, None), default)
845845

846846
def quantify(iterable, pred=bool):
847847
"Given a predicate that returns True or False, count the True results."
848848
return sum(map(pred, iterable))
849849

850850
def all_equal(iterable):
851-
"Returns True if all the elements are equal to each other"
851+
"Returns True if all the elements are equal to each other."
852852
g = groupby(iterable)
853853
return next(g, True) and not next(g, False)
854854

@@ -865,6 +865,30 @@ which incur interpreter overhead.
865865
# first_true([a,b], x, f) --> a if f(a) else b if f(b) else x
866866
return next(filter(pred, iterable), default)
867867

868+
def unique_everseen(iterable, key=None):
869+
"List unique elements, preserving order. Remember all elements ever seen."
870+
# unique_everseen('AAAABBBCCDAABBB') --> A B C D
871+
# unique_everseen('ABBcCAD', str.casefold) --> A B c D
872+
seen = set()
873+
if key is None:
874+
for element in filterfalse(seen.__contains__, iterable):
875+
seen.add(element)
876+
yield element
877+
else:
878+
for element in iterable:
879+
k = key(element)
880+
if k not in seen:
881+
seen.add(k)
882+
yield element
883+
884+
def unique_justseen(iterable, key=None):
885+
"List unique elements, preserving order. Remember only the element just seen."
886+
# unique_justseen('AAAABBBCCDAABBB') --> A B C D A B
887+
# unique_justseen('ABBcCAD', str.casefold) --> A B c A D
888+
if key is None:
889+
return map(operator.itemgetter(0), groupby(iterable))
890+
return map(next, map(operator.itemgetter(1), groupby(iterable, key)))
891+
868892
def iter_index(iterable, value, start=0, stop=None):
869893
"Return indices where a value occurs in a sequence or iterable."
870894
# iter_index('AABCADEAF', 'A') --> 0 1 4 7
@@ -885,31 +909,17 @@ which incur interpreter overhead.
885909
except ValueError:
886910
pass
887911

888-
def iter_except(func, exception, first=None):
889-
""" Call a function repeatedly until an exception is raised.
890-
891-
Converts a call-until-exception interface to an iterator interface.
892-
Like builtins.iter(func, sentinel) but uses an exception instead
893-
of a sentinel to end the loop.
894-
895-
Examples:
896-
iter_except(functools.partial(heappop, h), IndexError) # priority queue iterator
897-
iter_except(d.popitem, KeyError) # non-blocking dict iterator
898-
iter_except(d.popleft, IndexError) # non-blocking deque iterator
899-
iter_except(q.get_nowait, Queue.Empty) # loop over a producer Queue
900-
iter_except(s.pop, KeyError) # non-blocking set iterator
901-
902-
"""
903-
try:
904-
if first is not None:
905-
yield first() # For database APIs needing an initial cast to db.first()
906-
while True:
907-
yield func()
908-
except exception:
909-
pass
912+
def sliding_window(iterable, n):
913+
"Collect data into overlapping fixed-length chunks or blocks."
914+
# sliding_window('ABCDEFG', 4) --> ABCD BCDE CDEF DEFG
915+
it = iter(iterable)
916+
window = collections.deque(islice(it, n-1), maxlen=n)
917+
for x in it:
918+
window.append(x)
919+
yield tuple(window)
910920

911921
def grouper(iterable, n, *, incomplete='fill', fillvalue=None):
912-
"Collect data into non-overlapping fixed-length chunks or blocks"
922+
"Collect data into non-overlapping fixed-length chunks or blocks."
913923
# grouper('ABCDEFG', 3, fillvalue='x') --> ABC DEF Gxx
914924
# grouper('ABCDEFG', 3, incomplete='strict') --> ABC DEF ValueError
915925
# grouper('ABCDEFG', 3, incomplete='ignore') --> ABC DEF
@@ -924,16 +934,9 @@ which incur interpreter overhead.
924934
case _:
925935
raise ValueError('Expected fill, strict, or ignore')
926936
927-
def sliding_window(iterable, n):
928-
# sliding_window('ABCDEFG', 4) --> ABCD BCDE CDEF DEFG
929-
it = iter(iterable)
930-
window = collections.deque(islice(it, n-1), maxlen=n)
931-
for x in it:
932-
window.append(x)
933-
yield tuple(window)
934-
935937
def roundrobin(*iterables):
936-
"roundrobin('ABC', 'D', 'EF') --> A D E B F C"
938+
"Visit input iterables in a cycle until each is exhausted."
939+
# roundrobin('ABC', 'D', 'EF') --> A D E B F C
937940
# Recipe credited to George Sakkis
938941
num_active = len(iterables)
939942
nexts = cycle(iter(it).__next__ for it in iterables)
@@ -956,11 +959,43 @@ which incur interpreter overhead.
956959
return filterfalse(pred, t1), filter(pred, t2)
957960

958961
def subslices(seq):
959-
"Return all contiguous non-empty subslices of a sequence"
962+
"Return all contiguous non-empty subslices of a sequence."
960963
# subslices('ABCD') --> A AB ABC ABCD B BC BCD C CD D
961964
slices = starmap(slice, combinations(range(len(seq) + 1), 2))
962965
return map(operator.getitem, repeat(seq), slices)
963966

967+
def iter_except(func, exception, first=None):
968+
""" Call a function repeatedly until an exception is raised.
969+
970+
Converts a call-until-exception interface to an iterator interface.
971+
Like builtins.iter(func, sentinel) but uses an exception instead
972+
of a sentinel to end the loop.
973+
974+
Priority queue iterator:
975+
iter_except(functools.partial(heappop, h), IndexError)
976+
977+
Non-blocking dictionary iterator:
978+
iter_except(d.popitem, KeyError)
979+
980+
Non-blocking deque iterator:
981+
iter_except(d.popleft, IndexError)
982+
983+
Non-blocking iterator over a producer Queue:
984+
iter_except(q.get_nowait, Queue.Empty)
985+
986+
Non-blocking set iterator:
987+
iter_except(s.pop, KeyError)
988+
989+
"""
990+
try:
991+
if first is not None:
992+
# For database APIs needing an initial call to db.first()
993+
yield first()
994+
while True:
995+
yield func()
996+
except exception:
997+
pass
998+
964999
def before_and_after(predicate, it):
9651000
""" Variant of takewhile() that allows complete
9661001
access to the remainder of the iterator.
@@ -972,54 +1007,21 @@ which incur interpreter overhead.
9721007
>>> ''.join(remainder) # takewhile() would lose the 'd'
9731008
'dEfGhI'
9741009

975-
Note that the first iterator must be fully
976-
consumed before the second iterator can
977-
generate valid results.
1010+
Note that the true iterator must be fully consumed
1011+
before the remainder iterator can generate valid results.
9781012
"""
9791013
it = iter(it)
9801014
transition = []
1015+
9811016
def true_iterator():
9821017
for elem in it:
9831018
if predicate(elem):
9841019
yield elem
9851020
else:
9861021
transition.append(elem)
9871022
return
988-
def remainder_iterator():
989-
yield from transition
990-
yield from it
991-
return true_iterator(), remainder_iterator()
9921023

993-
def unique_everseen(iterable, key=None):
994-
"List unique elements, preserving order. Remember all elements ever seen."
995-
# unique_everseen('AAAABBBCCDAABBB') --> A B C D
996-
# unique_everseen('ABBcCAD', str.lower) --> A B c D
997-
seen = set()
998-
if key is None:
999-
for element in filterfalse(seen.__contains__, iterable):
1000-
seen.add(element)
1001-
yield element
1002-
# For order preserving deduplication,
1003-
# a faster but non-lazy solution is:
1004-
# yield from dict.fromkeys(iterable)
1005-
else:
1006-
for element in iterable:
1007-
k = key(element)
1008-
if k not in seen:
1009-
seen.add(k)
1010-
yield element
1011-
# For use cases that allow the last matching element to be returned,
1012-
# a faster but non-lazy solution is:
1013-
# t1, t2 = tee(iterable)
1014-
# yield from dict(zip(map(key, t1), t2)).values()
1015-
1016-
def unique_justseen(iterable, key=None):
1017-
"List unique elements, preserving order. Remember only the element just seen."
1018-
# unique_justseen('AAAABBBCCDAABBB') --> A B C D A B
1019-
# unique_justseen('ABBcCAD', str.lower) --> A B c A D
1020-
if key is None:
1021-
return map(operator.itemgetter(0), groupby(iterable))
1022-
return map(next, map(operator.itemgetter(1), groupby(iterable, key)))
1024+
return true_iterator(), chain(transition, it)
10231025

10241026

10251027
The following recipes have a more mathematical flavor:
@@ -1550,16 +1552,16 @@ The following recipes have a more mathematical flavor:
15501552

15511553
>>> list(unique_everseen('AAAABBBCCDAABBB'))
15521554
['A', 'B', 'C', 'D']
1553-
>>> list(unique_everseen('ABBCcAD', str.lower))
1555+
>>> list(unique_everseen('ABBCcAD', str.casefold))
15541556
['A', 'B', 'C', 'D']
1555-
>>> list(unique_everseen('ABBcCAD', str.lower))
1557+
>>> list(unique_everseen('ABBcCAD', str.casefold))
15561558
['A', 'B', 'c', 'D']
15571559

15581560
>>> list(unique_justseen('AAAABBBCCDAABBB'))
15591561
['A', 'B', 'C', 'D', 'A', 'B']
1560-
>>> list(unique_justseen('ABBCcAD', str.lower))
1562+
>>> list(unique_justseen('ABBCcAD', str.casefold))
15611563
['A', 'B', 'C', 'A', 'D']
1562-
>>> list(unique_justseen('ABBcCAD', str.lower))
1564+
>>> list(unique_justseen('ABBcCAD', str.casefold))
15631565
['A', 'B', 'c', 'A', 'D']
15641566

15651567
>>> d = dict(a=1, b=2, c=3)

0 commit comments

Comments
 (0)