Consider the following Julia “compound” iterator: it merges two iterators, a
and b
, each of which are assumed to be sorted according to order
, to a single ordered sequence:
struct MergeSorted{T,A,B,O}
a::A
b::B
order::O
MergeSorted(a::A, b::B, order::O=Base.Order.Forward) where {A,B,O} =
new{promote_type(eltype(A),eltype(B)),A,B,O}(a, b, order)
end
Base.eltype(::Type{MergeSorted{T,A,B,O}}) where {T,A,B,O} = T
function Base.iterate(self::MergeSorted{T},
state=(iterate(self.a), iterate(self.b))) where T
a_result, b_result = state
if b_result === nothing
a_result === nothing && return nothing
a_curr, a_state = a_result
return T(a_curr), (iterate(self.a, a_state), b_result)
end
b_curr, b_state = b_result
if a_result !== nothing
a_curr, a_state = a_result
Base.Order.lt(self.order, a_curr, b_curr) &&
return T(a_curr), (iterate(self.a, a_state), b_result)
end
return T(b_curr), (a_result, iterate(self.b, b_state))
end
x = MergeSorted([1,4,5,9,32,44], [0,7,9,24,134])
sum(x); print(@allocated sum(x))
This code works, but creates a temporary in each iteration step. Running iterate(::MergeSorted)
through @code_warntype
reveals that the return type is Union{Nothing, Tuple{Int, Any}}
, which means that Julia gives up on specializing on the return type.
I first posted this question on stackoverflow, where user Bogumił Kamiński suggested a rather ugly workaround, which introduces a special state type to “force” julia to specialize. We were wondering whether there is a better possiblity, e.g., by forcing Julia to specialize on more complicated types?
Or maybe there is a smart way to rewrite this such that julia can figure this out on its own? I got encouraged by the standard library’s Base.Iterators
package: it has a flatten()
routine which does pretty much the same thing as MergeSorted()
except that it does not intersperse the elements, yet it does not create temporaries. Does anyone with more iterator-foo transfer this to this case?