[Python-ideas] Start argument for itertools.accumulate() [Was: Proposal: A Reduce-Map Comprehension and a "last" builtin]
Tim Peters
tim.peters at gmail.com
Sun Apr 8 12:38:28 EDT 2018
Another bit of prior art: the Python itertoolz package also supplies
`accumulate()`, with an optional `initial` argument. I stumbled into
that when reading a Stackoverflow "how can I do Haskell's scanl in
Python?" question.
https://toolz.readthedocs.io/en/latest/api.html#toolz.itertoolz.accumulate
On Fri, Apr 6, 2018 at 8:02 PM, Raymond Hettinger
<raymond.hettinger at gmail.com> wrote:
>> On Friday, April 6, 2018 at 8:14:30 AM UTC-7, Guido van Rossum wrote:
>> On Fri, Apr 6, 2018 at 7:47 AM, Peter O'Connor <peter.ed... at gmail.com> wrote:
>>> So some more humble proposals would be:
>>>
>>> 1) An initializer to itertools.accumulate
>>> functools.reduce already has an initializer, I can't see any controversy to adding an initializer to itertools.accumulate
>>
>> See if that's accepted in the bug tracker.
>
> It did come-up once but was closed for a number reasons including lack of use cases. However, Peter's signal processing example does sound interesting, so we could re-open the discussion.
>
> For those who want to think through the pluses and minuses, I've put together a Q&A as food for thought (see below). Everybody's design instincts are different -- I'm curious what you all think think about the proposal.
>
>
> Raymond
>
> ---------------------------------------------
>
> Q. Can it be done?
> A. Yes, it wouldn't be hard.
>
> _sentinel = object()
>
> def accumulate(iterable, func=operator.add, start=_sentinel):
> it = iter(iterable)
> if start is _sentinel:
> try:
> total = next(it)
> except StopIteration:
> return
> else:
> total = start
> yield total
> for element in it:
> total = func(total, element)
> yield total
>
> Q. Do other languages do it?
> A. Numpy, no. R, no. APL, no. Mathematica, no. Haskell, yes.
>
> * http://docs.scipy.org/doc/numpy/reference/generated/numpy.ufunc.accumulate.html
> * https://stat.ethz.ch/R-manual/R-devel/library/base/html/cumsum.html
> * http://microapl.com/apl/apl_concepts_chapter5.html
> \+ 1 2 3 4 5
> 1 3 6 10 15
> * https://reference.wolfram.com/language/ref/Accumulate.html
> * https://www.haskell.org/hoogle/?hoogle=mapAccumL
>
>
> Q. How much work for a person to do it currently?
> A. Almost zero effort to write a simple helper function:
>
> myaccum = lambda it, func, start: accumulate(chain([start], it), func)
>
>
> Q. How common is the need?
> A. Rare.
>
>
> Q. Which would be better, a simple for-loop or a customized itertool?
> A. The itertool is shorter but more opaque (especially with respect
> to the argument order for the function call):
>
> result = [start]
> for x in iterable:
> y = func(result[-1], x)
> result.append(y)
>
> versus:
>
> result = list(accumulate(iterable, func, start=start))
>
>
> Q. How readable is the proposed code?
> A. Look at the following code and ask yourself what it does:
>
> accumulate(range(4, 6), operator.mul, start=6)
>
> Now test your understanding:
>
> How many values are emitted?
> What is the first value emitted?
> Are the two sixes related?
> What is this code trying to accomplish?
>
>
> Q. Are there potential surprises or oddities?
> A. Is it readily apparent which of assertions will succeed?
>
> a1 = sum(range(10))
> a2 = sum(range(10), 0)
> assert a1 == a2
>
> a3 = functools.reduce(operator.add, range(10))
> a4 = functools.reduce(operator.add, range(10), 0)
> assert a3 == a4
>
> a4 = list(accumulate(range(10), operator.add))
> a5 = list(accumulate(range(10), operator.add, start=0))
> assert a5 == a6
>
>
> Q. What did the Python 3.0 Whatsnew document have to say about reduce()?
> A. "Removed reduce(). Use functools.reduce() if you really need it; however, 99 percent of the time an explicit for loop is more readable."
>
>
> Q. What would this look like in real code?
> A. We have almost no real-world examples, but here is one from a StackExchange post:
>
> def wsieve(): # wheel-sieve, by Will Ness. ideone.com/mqO25A->0hIE89
> wh11 = [ 2,4,2,4,6,2,6,4,2,4,6,6, 2,6,4,2,6,4,6,8,4,2,4,2,
> 4,8,6,4,6,2,4,6,2,6,6,4, 2,4,6,2,6,4,2,4,2,10,2,10]
> cs = accumulate(cycle(wh11), start=11)
> yield( next( cs)) # cf. ideone.com/WFv4f
> ps = wsieve() # codereview.stackexchange.com/q/92365/9064
> p = next(ps) # 11
> psq = p*p # 121
> D = dict( zip( accumulate(wh11, start=0), count(0))) # start from
> sieve = {}
> for c in cs:
> if c in sieve:
> wheel = sieve.pop(c)
> for m in wheel:
> if not m in sieve:
> break
> sieve[m] = wheel # sieve[143] = wheel at 187
> elif c < psq:
> yield c
> else: # (c==psq)
> # map (p*) (roll wh from p) = roll (wh*p) from (p*p)
> x = [p*d for d in wh11]
> i = D[ (p-11) % 210]
> wheel = accumulate(cycle(x[i:] + x[:i]), start=psq)
> p = next(ps) ; psq = p*p
> next(wheel) ; m = next(wheel)
> sieve[m] = wheel
> _______________________________________________
> Python-ideas mailing list
> Python-ideas at python.org
> https://mail.python.org/mailman/listinfo/python-ideas
> Code of Conduct: http://python.org/psf/codeofconduct/
More information about the Python-ideas
mailing list