This is not particularly efficient in terms of runtime since we still first generate all possible combinations, and then only keep the valid ones, but for memory consumption this should be fine:
using StaticArrays
function generate(as)
N = length(as)
T = eltype(eltype(as))
s = Set{SVector{N, T}}()
_generate!(s, Iterators.product(as...))
# Function boundary for type stability
# (We will have to recompile for every value of N though, but this is relatively fast)
return s
end
function _generate!(s::Set{SVector{N, T}}, c) where {N, T}
for x in c
allunique(x) || continue
sx = sort(SVector{N, T}(x))
push!(s, sx)
end
return s
end
julia> as = [a, b, c, d, e, f, g, h, i, j, k, l] # (or tuple (of (S)Vectors), or ...)
12-element Vector{Vector{Int64}}:
[3, 4, 6]
[19, 20, 39, 40]
[12, 17, 23]
[13, 15, 16, 17, 21, 24, 25]
[12, 13, 15, 16, 17, 25, 26]
[29, 30, 38, 41]
[13, 14, 15, 16, 17]
[23, 24, 26, 29, 41]
[23, 24, 25, 29, 30]
[32, 34, 35, 36, 41]
[31, 32, 33, 34, 35, 40, 41]
[31, 32, 33, 34, 36, 39, 40, 41]
julia> @time generate(as)
7.380655 seconds (2.21 M allocations: 871.420 MiB, 0.85% gc time, 7.67% compilation time)
Set{SVector{12, Int64}} with 2707644 elements:
[6, 13, 17, 19, 21, 25, 26, 32, 36, 38, 40, 41]
[4, 14, 15, 23, 24, 26, 29, 30, 33, 36, 40, 41]
[6, 15, 20, 23, 25, 26, 29, 30, 31, 33, 35, 38]
[4, 14, 16, 21, 23, 24, 25, 29, 34, 35, 36, 40]
[3, 13, 14, 17, 20, 24, 25, 26, 29, 34, 39, 40]
[4, 12, 16, 17, 20, 21, 23, 30, 32, 36, 38, 41]
[6, 12, 17, 19, 23, 24, 29, 30, 35, 38, 39, 41]
[4, 12, 13, 16, 24, 26, 29, 34, 36, 38, 39, 40]
[6, 12, 14, 16, 25, 26, 29, 30, 31, 35, 39, 41]
[3, 15, 23, 24, 25, 30, 33, 35, 36, 38, 39, 41]
[6, 12, 13, 15, 21, 23, 30, 34, 35, 38, 39, 40]
[4, 13, 14, 15, 17, 19, 23, 25, 32, 36, 38, 40]
[4, 12, 14, 16, 17, 20, 26, 30, 31, 33, 38, 41]
[3, 12, 13, 23, 25, 26, 31, 32, 38, 39, 40, 41]
[3, 13, 15, 20, 21, 23, 25, 26, 31, 33, 35, 41]
[6, 12, 13, 15, 17, 26, 30, 33, 34, 35, 39, 41]
[4, 12, 16, 17, 20, 23, 25, 26, 29, 32, 33, 34]
[3, 14, 15, 19, 21, 23, 25, 32, 33, 34, 38, 41]
[6, 13, 14, 17, 20, 21, 23, 24, 29, 31, 35, 36]
[4, 16, 17, 21, 24, 25, 32, 35, 36, 38, 40, 41]
[3, 12, 16, 17, 20, 23, 24, 29, 33, 34, 38, 41]
[3, 14, 17, 21, 23, 24, 25, 31, 34, 35, 38, 40]
[4, 13, 14, 17, 21, 25, 29, 32, 36, 38, 39, 40]
[6, 12, 14, 16, 17, 20, 25, 29, 34, 35, 38, 41]
[6, 14, 17, 19, 21, 23, 26, 29, 31, 35, 38, 41]
⋮
julia> @time s = generate(as); # Compilation only adds many small allocations
6.904371 seconds (68 allocations: 759.835 MiB, 0.95% gc time)
julia> (length(s) * sizeof(eltype(s))) / 2^20 # Minimal size of output, in MiB
247.8922119140625
julia> Base.summarysize(s) / 2^20 # Including Set overhead
388.00011444091797
So we are (in total) allocating about three times what we actually want to output.