@@ -95,6 +95,8 @@ Base.@propagate_inbounds function update_state!(
9595) where {T,Op,Op!}
9696 # Our index into previous_cumsum is advanced by the number of values we drop from the
9797 # window.
98+ # TODO This could be dangerous, since if we get an error later the state will be
99+ # invalid.
98100 state. ri_previous_cumsum += num_dropped_from_window
99101
100102 # If this has taken us out-of-range of the _previous_cumsum values, we must recompute
@@ -167,6 +169,96 @@ Base.@propagate_inbounds function update_state!(
167169 return state
168170end
169171
172+ """
173+ drop_values!(
174+ state::WindowedAssociativeOp{T,Op,Op!}, n::Integer
175+ ) where {T,Op,Op!}
176+
177+ Drop `n` values from the leading edge of the window.
178+ """
179+ function drop_values! (state:: WindowedAssociativeOp{T,Op,Op!} , n:: Integer ) where {T,Op,Op!}
180+ n > 0 || throw (ArgumentError (" n must be positive, got n=$n " ))
181+
182+ # FIXME At best this will be hacky
183+ # FIXME At best this will be hacky
184+ # FIXME At best this will be hacky
185+ # FIXME At best this will be hacky
186+
187+ # Our index into previous_cumsum is advanced by the number of values we drop from the
188+ # window.
189+ # TODO This could be dangerous, since if we get an error later the state will be
190+ # invalid.
191+ state. ri_previous_cumsum += n
192+
193+ # If this has taken us out-of-range of the _previous_cumsum values, we must recompute
194+ # them.
195+ elements_remaining = length (state. previous_cumsum) - state. ri_previous_cumsum
196+
197+ if elements_remaining < 0
198+ # We have overshot the end of previous_cumsum.
199+
200+ # We may also need to discard elements from values. This could happen if n > 1.
201+ num_values_to_remove = - elements_remaining
202+ @boundscheck if num_values_to_remove > length (state. values)
203+ throw (ArgumentError (" n = $n is out of range" ))
204+ end
205+
206+ # We now generate the partial sum, and set our index back to zero. values is also
207+ # emptied, since its information is now reflected in previous_cumsum.
208+ # NOTE: We need to take care here in the case of non-commutation. In accumulate, we
209+ # will be getting:
210+ #
211+ # (x0, op(x0, x1), op(op(x0, x1), x2), ...)
212+ #
213+ # but we actually want:
214+ #
215+ # (x0, op(x1, x0), op(x2, op(x1, x0)), ...)
216+
217+ # Conceptually the following code is equivalent to the following, however avoids
218+ # unnecessary allocations:
219+ # trimmed_reversed_values = state.values[end:-1:1 + num_values_to_remove]
220+ # state.previous_cumsum = accumulate(
221+ # (x, y) -> Op(y, x),
222+ # trimmed_reversed_values
223+ # )
224+
225+ empty! (state. previous_cumsum)
226+ upper = length (state. values)
227+ lower = 1 + num_values_to_remove
228+ if (upper - lower) >= 0 # i.e. we have a non-zero range
229+ i = upper
230+ accumulation = @inbounds (state. values[i])
231+ while true
232+ push! (state. previous_cumsum, accumulation)
233+ i -= 1
234+ i >= lower || break
235+ # If we were to use a mutating operation here, then we'd have to introduce
236+ # a copy above. So there is no drawback to using the non-mutating Op.
237+ accumulation = Op (@inbounds (state. values[i]), accumulation)
238+ end
239+ end
240+
241+ state. ri_previous_cumsum = 0
242+ empty! (state. values)
243+ # FIXME state.sum is now garbage.
244+ # This matters...
245+ end
246+
247+
248+ # # We want to make sure that we never actually mutate any value object that we pass to
249+ # # `update_state!`.
250+ # # Note that when initialising `state.sum`, we should take a copy if our mutating `Op!`
251+ # # is different to `Op`. This is because we know that `Op` is necessarily non-mutating.
252+ # _copy(x) = (Op == Op!) ? x : deepcopy(x)
253+
254+ # # Include the new value in sum and values.
255+ # state.sum = isempty(state.values) ? _copy(value) : Op!(state.sum, value)
256+ # state.sum =
257+ # push!(state.values, value)
258+
259+ return state
260+ end
261+
170262"""
171263 window_value(state::WindowedAssociativeOp{T})::T where T
172264
@@ -188,6 +280,10 @@ function window_value(state::WindowedAssociativeOp{T,Op})::T where {T,Op}
188280 # We aren't using the A buffer, either because values is full or the A buffer has
189281 # not yet been populated.
190282 state. sum
283+ elseif length (state. values) == 0
284+ # We are using the A buffer, but not the B buffer.
285+ # `state.sum` has an uninitialised value.
286+ @inbounds (state. previous_cumsum[index])
191287 else
192288 Op (@inbounds (state. previous_cumsum[index]), state. sum)
193289 end
0 commit comments