From 0196c478830449d5d41fd25acf63240c481782a6 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Tue, 12 Sep 2023 22:26:27 +0200 Subject: [PATCH 1/5] Improve the clarity of the polynomial_eval type coercion logic --- Doc/library/itertools.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index 5e187aea441bb9..f003ec8a4f44bb 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -1104,8 +1104,8 @@ The following recipes have a more mathematical flavor: # Evaluate x³ -4x² -17x + 60 at x = 2.5 # polynomial_eval([1, -4, -17, 60], x=2.5) --> 8.125 n = len(coefficients) - if n == 0: - return x * 0 # coerce zero to the type of x + if not n: + return type(x)(0) powers = map(pow, repeat(x), reversed(range(n))) return math.sumprod(coefficients, powers) From c83b02fdcc4b1857672a6663aa5c3f11295be598 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Wed, 13 Sep 2023 13:11:17 +0200 Subject: [PATCH 2/5] Improve docstring and comments for the convolve() recipe --- Doc/library/itertools.rst | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index f003ec8a4f44bb..8685373e896946 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -1073,14 +1073,22 @@ The following recipes have a more mathematical flavor: return batched(starmap(math.sumprod, product(m1, transpose(m2))), n) def convolve(signal, kernel): - """Linear convolution of two iterables. + """Discrete linear convolution of two iterables. + + The kernel is fully consumed before the calculations begin. + The signal is consumed lazily and can be infinite. + + Convolutions are mathematically commutative. + If the signal and kernel are swapped, the + output will be the same. Article: https://betterexplained.com/articles/intuitive-convolution/ Video: https://www.youtube.com/watch?v=KuXjwB4LzSA + Tables: https://www.ams.org/journals/mcom/1988-51-184/S0025-5718-1988-0935077-0/S0025-5718-1988-0935077-0.pdf """ # convolve(data, [0.25, 0.25, 0.25, 0.25]) --> Moving average (blur) - # convolve(data, [1, -1]) --> 1st finite difference (1st derivative) - # convolve(data, [1, -2, 1]) --> 2nd finite difference (2nd derivative) + # convolve(data, [1/2, 0.0, -1/2]) --> Estimate 1st derivative + # convolve(data, [1, -2, 1]) --> Estimate 2nd derivative kernel = tuple(kernel)[::-1] n = len(kernel) padded_signal = chain(repeat(0, n-1), signal, repeat(0, n-1)) From f5a4aa014e88854d4d3a28a7c8a4f10e3e091fa0 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Wed, 13 Sep 2023 13:34:42 +0200 Subject: [PATCH 3/5] Move the succinct recipes upward --- Doc/library/itertools.rst | 65 +++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index 8685373e896946..eacc81b8501ba2 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -1028,34 +1028,6 @@ The following recipes have a more mathematical flavor: s = list(iterable) return chain.from_iterable(combinations(s, r) for r in range(len(s)+1)) - def sieve(n): - "Primes less than n." - # sieve(30) --> 2 3 5 7 11 13 17 19 23 29 - if n > 2: - yield 2 - start = 3 - data = bytearray((0, 1)) * (n // 2) - limit = math.isqrt(n) + 1 - for p in iter_index(data, 1, start, limit): - yield from iter_index(data, 1, start, p*p) - data[p*p : n : p+p] = bytes(len(range(p*p, n, p+p))) - start = p*p - yield from iter_index(data, 1, start) - - def factor(n): - "Prime factors of n." - # factor(99) --> 3 3 11 - # factor(1_000_000_000_000_007) --> 47 59 360620266859 - # factor(1_000_000_000_000_403) --> 1000000000000403 - for prime in sieve(math.isqrt(n) + 1): - while not n % prime: - yield prime - n //= prime - if n == 1: - return - if n > 1: - yield n - def sum_of_squares(it): "Add up the squares of the input values." # sum_of_squares([10, 20, 30]) -> 1400 @@ -1079,16 +1051,15 @@ The following recipes have a more mathematical flavor: The signal is consumed lazily and can be infinite. Convolutions are mathematically commutative. - If the signal and kernel are swapped, the - output will be the same. + If the signal and kernel are swapped, + the output will be the same. Article: https://betterexplained.com/articles/intuitive-convolution/ Video: https://www.youtube.com/watch?v=KuXjwB4LzSA - Tables: https://www.ams.org/journals/mcom/1988-51-184/S0025-5718-1988-0935077-0/S0025-5718-1988-0935077-0.pdf """ # convolve(data, [0.25, 0.25, 0.25, 0.25]) --> Moving average (blur) - # convolve(data, [1/2, 0.0, -1/2]) --> Estimate 1st derivative - # convolve(data, [1, -2, 1]) --> Estimate 2nd derivative + # convolve(data, [1/2, 0, -1/2]) --> 1st derivative estimate + # convolve(data, [1, -2, 1]) --> 2nd derivative estimate kernel = tuple(kernel)[::-1] n = len(kernel) padded_signal = chain(repeat(0, n-1), signal, repeat(0, n-1)) @@ -1128,6 +1099,34 @@ The following recipes have a more mathematical flavor: powers = reversed(range(1, n)) return list(map(operator.mul, coefficients, powers)) + def sieve(n): + "Primes less than n." + # sieve(30) --> 2 3 5 7 11 13 17 19 23 29 + if n > 2: + yield 2 + start = 3 + data = bytearray((0, 1)) * (n // 2) + limit = math.isqrt(n) + 1 + for p in iter_index(data, 1, start, limit): + yield from iter_index(data, 1, start, p*p) + data[p*p : n : p+p] = bytes(len(range(p*p, n, p+p))) + start = p*p + yield from iter_index(data, 1, start) + + def factor(n): + "Prime factors of n." + # factor(99) --> 3 3 11 + # factor(1_000_000_000_000_007) --> 47 59 360620266859 + # factor(1_000_000_000_000_403) --> 1000000000000403 + for prime in sieve(math.isqrt(n) + 1): + while not n % prime: + yield prime + n //= prime + if n == 1: + return + if n > 1: + yield n + def nth_combination(iterable, r, index): "Equivalent to list(combinations(iterable, r))[index]" pool = tuple(iterable) From 19a0d77272ed62a9dd8d83c65e863c981686201f Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Mon, 18 Sep 2023 16:49:41 -0500 Subject: [PATCH 4/5] Clarify that the predicate should return actual booleans rather than truthy values. --- Doc/library/itertools.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index eacc81b8501ba2..6e45f57e877647 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -844,6 +844,7 @@ which incur interpreter overhead. return next(islice(iterable, n, None), default) def quantify(iterable, pred=bool): + "Given a predicate that returns True or False, count the True results." "Count how many times the predicate is True" return sum(map(pred, iterable)) From 700af2be5ed6f9ef52e4a13ace13fdb64709d1ed Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Tue, 19 Sep 2023 01:36:35 -0500 Subject: [PATCH 5/5] Only preserve the type, not other information from a Decimal. --- Doc/library/itertools.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index 6e45f57e877647..bd347e6448f1a0 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -1303,7 +1303,7 @@ The following recipes have a more mathematical flavor: >>> polynomial_eval([], Fraction(2, 3)) Fraction(0, 1) >>> polynomial_eval([], Decimal('1.75')) - Decimal('0.00') + Decimal('0') >>> polynomial_eval([11], 7) == 11 True >>> polynomial_eval([11, 2], 7) == 11 * 7 + 2