From 61ea80328d02d68411b977bcb6c22594338fbb53 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Wed, 8 Apr 2026 15:26:41 -0500 Subject: [PATCH 1/5] Add running statistics recipes. Needs tests. --- Doc/library/itertools.rst | 49 +++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index 5a0ac60ab7d2ec..c8a0172bf76eab 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -848,11 +848,6 @@ and :term:`generators ` which incur interpreter overhead. # prepend(1, [2, 3, 4]) → 1 2 3 4 return chain([value], iterable) - def running_mean(iterable): - "Yield the average of all values seen so far." - # running_mean([8.5, 9.5, 7.5, 6.5]) → 8.5 9.0 8.5 8.0 - return map(truediv, accumulate(iterable), count(1)) - def repeatfunc(function, times=None, *args): "Repeat calls to a function with specified arguments." if times is None: @@ -1150,6 +1145,50 @@ and :term:`generators ` which incur interpreter overhead. return n + # ==== Running statistics ==== + + def running_min(iterable): + "Smallest of values seen so far." + # running_min([37, 33, 38, 28]) -> 37 33 33 28 + return accumulate(iterable, func=min) + + def running_median(iterable): + "Median of values seen so far." + # running_median([37, 33, 38, 28]) -> 37 35 37 35 + read = iter(iterable).__next__ + lo = [] # max-heap + hi = [] # min-heap (same size as or one smaller than lo) + + with suppress(StopIteration): + while True: + heappush_max(lo, heappushpop(hi, read())) + yield lo[0] + heappush(hi, heappushpop_max(lo, read())) + yield (lo[0] + hi[0]) / 2 + + def running_max(iterable): + "Largest of values seen so far." + # running_max([37, 33, 38, 28]) -> 37 37 38 38 + return accumulate(iterable, func=max) + + def running_mean(iterable): + "Average of values seen so far." + # running_mean([37, 33, 38, 28]) -> 37 35 36 34 + return map(truediv, accumulate(iterable), count(1)) + + def running_statistics(iterable): + "Aggregate statistics for values seen so far." + # Generate tuples: (size, minimum, median, maximum, mean) + t0, t1, t2, t3 = tee(iterable, 4) + return zip( + count(1), + running_min(t0), + running_median(t1), + running_max(t2), + running_mean(t3), + ) + + .. doctest:: :hide: From eb8e41cb2b160db43fdc5ca8d7c501693fd3de67 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Wed, 8 Apr 2026 15:28:45 -0500 Subject: [PATCH 2/5] Better ordering --- Doc/library/itertools.rst | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index c8a0172bf76eab..47abb686c340af 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -1147,11 +1147,21 @@ and :term:`generators ` which incur interpreter overhead. # ==== Running statistics ==== + def running_mean(iterable): + "Average of values seen so far." + # running_mean([37, 33, 38, 28]) -> 37 35 36 34 + return map(truediv, accumulate(iterable), count(1)) + def running_min(iterable): "Smallest of values seen so far." # running_min([37, 33, 38, 28]) -> 37 33 33 28 return accumulate(iterable, func=min) + def running_max(iterable): + "Largest of values seen so far." + # running_max([37, 33, 38, 28]) -> 37 37 38 38 + return accumulate(iterable, func=max) + def running_median(iterable): "Median of values seen so far." # running_median([37, 33, 38, 28]) -> 37 35 37 35 @@ -1166,16 +1176,6 @@ and :term:`generators ` which incur interpreter overhead. heappush(hi, heappushpop_max(lo, read())) yield (lo[0] + hi[0]) / 2 - def running_max(iterable): - "Largest of values seen so far." - # running_max([37, 33, 38, 28]) -> 37 37 38 38 - return accumulate(iterable, func=max) - - def running_mean(iterable): - "Average of values seen so far." - # running_mean([37, 33, 38, 28]) -> 37 35 36 34 - return map(truediv, accumulate(iterable), count(1)) - def running_statistics(iterable): "Aggregate statistics for values seen so far." # Generate tuples: (size, minimum, median, maximum, mean) From 4bb24de103d89b0def17dc57edbcf204862bfbea Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Wed, 8 Apr 2026 15:47:35 -0500 Subject: [PATCH 3/5] . --- Doc/library/itertools.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index 47abb686c340af..cc16f832ec046f 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -1167,8 +1167,7 @@ and :term:`generators ` which incur interpreter overhead. # running_median([37, 33, 38, 28]) -> 37 35 37 35 read = iter(iterable).__next__ lo = [] # max-heap - hi = [] # min-heap (same size as or one smaller than lo) - + hi = [] # min-heap the same size as or one smaller than lo with suppress(StopIteration): while True: heappush_max(lo, heappushpop(hi, read())) From 3c4648ee698ab3e0bdeb717e9d4d9ed19ab16ee2 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Wed, 22 Apr 2026 10:10:48 -0500 Subject: [PATCH 4/5] Add imports --- Doc/library/itertools.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index cc16f832ec046f..bd8d863ee0a9e8 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -833,6 +833,7 @@ and :term:`generators ` which incur interpreter overhead. from collections import Counter, deque from contextlib import suppress from functools import reduce + from heapq import heappush, heappushpop, heappush_max, heappushpop_max from math import comb, isqrt, prod, sumprod from operator import getitem, is_not, itemgetter, mul, neg, truediv From dec5cd1c0c75b908d2a7bb5db3e2779b3e864d2d Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Wed, 22 Apr 2026 11:19:33 -0500 Subject: [PATCH 5/5] Add tests --- Doc/library/itertools.rst | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index bd8d863ee0a9e8..06f8bf2a8b6fa8 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -1150,22 +1150,22 @@ and :term:`generators ` which incur interpreter overhead. def running_mean(iterable): "Average of values seen so far." - # running_mean([37, 33, 38, 28]) -> 37 35 36 34 + # running_mean([37, 33, 38, 28]) → 37 35 36 34 return map(truediv, accumulate(iterable), count(1)) def running_min(iterable): "Smallest of values seen so far." - # running_min([37, 33, 38, 28]) -> 37 33 33 28 + # running_min([37, 33, 38, 28]) → 37 33 33 28 return accumulate(iterable, func=min) def running_max(iterable): "Largest of values seen so far." - # running_max([37, 33, 38, 28]) -> 37 37 38 38 + # running_max([37, 33, 38, 28]) → 37 37 38 38 return accumulate(iterable, func=max) def running_median(iterable): "Median of values seen so far." - # running_median([37, 33, 38, 28]) -> 37 35 37 35 + # running_median([37, 33, 38, 28]) → 37 35 37 35 read = iter(iterable).__next__ lo = [] # max-heap hi = [] # min-heap the same size as or one smaller than lo @@ -1265,10 +1265,6 @@ and :term:`generators ` which incur interpreter overhead. [(0, 'a'), (1, 'b'), (2, 'c')] - >>> list(running_mean([8.5, 9.5, 7.5, 6.5])) - [8.5, 9.0, 8.5, 8.0] - - >>> for _ in loops(5): ... print('hi') ... @@ -1828,6 +1824,28 @@ and :term:`generators ` which incur interpreter overhead. True + >>> list(running_mean([8.5, 9.5, 7.5, 6.5])) + [8.5, 9.0, 8.5, 8.0] + >>> list(running_mean([37, 33, 38, 28])) + [37.0, 35.0, 36.0, 34.0] + + + >>> list(running_min([37, 33, 38, 28])) + [37, 33, 33, 28] + + + >>> list(running_max([37, 33, 38, 28])) + [37, 37, 38, 38] + + + >>> list(running_median([37, 33, 38, 28])) + [37, 35.0, 37, 35.0] + + + >>> list(running_statistics([37, 33, 38, 28])) + [(1, 37, 37, 37, 37.0), (2, 33, 35.0, 37, 35.0), (3, 33, 37, 38, 36.0), (4, 28, 35.0, 38, 34.0)] + + .. testcode:: :hide: