@@ -836,6 +836,7 @@ and :term:`generators <generator>` which incur interpreter overhead.
836836 from collections import Counter, deque
837837 from contextlib import suppress
838838 from functools import reduce
839+ from heapq import heappush, heappushpop, heappush_max, heappushpop_max
839840 from math import comb, isqrt, prod, sumprod
840841 from operator import getitem, is_not, itemgetter, mul, neg, truediv
841842
@@ -851,11 +852,6 @@ and :term:`generators <generator>` which incur interpreter overhead.
851852 # prepend(1, [2, 3, 4]) → 1 2 3 4
852853 return chain([value], iterable)
853854
854- def running_mean(iterable):
855- "Yield the average of all values seen so far."
856- # running_mean([8.5, 9.5, 7.5, 6.5]) → 8.5 9.0 8.5 8.0
857- return map(truediv, accumulate(iterable), count(1))
858-
859855 def repeatfunc(function, times=None, *args):
860856 "Repeat calls to a function with specified arguments."
861857 if times is None:
@@ -1153,6 +1149,49 @@ and :term:`generators <generator>` which incur interpreter overhead.
11531149 return n
11541150
11551151
1152+ # ==== Running statistics ====
1153+
1154+ def running_mean(iterable):
1155+ "Average of values seen so far."
1156+ # running_mean([37, 33, 38, 28]) → 37 35 36 34
1157+ return map(truediv, accumulate(iterable), count(1))
1158+
1159+ def running_min(iterable):
1160+ "Smallest of values seen so far."
1161+ # running_min([37, 33, 38, 28]) → 37 33 33 28
1162+ return accumulate(iterable, func=min)
1163+
1164+ def running_max(iterable):
1165+ "Largest of values seen so far."
1166+ # running_max([37, 33, 38, 28]) → 37 37 38 38
1167+ return accumulate(iterable, func=max)
1168+
1169+ def running_median(iterable):
1170+ "Median of values seen so far."
1171+ # running_median([37, 33, 38, 28]) → 37 35 37 35
1172+ read = iter(iterable).__next__
1173+ lo = [] # max-heap
1174+ hi = [] # min-heap the same size as or one smaller than lo
1175+ with suppress(StopIteration):
1176+ while True:
1177+ heappush_max(lo, heappushpop(hi, read()))
1178+ yield lo[0]
1179+ heappush(hi, heappushpop_max(lo, read()))
1180+ yield (lo[0] + hi[0]) / 2
1181+
1182+ def running_statistics(iterable):
1183+ "Aggregate statistics for values seen so far."
1184+ # Generate tuples: (size, minimum, median, maximum, mean)
1185+ t0, t1, t2, t3 = tee(iterable, 4)
1186+ return zip(
1187+ count(1),
1188+ running_min(t0),
1189+ running_median(t1),
1190+ running_max(t2),
1191+ running_mean(t3),
1192+ )
1193+
1194+
11561195.. doctest ::
11571196 :hide:
11581197
@@ -1229,10 +1268,6 @@ and :term:`generators <generator>` which incur interpreter overhead.
12291268 [(0, 'a'), (1, 'b'), (2, 'c')]
12301269
12311270
1232- >>> list (running_mean([8.5 , 9.5 , 7.5 , 6.5 ]))
1233- [8.5, 9.0, 8.5, 8.0]
1234-
1235-
12361271 >>> for _ in loops(5 ):
12371272 ... print (' hi' )
12381273 ...
@@ -1792,6 +1827,28 @@ and :term:`generators <generator>` which incur interpreter overhead.
17921827 True
17931828
17941829
1830+ >>> list (running_mean([8.5 , 9.5 , 7.5 , 6.5 ]))
1831+ [8.5, 9.0, 8.5, 8.0]
1832+ >>> list (running_mean([37 , 33 , 38 , 28 ]))
1833+ [37.0, 35.0, 36.0, 34.0]
1834+
1835+
1836+ >>> list (running_min([37 , 33 , 38 , 28 ]))
1837+ [37, 33, 33, 28]
1838+
1839+
1840+ >>> list (running_max([37 , 33 , 38 , 28 ]))
1841+ [37, 37, 38, 38]
1842+
1843+
1844+ >>> list (running_median([37 , 33 , 38 , 28 ]))
1845+ [37, 35.0, 37, 35.0]
1846+
1847+
1848+ >>> list (running_statistics([37 , 33 , 38 , 28 ]))
1849+ [(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)]
1850+
1851+
17951852.. testcode ::
17961853 :hide:
17971854
0 commit comments