On Aug 9, 2019, at 08:47, Peter O'Connor <[email protected]> wrote:
>
> I've often found that it would be useful for the following type of expression
> to be condensed to a one-liner:
>
> def running_average(x_seq):
> averages = []
> avg = 0
> for t, x in enumerate(x_seq):
> avg = avg*t/(t+1) + x/(t+1)
> averages.append(avg)
> return averages
>
> Because really, there's only one line doing the heavy lifting here, the rest
> is kind of boilerplate.
It seems like the only reason you can’t write this with accumulate is that
accumulate doesn’t take a start value like reduce does?
And I think this would be a lot clearer and more readable, especially if you’re
doing this kind of thing more than once:
def running(xs, func, start):
yield from accumulate(enumerate(xs), lambda avg, told: func(avg, *tx),
start)
def running_average(xs, func):
yield from running(xs, lambda avg, t, x: avg*t/(t+1) + x/(t+1), 0.0)
Now the part that does the heavy lifting is all in one place and just does what
it’s says, without being confusingly interleaved with the boilerplate. Plus,
the parts of the boilerplate that are reusable are abstracted into functions
(accumulate and running) that can be reused, while the rest of it has vanished.
(This is one of those rare cases where 2.x-style decomposing def/lambda was
actually useful, but if that extra lambda in running really bothers you, that’s
another one-liner HOF you can abstract out trivially and reuse.)
More generally, it’s a lot easier to use comprehensions and higher order
functions if your algorithm can be written in terms of “generate the next
immutable value” instead of “update the mutable variable”, and I don’t think
that’s a limitation of the language. Comprehensions are much more readable when
they’re declarative than when they’re for statements in disguise.
Also, I don’t think the reason people were objecting to your four-clause
comprehension was that it wasn’t easy enough to tell that the innermost clause
only “loops” exactly one time, but that it’s a comprehension with four clauses
in the first place. Changing the spelling of that clause to make the
no-actual-looping doesn’t solve that.
Finally, you can already play tricks with the walrus operator to avoid moving
things like initialization outside the comprehension, just as you could with
your proposed syntax. For example, “for t, x in (avg:=0) or enumerate(xs)” is a
perfectly valid clause that assigns 0 to avg and then loops over the enumerate,
and it doesn’t require you to turn one for clause into two. But it still adds
just as much complexity for the reader to deal with, so I think you’re still
better off not doing it.
(As a side note, you probably want a numerical stable average like the ones in
statistics or numpy, rather than one that accumulates float rounding errors
indiscriminately, but that’s another issue.)
_______________________________________________
Python-ideas mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at
https://mail.python.org/archives/list/[email protected]/message/3RPVRDFALMRMYHWYLCP4EPTOI4LKQIZY/
Code of Conduct: http://python.org/psf/codeofconduct/