@@ -902,18 +902,19 @@ which incur interpreter overhead.
902902 # iter_index('AABCADEAF', 'A') --> 0 1 4 7
903903 seq_index = getattr(iterable, 'index', None)
904904 if seq_index is None:
905- # Slow path for general iterables
905+ # Path for general iterables
906906 it = islice(iterable, start, stop)
907907 for i, element in enumerate(it, start):
908908 if element is value or element == value:
909909 yield i
910910 else:
911- # Fast path for sequences
911+ # Path for sequences with an index() method
912912 stop = len(iterable) if stop is None else stop
913- i = start - 1
913+ i = start
914914 try:
915915 while True:
916- yield (i := seq_index(value, i+1, stop))
916+ yield (i := seq_index(value, i, stop))
917+ i += 1
917918 except ValueError:
918919 pass
919920
@@ -931,31 +932,25 @@ which incur interpreter overhead.
931932 # grouper('ABCDEFG', 3, fillvalue='x') --> ABC DEF Gxx
932933 # grouper('ABCDEFG', 3, incomplete='strict') --> ABC DEF ValueError
933934 # grouper('ABCDEFG', 3, incomplete='ignore') --> ABC DEF
934- args = [iter(iterable)] * n
935+ iterators = [iter(iterable)] * n
935936 match incomplete:
936937 case 'fill':
937- return zip_longest(*args , fillvalue=fillvalue)
938+ return zip_longest(*iterators , fillvalue=fillvalue)
938939 case 'strict':
939- return zip(*args , strict=True)
940+ return zip(*iterators , strict=True)
940941 case 'ignore':
941- return zip(*args )
942+ return zip(*iterators )
942943 case _:
943944 raise ValueError('Expected fill, strict, or ignore')
944945
945946 def roundrobin(*iterables):
946947 "Visit input iterables in a cycle until each is exhausted."
947948 # roundrobin('ABC', 'D', 'EF') --> A D E B F C
948- # Recipe credited to George Sakkis
949- num_active = len(iterables)
950- nexts = cycle(iter(it).__next__ for it in iterables)
951- while num_active:
952- try:
953- for next in nexts:
954- yield next()
955- except StopIteration:
956- # Remove the iterator we just exhausted from the cycle.
957- num_active -= 1
958- nexts = cycle(islice(nexts, num_active))
949+ # Algorithm credited to George Sakkis
950+ iterators = map(iter, iterables)
951+ for num_active in range(len(iterables), 0, -1):
952+ iterators = cycle(islice(iterators, num_active))
953+ yield from map(next, iterators)
959954
960955 def partition(predicate, iterable):
961956 """Partition entries into false entries and true entries.
@@ -996,10 +991,10 @@ The following recipes have a more mathematical flavor:
996991 s = list(iterable)
997992 return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))
998993
999- def sum_of_squares(it ):
994+ def sum_of_squares(iterable ):
1000995 "Add up the squares of the input values."
1001- # sum_of_squares([10, 20, 30]) -> 1400
1002- return math.sumprod(*tee(it ))
996+ # sum_of_squares([10, 20, 30]) -- > 1400
997+ return math.sumprod(*tee(iterable ))
1003998
1004999 def reshape(matrix, cols):
10051000 "Reshape a 2-D matrix to have a given number of columns."
@@ -1019,17 +1014,16 @@ The following recipes have a more mathematical flavor:
10191014
10201015 def convolve(signal, kernel):
10211016 """Discrete linear convolution of two iterables.
1017+ Equivalent to polynomial multiplication.
10221018
1023- The kernel is fully consumed before the calculations begin.
1024- The signal is consumed lazily and can be infinite.
1025-
1026- Convolutions are mathematically commutative.
1027- If the signal and kernel are swapped,
1028- the output will be the same.
1019+ Convolutions are mathematically commutative; however, the inputs are
1020+ evaluated differently. The signal is consumed lazily and can be
1021+ infinite. The kernel is fully consumed before the calculations begin.
10291022
10301023 Article: https://betterexplained.com/articles/intuitive-convolution/
10311024 Video: https://www.youtube.com/watch?v=KuXjwB4LzSA
10321025 """
1026+ # convolve([1, -1, -20], [1, -3]) --> 1 -4 -17 60
10331027 # convolve(data, [0.25, 0.25, 0.25, 0.25]) --> Moving average (blur)
10341028 # convolve(data, [1/2, 0, -1/2]) --> 1st derivative estimate
10351029 # convolve(data, [1, -2, 1]) --> 2nd derivative estimate
@@ -1067,7 +1061,7 @@ The following recipes have a more mathematical flavor:
10671061 f(x) = x³ -4x² -17x + 60
10681062 f'(x) = 3x² -8x -17
10691063 """
1070- # polynomial_derivative([1, -4, -17, 60]) -> [3, -8, -17]
1064+ # polynomial_derivative([1, -4, -17, 60]) -- > [3, -8, -17]
10711065 n = len(coefficients)
10721066 powers = reversed(range(1, n))
10731067 return list(map(operator.mul, coefficients, powers))
@@ -1169,6 +1163,12 @@ The following recipes have a more mathematical flavor:
11691163
11701164 >>> take(10 , count())
11711165 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1166+ >>> # Verify that the input is consumed lazily
1167+ >>> it = iter (' abcdef' )
1168+ >>> take(3 , it)
1169+ ['a', 'b', 'c']
1170+ >>> list (it)
1171+ ['d', 'e', 'f']
11721172
11731173 >>> list (prepend(1 , [2 , 3 , 4 ]))
11741174 [1, 2, 3, 4]
@@ -1181,25 +1181,45 @@ The following recipes have a more mathematical flavor:
11811181
11821182 >>> list (tail(3 , ' ABCDEFG' ))
11831183 ['E', 'F', 'G']
1184+ >>> # Verify the input is consumed greedily
1185+ >>> input_iterator = iter (' ABCDEFG' )
1186+ >>> output_iterator = tail(3 , input_iterator)
1187+ >>> list (input_iterator)
1188+ []
11841189
11851190 >>> it = iter (range (10 ))
11861191 >>> consume(it, 3 )
1192+ >>> # Verify the input is consumed lazily
11871193 >>> next (it)
11881194 3
1195+ >>> # Verify the input is consumed completely
11891196 >>> consume(it)
11901197 >>> next (it, ' Done' )
11911198 'Done'
11921199
11931200 >>> nth(' abcde' , 3 )
11941201 'd'
1195-
11961202 >>> nth(' abcde' , 9 ) is None
11971203 True
1204+ >>> # Verify that the input is consumed lazily
1205+ >>> it = iter (' abcde' )
1206+ >>> nth(it, 2 )
1207+ 'c'
1208+ >>> list (it)
1209+ ['d', 'e']
11981210
11991211 >>> [all_equal(s) for s in (' ' , ' A' , ' AAAA' , ' AAAB' , ' AAABA' )]
12001212 [True, True, True, False, False]
12011213 >>> [all_equal(s, key = str .casefold) for s in (' ' , ' A' , ' AaAa' , ' AAAB' , ' AAABA' )]
12021214 [True, True, True, False, False]
1215+ >>> # Verify that the input is consumed lazily and that only
1216+ >>> # one element of a second equivalence class is used to disprove
1217+ >>> # the assertion that all elements are equal.
1218+ >>> it = iter (' aaabbbccc' )
1219+ >>> all_equal(it)
1220+ False
1221+ >>> ' ' .join(it)
1222+ 'bbccc'
12031223
12041224 >>> quantify(range (99 ), lambda x : x% 2 == 0 )
12051225 50
@@ -1222,6 +1242,11 @@ The following recipes have a more mathematical flavor:
12221242
12231243 >>> list (ncycles(' abc' , 3 ))
12241244 ['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c']
1245+ >>> # Verify greedy consumption of input iterator
1246+ >>> input_iterator = iter (' abc' )
1247+ >>> output_iterator = ncycles(input_iterator, 3 )
1248+ >>> list (input_iterator)
1249+ []
12251250
12261251 >>> sum_of_squares([10 , 20 , 30 ])
12271252 1400
@@ -1248,19 +1273,41 @@ The following recipes have a more mathematical flavor:
12481273
12491274 >>> list (transpose([(1 , 2 , 3 ), (11 , 22 , 33 )]))
12501275 [(1, 11), (2, 22), (3, 33)]
1276+ >>> # Verify that the inputs are consumed lazily
1277+ >>> input1 = iter ([1 , 2 , 3 ])
1278+ >>> input2 = iter ([11 , 22 , 33 ])
1279+ >>> output_iterator = transpose([input1, input2])
1280+ >>> next (output_iterator)
1281+ (1, 11)
1282+ >>> list (zip (input1, input2))
1283+ [(2, 22), (3, 33)]
12511284
12521285 >>> list (matmul([(7 , 5 ), (3 , 5 )], [[2 , 5 ], [7 , 9 ]]))
12531286 [(49, 80), (41, 60)]
12541287 >>> list (matmul([[2 , 5 ], [7 , 9 ], [3 , 4 ]], [[7 , 11 , 5 , 4 , 9 ], [3 , 5 , 2 , 6 , 3 ]]))
12551288 [(29, 47, 20, 38, 33), (76, 122, 53, 82, 90), (33, 53, 23, 36, 39)]
12561289
1290+ >>> list (convolve([1 , - 1 , - 20 ], [1 , - 3 ])) == [1 , - 4 , - 17 , 60 ]
1291+ True
12571292 >>> data = [20 , 40 , 24 , 32 , 20 , 28 , 16 ]
12581293 >>> list (convolve(data, [0.25 , 0.25 , 0.25 , 0.25 ]))
12591294 [5.0, 15.0, 21.0, 29.0, 29.0, 26.0, 24.0, 16.0, 11.0, 4.0]
12601295 >>> list (convolve(data, [1 , - 1 ]))
12611296 [20, 20, -16, 8, -12, 8, -12, -16]
12621297 >>> list (convolve(data, [1 , - 2 , 1 ]))
12631298 [20, 0, -36, 24, -20, 20, -20, -4, 16]
1299+ >>> # Verify signal is consumed lazily and the kernel greedily
1300+ >>> signal_iterator = iter ([10 , 20 , 30 , 40 , 50 ])
1301+ >>> kernel_iterator = iter ([1 , 2 , 3 ])
1302+ >>> output_iterator = convolve(signal_iterator, kernel_iterator)
1303+ >>> list (kernel_iterator)
1304+ []
1305+ >>> next (output_iterator)
1306+ 10
1307+ >>> next (output_iterator)
1308+ 40
1309+ >>> list (signal_iterator)
1310+ [30, 40, 50]
12641311
12651312 >>> from fractions import Fraction
12661313 >>> from decimal import Decimal
@@ -1348,6 +1395,33 @@ The following recipes have a more mathematical flavor:
13481395 >>> # Test list input. Lists do not support None for the stop argument
13491396 >>> list (iter_index(list (' AABCADEAF' ), ' A' ))
13501397 [0, 1, 4, 7]
1398+ >>> # Verify that input is consumed lazily
1399+ >>> input_iterator = iter (' AABCADEAF' )
1400+ >>> output_iterator = iter_index(input_iterator, ' A' )
1401+ >>> next (output_iterator)
1402+ 0
1403+ >>> next (output_iterator)
1404+ 1
1405+ >>> next (output_iterator)
1406+ 4
1407+ >>> ' ' .join(input_iterator)
1408+ 'DEAF'
1409+
1410+ >>> # Verify that the target value can be a sequence.
1411+ >>> seq = [[10 , 20 ], [30 , 40 ], 30 , 40 , [30 , 40 ], 50 ]
1412+ >>> target = [30 , 40 ]
1413+ >>> list (iter_index(seq, target))
1414+ [1, 4]
1415+
1416+ >>> # Verify faithfulness to type specific index() method behaviors.
1417+ >>> # For example, bytes and str perform subsequence searches
1418+ >>> # that do not match the general behavior specified
1419+ >>> # in collections.abc.Sequence.index().
1420+ >>> seq = ' abracadabra'
1421+ >>> target = ' ab'
1422+ >>> list (iter_index(seq, target))
1423+ [0, 7]
1424+
13511425
13521426 >>> list (sieve(30 ))
13531427 [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
@@ -1490,6 +1564,9 @@ The following recipes have a more mathematical flavor:
14901564
14911565 >>> list (roundrobin(' abc' , ' d' , ' ef' ))
14921566 ['a', 'd', 'e', 'b', 'f', 'c']
1567+ >>> ranges = [range (5 , 1000 ), range (4 , 3000 ), range (0 ), range (3 , 2000 ), range (2 , 5000 ), range (1 , 3500 )]
1568+ >>> collections.Counter(roundrobin(ranges)) == collections.Counter(ranges)
1569+ True
14931570
14941571 >>> def is_odd (x ):
14951572 ... return x % 2 == 1
@@ -1499,6 +1576,17 @@ The following recipes have a more mathematical flavor:
14991576 [0, 2, 4, 6, 8]
15001577 >>> list (odds)
15011578 [1, 3, 5, 7, 9]
1579+ >>> # Verify that the input is consumed lazily
1580+ >>> input_iterator = iter (range (10 ))
1581+ >>> evens, odds = partition(is_odd, input_iterator)
1582+ >>> next (odds)
1583+ 1
1584+ >>> next (odds)
1585+ 3
1586+ >>> next (evens)
1587+ 0
1588+ >>> list (input_iterator)
1589+ [4, 5, 6, 7, 8, 9]
15021590
15031591 >>> list (subslices(' ABCD' ))
15041592 ['A', 'AB', 'ABC', 'ABCD', 'B', 'BC', 'BCD', 'C', 'CD', 'D']
@@ -1518,13 +1606,27 @@ The following recipes have a more mathematical flavor:
15181606 ['A', 'B', 'C', 'D']
15191607 >>> list (unique_everseen(' ABBcCAD' , str .casefold))
15201608 ['A', 'B', 'c', 'D']
1609+ >>> # Verify that the input is consumed lazily
1610+ >>> input_iterator = iter (' AAAABBBCCDAABBB' )
1611+ >>> output_iterator = unique_everseen(input_iterator)
1612+ >>> next (output_iterator)
1613+ 'A'
1614+ >>> ' ' .join(input_iterator)
1615+ 'AAABBBCCDAABBB'
15211616
15221617 >>> list (unique_justseen(' AAAABBBCCDAABBB' ))
15231618 ['A', 'B', 'C', 'D', 'A', 'B']
15241619 >>> list (unique_justseen(' ABBCcAD' , str .casefold))
15251620 ['A', 'B', 'C', 'A', 'D']
15261621 >>> list (unique_justseen(' ABBcCAD' , str .casefold))
15271622 ['A', 'B', 'c', 'A', 'D']
1623+ >>> # Verify that the input is consumed lazily
1624+ >>> input_iterator = iter (' AAAABBBCCDAABBB' )
1625+ >>> output_iterator = unique_justseen(input_iterator)
1626+ >>> next (output_iterator)
1627+ 'A'
1628+ >>> ' ' .join(input_iterator)
1629+ 'AAABBBCCDAABBB'
15281630
15291631 >>> d = dict (a = 1 , b = 2 , c = 3 )
15301632 >>> it = iter_except(d.popitem, KeyError )
@@ -1545,6 +1647,12 @@ The following recipes have a more mathematical flavor:
15451647
15461648 >>> first_true(' ABC0DEF1' , ' 9' , str .isdigit)
15471649 '0'
1650+ >>> # Verify that inputs are consumed lazily
1651+ >>> it = iter (' ABC0DEF1' )
1652+ >>> first_true(it, predicate = str .isdigit)
1653+ '0'
1654+ >>> ' ' .join(it)
1655+ 'DEF1'
15481656
15491657
15501658.. testcode ::
0 commit comments