My problem occured while trying to write an efficient iterator for permutations groups: they can be efficient generated by running throught the products of some lists of permutations (some transversals).
I give here a simple MWE which mimics that situation, using concatenation of strings instead of multiplication of permutations to mimic a non-commutative product.
So imagine that a group is:
struct Group
s::Vector{String}
end
G=Group(["one","happy","man"])
and the “elements” of G
are all the words made by taking one letter from the first string, one from the second, etc…
I can make an efficient iterator for this
I=(prod(x) for x in Iterators.product(G.s...));
and the timing is not bad.
julia> @btime collect(I)
902.024 ns (46 allocations: 1.84 KiB)
3Ă—5Ă—3 Array{String,3}:
[:, :, 1] =
"ohm" "oam" "opm" "opm" "oym"
"nhm" "nam" "npm" "npm" "nym"
"ehm" "eam" "epm" "epm" "eym"
[:, :, 2] =
"oha" "oaa" "opa" "opa" "oya"
"nha" "naa" "npa" "npa" "nya"
"eha" "eaa" "epa" "epa" "eya"
[:, :, 3] =
"ohn" "oan" "opn" "opn" "oyn"
"nhn" "nan" "npn" "npn" "nyn"
"ehn" "ean" "epn" "epn" "eyn"
But now I would like G
itself to be able to iterate. I do not want to write for s in I
or for s in iterator(G)
but for s in G
. So I want G
itself to iterate through I
.
The only way I found is:
Base.length(G::Group)=prod(length.(G.s))
Base.eltype(::Type{Group})=String
function Base.iterate(G::Group)
I=(prod(x) for x in Iterators.product(G.s...))
p=iterate(I)
if isnothing(p) return end
str,state=p
str,(I,state)
end
function Base.iterate(G::Group,(I,state))
p=iterate(I,state)
if isnothing(p) return end
str,state=p
str,(I,state)
end
This iterates OK but the performance is terrible:
julia> @btime collect(G);
5.672 ÎĽs (241 allocations: 13.92 KiB)
So my question is: is there a better way to do that, that is to delegate efficiently iteration from G
to a dynamically computed I
?