Product iterator with NamedTuples

Hi all,

I was wondering whether it is possible to create a product iterator consisting of NamedTuples. The product iterator generates tuples like so:

Base.product(1:3,4:5) |> collect
3×2 Matrix{Tuple{Int64, Int64}}:
 (1, 4)  (1, 5)
 (2, 4)  (2, 5)
 (3, 4)  (3, 5)

What I would like to do is associate each element with a keyword, such as:

 (a=1, b=4)  (a=1, b=5)
 (a=2, b=4)  (a=2, b=5)
 (a=3, b=4)  (a=3, b=5)

Is this possible?

Is this generic enough?

julia> abTuple(t) = (; zip((:a, :b), t)...)
abTuple (generic function with 1 method)

julia> Base.product(1:3,4:5) .|> abTuple
3×2 Matrix{NamedTuple{(:a, :b), Tuple{Int64, Int64}}}:
 (a = 1, b = 4)  (a = 1, b = 5)
 (a = 2, b = 4)  (a = 2, b = 5)
 (a = 3, b = 4)  (a = 3, b = 5)
1 Like

Yes. Thank you. This will work for my use case. I suppose one way to make it more generic is to create a custom iterator.

Another way might be to broadcast the constructor, as

julia> it = Base.product(1:3, 4:5);

julia> @NamedTuple{a::Int, b::Int}.(it)
3×2 Matrix{NamedTuple{(:a, :b), Tuple{Int64, Int64}}}:
 (a = 1, b = 4)  (a = 1, b = 5)
 (a = 2, b = 4)  (a = 2, b = 5)
 (a = 3, b = 4)  (a = 3, b = 5)

A lazy version might be

julia> g = (@NamedTuple{a::Int, b::Int}(x) for x in it)

julia> collect(g)
3×2 Matrix{NamedTuple{(:a, :b), Tuple{Int64, Int64}}}:
 (a = 1, b = 4)  (a = 1, b = 5)
 (a = 2, b = 4)  (a = 2, b = 5)
 (a = 3, b = 4)  (a = 3, b = 5)
4 Likes

For something more than an iterator, see (my) RectiGrids.jl package:

julia> using RectiGrids

# create a grid with named dimensions:
julia> G = grid(a=1:3, b=4:5)
2-dimensional KeyedArray(NamedDimsArray(...)) with keys:
↓   a ∈ 3-element UnitRange{Int64}
→   b ∈ 2-element UnitRange{Int64}
And data, 3×2 RectiGrids.RectiGridArr{(:a, :b), NamedTuple{(:a, :b), Tuple{Int64, Int64}}, 2, Tuple{Nothing, Nothing}, Tuple{UnitRange{Int64}, UnitRange{Int64}}}:
      (4)                (5)
 (1)     (a = 1, b = 4)     (a = 1, b = 5)
 (2)     (a = 2, b = 4)     (a = 2, b = 5)
 (3)     (a = 3, b = 4)     (a = 3, b = 5)

# it behaves as an array, with NamedTuple elements:
julia> G[1, 2]
(a = 1, b = 5)

# even more, a grid is a KeyedArray and can use any of KeyedArray selectors
# this still doesn't materialize the whole grid:
julia> G(a = >(1))  # only keep values with a > 1
2-dimensional KeyedArray(NamedDimsArray(...)) with keys:
↓   a ∈ 2-element UnitRange{Int64}
→   b ∈ 2-element UnitRange{Int64}
And data, 2×2 view(::RectiGrids.RectiGridArr{(:a, :b), NamedTuple{(:a, :b), Tuple{Int64, Int64}}, 2, Tuple{Nothing, Nothing}, Tuple{UnitRange{Int64}, UnitRange{Int64}}}, 2:3, :) with eltype NamedTuple{(:a, :b), Tuple{Int64, Int64}}:
      (4)                (5)
 (2)     (a = 2, b = 4)     (a = 2, b = 5)
 (3)     (a = 3, b = 4)     (a = 3, b = 5)

# applying a function over the grid keeps the keyed indices (a and b):
julia> map(x -> x.a^2 + x.b, G)
2-dimensional KeyedArray(NamedDimsArray(...)) with keys:
↓   a ∈ 3-element UnitRange{Int64}
→   b ∈ 2-element UnitRange{Int64}
And data, 3×2 Matrix{Int64}:
      (4)  (5)
 (1)    5    6
 (2)    8    9
 (3)   13   14
1 Like

Awesome. Thank you for all of the ideas!