7272WindowedAssociativeOp {T,Op} () where {T,Op} = WindowedAssociativeOp {T,Op,Op} ()
7373
7474"""
75- update_state!(
76- state::WindowedAssociativeOp,
77- value,
78- num_dropped_from_window::Integer
79- ) -> state
75+ push!(state::WindowedAssociativeOp{T}, value) -> state
8076
81- Add the specified value to the state, drop some number of elements from the start of the
82- window, and return `state` (which will have been mutated).
77+ Push `value` onto the end of `state`.
8378
8479# Arguments
8580- `state::WindowedAssociativeOp`: The state to update (will be mutated).
8681- `value`: The value to add to the end of the window - must be convertible to a `T`.
87- - `num_dropped_from_window::Integer`: The number of elements to remove from the front of
88- the window.
89-
90- # Returns
91- - The instance `state` that was passed in.
9282"""
93- Base. @propagate_inbounds function update_state! (
94- state:: WindowedAssociativeOp{T,Op,Op!} , value, num_dropped_from_window:: Integer
95- ) where {T,Op,Op!}
96- # Our index into previous_cumsum is advanced by the number of values we drop from the
97- # window.
98- # TODO This could be dangerous, since if we get an error later the state will be
99- # invalid.
100- state. ri_previous_cumsum += num_dropped_from_window
101-
102- # If this has taken us out-of-range of the _previous_cumsum values, we must recompute
103- # them.
104- elements_remaining = length (state. previous_cumsum) - state. ri_previous_cumsum
105-
106- if elements_remaining < 0
107- # We have overshot the end of previous_cumsum.
108-
109- # We may also need to discard elements from values. This could happen if
110- # num_dropped_from_window > 1.
111- num_values_to_remove = - elements_remaining
112- @boundscheck if num_values_to_remove > length (state. values)
113- throw (
114- ArgumentError (
115- " num_dropped_from_window = $num_dropped_from_window is out of range"
116- ),
117- )
118- end
119-
120- # We now generate the partial sum, and set our index back to zero. values is also
121- # emptied, since its information is now reflected in previous_cumsum.
122- # NOTE: We need to take care here in the case of non-commutation. In accumulate, we
123- # will be getting:
124- #
125- # (x0, op(x0, x1), op(op(x0, x1), x2), ...)
126- #
127- # but we actually want:
128- #
129- # (x0, op(x1, x0), op(x2, op(x1, x0)), ...)
130-
131- # Conceptually the following code is equivalent to the following, however avoids
132- # unnecessary allocations:
133- # trimmed_reversed_values = state.values[end:-1:1 + num_values_to_remove]
134- # state.previous_cumsum = accumulate(
135- # (x, y) -> Op(y, x),
136- # trimmed_reversed_values
137- # )
138-
139- empty! (state. previous_cumsum)
140- upper = length (state. values)
141- lower = 1 + num_values_to_remove
142- @inbounds if (upper - lower) >= 0 # i.e. we have a non-zero range
143- i = upper
144- accumulation = state. values[i]
145- while true
146- push! (state. previous_cumsum, accumulation)
147- i -= 1
148- i >= lower || break
149- # If we were to use a mutating operation here, then we'd have to introduce
150- # a copy above. So there is no drawback to using the non-mutating Op.
151- accumulation = Op (state. values[i], accumulation)
152- end
153- end
154-
155- state. ri_previous_cumsum = 0
156- empty! (state. values)
157- # state.sum is now garbage, but we are not going to use it before we recompute it.
158- end
159-
83+ function Base. push! (state:: WindowedAssociativeOp{T,Op,Op!} , value) where {T,Op,Op!}
16084 # We want to make sure that we never actually mutate any value object that we pass to
161- # `update_state !`.
85+ # `push !`.
16286 # Note that when initialising `state.sum`, we should take a copy if our mutating `Op!`
16387 # is different to `Op`. This is because we know that `Op` is necessarily non-mutating.
164- _copy (x) = (Op == Op!) ? x : deepcopy (x)
88+ _copy (x) = (Op === Op!) ? x : deepcopy (x)
16589
16690 # Include the new value in sum and values.
16791 state. sum = isempty (state. values) ? _copy (value) : Op! (state. sum, value)
@@ -170,29 +94,24 @@ Base.@propagate_inbounds function update_state!(
17094end
17195
17296"""
173- drop_values!(
174- state::WindowedAssociativeOp{T,Op,Op!}, n::Integer
175- ) where {T,Op,Op!}
97+ popfirst!(state::WindowedAssociativeOp, n::Integer=1) -> state
17698
177- Drop `n` values from the leading edge of the window .
99+ Drop `n` values from the start of `state` .
178100"""
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
101+ Base. @propagate_inbounds function Base. popfirst! (
102+ state:: WindowedAssociativeOp{T,Op} , n:: Integer = 1
103+ ) where {T,Op}
104+ n == 0 && return state
105+ n >= 0 || throw (ArgumentError (" n must be non-negative, got n=$n " ))
186106
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
107+ # Our index into previous_cumsum is advanced by the number of values we drop.
108+ # Note that we do not assign to `ri_previous_cumsum` immediately, since this would make
109+ # `state` invalid were we to throw an exception before returning.
110+ new_ri_previous_cumsum = state. ri_previous_cumsum + n
192111
193112 # If this has taken us out-of-range of the _previous_cumsum values, we must recompute
194113 # them.
195- elements_remaining = length (state. previous_cumsum) - state . ri_previous_cumsum
114+ elements_remaining = length (state. previous_cumsum) - new_ri_previous_cumsum
196115
197116 if elements_remaining < 0
198117 # We have overshot the end of previous_cumsum.
@@ -225,37 +144,27 @@ function drop_values!(state::WindowedAssociativeOp{T,Op,Op!}, n::Integer) where
225144 empty! (state. previous_cumsum)
226145 upper = length (state. values)
227146 lower = 1 + num_values_to_remove
228- if (upper - lower) >= 0 # i.e. we have a non-zero range
147+ @inbounds if (upper - lower) >= 0 # i.e. we have a non-zero range
229148 i = upper
230- accumulation = @inbounds ( state. values[i])
149+ accumulation = state. values[i]
231150 while true
232151 push! (state. previous_cumsum, accumulation)
233152 i -= 1
234153 i >= lower || break
235154 # If we were to use a mutating operation here, then we'd have to introduce
236155 # a copy above. So there is no drawback to using the non-mutating Op.
237- accumulation = Op (@inbounds ( state. values[i]) , accumulation)
156+ accumulation = Op (state. values[i], accumulation)
238157 end
239158 end
240159
241160 state. ri_previous_cumsum = 0
242161 empty! (state. values)
243- # FIXME state.sum is now garbage.
244- # This matters...
162+ # WARNING! `state.sum` is now garbage.
163+ # This means that we have to be careful to not use it if `state.values` is empty.
164+ else
165+ state. ri_previous_cumsum = new_ri_previous_cumsum
245166 end
246167
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-
259168 return state
260169end
261170
@@ -280,7 +189,7 @@ function window_value(state::WindowedAssociativeOp{T,Op})::T where {T,Op}
280189 # We aren't using the A buffer, either because values is full or the A buffer has
281190 # not yet been populated.
282191 state. sum
283- elseif length (state. values) == 0
192+ elseif isempty (state. values)
284193 # We are using the A buffer, but not the B buffer.
285194 # `state.sum` has an uninitialised value.
286195 @inbounds (state. previous_cumsum[index])
0 commit comments