[Python-ideas] Start argument for itertools.accumulate() [Was: Proposal: A Reduce-Map Comprehension and a "last" builtin]
Tim Peters
tim.peters at gmail.com
Sat Apr 7 00:06:30 EDT 2018
[Raymond Hettinger <raymond.hettinger at gmail.com>]
> ...
> 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?
3
> What is the first value emitted?
6
> Are the two sixes related?
No.
> What is this code trying to accomplish?
It's quite obviously trying to bias the reader against the proposal by
presenting a senseless example ;-) Assuming there's any real reason
to write that code at all, a better question is whether it's more
comprehensible than
accumulate(itertools.chain([6], range(4, 6)), operator.mul)
> ...
> 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
> ...
By sheer coincidence, I happened to write another yesterday. This is
from a program looking for the smallest integers that yield new
records for Collatz sequence lengths. The details don't matter,
except that - like Will Ness's wheel sieve code - it needs to generate
an unbounded increasing sequence of integers with a periodic, but
complicated, sequence of deltas, starting at a more-or-less arbitrary
point.
def buildtab(SHIFT, LIMIT):
...
# Candidates are of the form i*LIMIT + j, for i >= 1 and j in
# goodix. However, a new record can't be set for a number of
# the form 3k+2: that's two steps after 2k+1, so the smaller
# 2k+1 has a glide 2 longer. We want to arrange to never try
# numbers of the form 3k+2 to begin with.
base = 0
ix2 = []
for i in range(3):
base += LIMIT
for ix in goodix:
num = base + ix
if num % 3 != 2:
ix2.append(num)
ix2.append(ix2[0] + 3 * LIMIT)
assert len(ix2) == 2 * len(goodix) + 1
del goodix
deltas = tuple(ix2[i] - ix2[i-1] for i in range(1, len(ix2)))
return tuple(result), ix2[0], deltas
A note on "complicated": the tuple of deltas here can contain
millions of integers, and that's the smallest length at which it
becomes periodic.
Later:
def coll(SHIFT=24):
...
from itertools import accumulate, chain, cycle
...
LIMIT = 1 << SHIFT
...
abc, first, deltas = buildtab(SHIFT, LIMIT)
...
for num in accumulate(chain([first], cycle(deltas))):
assert num % 3 != 2
As in Will's code, it would be more readable as:
for num in accumulate(cycle(deltas), start=first):
That says what it does pretty clearly, whereas deducing the behavior
from "OK, it's chaining together a singleton list and a cycle, because
...?" is a bit of a head scratcher at first.
That said, if the need came up often, as you noted it's dead easy to
write a helper function to encapsulate the "head scratcher" part, and
with no significant loss of efficiency.
So I'd be -0 overall, _except_ that "chain together a singleton list
and a cycle" is so obscure on the face of it than I'm not sure most
programmers who wanted the functionality of `start=` would ever think
of it. I'm not sure that I would have, except that I studied Ness's
wheel sieve code a long time ago and the idea stuck. So that makes me
+0.4.
More information about the Python-ideas
mailing list