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.
82+ """
83+ function Base. push! (state:: WindowedAssociativeOp{T,Op,Op!} , value) where {T,Op,Op!}
84+ # We want to make sure that we never actually mutate any value object that we pass to
85+ # `push!`.
86+ # Note that when initialising `state.sum`, we should take a copy if our mutating `Op!`
87+ # is different to `Op`. This is because we know that `Op` is necessarily non-mutating.
88+ _copy (x) = (Op === Op!) ? x : deepcopy (x)
89+
90+ # Include the new value in sum and values.
91+ state. sum = isempty (state. values) ? _copy (value) : Op! (state. sum, value)
92+ push! (state. values, value)
93+ return state
94+ end
8995
90- # Returns
91- - The instance `state` that was passed in.
9296"""
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- state. ri_previous_cumsum += num_dropped_from_window
97+ popfirst!(state::WindowedAssociativeOp, n::Integer=1) -> state
98+
99+ Drop `n` values from the start of `state`.
100+ """
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 " ))
106+
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
99111
100112 # If this has taken us out-of-range of the _previous_cumsum values, we must recompute
101113 # them.
102- elements_remaining = length (state. previous_cumsum) - state . ri_previous_cumsum
114+ elements_remaining = length (state. previous_cumsum) - new_ri_previous_cumsum
103115
104116 if elements_remaining < 0
105117 # We have overshot the end of previous_cumsum.
106118
107- # We may also need to discard elements from values. This could happen if
108- # num_dropped_from_window > 1.
119+ # We may also need to discard elements from values. This could happen if n > 1.
109120 num_values_to_remove = - elements_remaining
110121 @boundscheck if num_values_to_remove > length (state. values)
111- throw (
112- ArgumentError (
113- " num_dropped_from_window = $num_dropped_from_window is out of range"
114- ),
115- )
122+ throw (ArgumentError (" n = $n is out of range" ))
116123 end
117124
118125 # We now generate the partial sum, and set our index back to zero. values is also
@@ -152,18 +159,12 @@ Base.@propagate_inbounds function update_state!(
152159
153160 state. ri_previous_cumsum = 0
154161 empty! (state. values)
155- # state.sum is now garbage, but we are not going to use it before we recompute it.
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
156166 end
157167
158- # We want to make sure that we never actually mutate any value object that we pass to
159- # `update_state!`.
160- # Note that when initialising `state.sum`, we should take a copy if our mutating `Op!`
161- # is different to `Op`. This is because we know that `Op` is necessarily non-mutating.
162- _copy (x) = (Op == Op!) ? x : deepcopy (x)
163-
164- # Include the new value in sum and values.
165- state. sum = isempty (state. values) ? _copy (value) : Op! (state. sum, value)
166- push! (state. values, value)
167168 return state
168169end
169170
@@ -188,6 +189,10 @@ function window_value(state::WindowedAssociativeOp{T,Op})::T where {T,Op}
188189 # We aren't using the A buffer, either because values is full or the A buffer has
189190 # not yet been populated.
190191 state. sum
192+ elseif isempty (state. values)
193+ # We are using the A buffer, but not the B buffer.
194+ # `state.sum` has an uninitialised value.
195+ @inbounds (state. previous_cumsum[index])
191196 else
192197 Op (@inbounds (state. previous_cumsum[index]), state. sum)
193198 end
0 commit comments