It seems clear that the name "accumulate" has been kind of antiquated since the "func" argument was added and "sum" became just a default.
And people seem to disagree about whether the result should have a length N or length N+1 (where N is the number of elements in the input iterable).
The behaviour where the first element of the return is the same as the first element of the input can be weird and confusing. E.g. compare:
>> list(itertools.accumulate([2, 3, 4], lambda accum, val: accum-val))
[2, -1, -5]
>> list(itertools.accumulate([2, 3, 4], lambda accum, val: val-accum))
[2, 1, 3]
One might expect that since the second function returned the negative of the first function, and both are linear, that the results of the second would be the negative of the first, but that is not the case.
Maybe we can instead let "accumulate" fall into deprecation, and instead add a new more general itertools "reducemap" method:
def reducemap(iterable: Iterable[Any], func: Callable[(Any, Any), Any], initial: Any, include_initial_in_return=False): -> Generator[Any]
Benefits:
- The name is more descriptive of the operation (a reduce operation where we keep values at each step, like a map)
- The existence of include_initial_in_return=False makes it somewhat clear that the initial value will by default NOT be provided in the returning generator
- The mandatory initial argument forces you to think about initial conditions.
Disadvantages:
- The most common use case (summation, product), has a "natural" first element (0, and 1, respectively) when you'd now be required to write out. (but we could just leave accumulate for sum).
I still prefer a built-in language comprehension syntax for this like: (y := f(y, x) for x in x_vals from y=0), but for a huge discussion on that see the other thread.
------- More Examples (using "accumulate" as the name for now) -------
# Kalman filters
def kalman_filter_update(state, measurement):
...
return state
online_trajectory_estimate = accumulate(measurement_generator, func=kalman_filter_update, initial = initial_state)
---
# Bayesian stats
def update_model(prior, evidence):
...
return posterior
model_history = accumulate(evidence_generator, func=update_model, initial = prior_distribution)
---
# Recurrent Neural networks:
def recurrent_network_layer_step(last_hidden, current_input):
new_hidden = ....
return new_hidden
hidden_state_generator = accumulate(input_sequence, func=recurrent_network_layer_step, initial = initial_hidden_state)