Construct NamedTuple dynamically

In Julia 1.0, I can constructed a named tuple with known names and values.

x = (a = 1, b = 2.0, c = "hello world")

Let’s say I have the names and values in separate arrays. How could I construct a named tuple dynamically?

mynames = ["a", "b", "c"]
myvalues = [1, 2.0, "hello world"]
3 Likes
julia> mynames = (:a, :b, :c);

julia> myvalues = [1, 2.0, "hello world"];

julia> NamedTuple{mynames}(myvalues)
(a = 1, b = 2.0, c = "hello world")
18 Likes

Answering my own question. I should have read this post first.

The solution:

julia> using NamedTupleTools

julia> nt = namedtuple(Symbol.(["a", "b", "c"])...)
NamedTuple{(:a, :b, :c),T} where T<:Tuple

julia> nt.([1, 2.0, "hello world"]...)
(a = 1, b = 2.0, c = "hello world")
1 Like

Hm, I don’t see any advantage in using an external package here.

1 Like

Please enlighten me… Is there an easier way?

What about the answer I posted?

Haha… I just need to scroll up :slight_smile:

Sorry for necroing but I see people still liking my post (Construct NamedTuple dynamically - #2 by kristoffer.carlsson). It is likely better to use:

julia> mynames = (:a, :b, :c);

julia> myvalues = [1, 2.0, "hello world"];

julia> (;zip(mynames, myvalues)...)
(a = 1, b = 2.0, c = "hello world")
18 Likes

Can you elaborate on why it is better to use (;zip(mynames, myvalues)...) rather than NamedTuple{mynames}(myvalues)?
The later appear more readible to me and some basic benchmarks showed “better” performance in terms of allocations and execution time for a small named tuple.

1 Like

Hm, well, if you have them as two arrays, one with symbols and one with values then perhaps the first way is better. But the one with the splatting is more general and you can use it without having to “unzip” the arrays in case you have

julia> v = [(:a, 1), (:b, 2)]
2-element Array{Tuple{Symbol,Int64},1}:
 (:a, 1)
 (:b, 2)

julia> (; v...)
(a = 1, b = 2)

I don’t think benchmarking this is too useful, it is very unlikely that converting to NamedTuples will take up and significant fraction of runtime of your program.

11 Likes

Thanks for the explanation!
I agree with you, I was just wondering if there is a “technical” reason to use (; v...) over the other version or if it is more about convenience or personal preferences.

The current documentation for NamedTuple says:

In a similar fashion as to how one can define keyword arguments programmatically,
a named tuple can be created by giving a pair name::Symbol => value or splatting
an iterator yielding such pairs after a semicolon inside a tuple literal:

julia> (; :a => 1)
(a = 1,)

julia> keys = (:a, :b, :c); values = (1, 2, 3);

julia> (; zip(keys, values)...)
(a = 1, b = 2, c = 3)

This implies that zip(keys, values)... evaluates to Pairs of name::Symbol => value. Instead it evaluates to an array of tuples as you show above. When evaluated inside (; ) that array is used to construct a named tuple. Granted, the array of tuples consists of (::Symbol, value) elements. Nevertheless for someone absorbing the language, the wording was confusing. I prefer your example for documentation!

1 Like

The documentation is accurate: zip doesn’t allocate an array, it returns an iterator (without allocation). Of course an array is iterable so you can use the (; A...) syntax with an array, it’s just not what happens here with zip.

When I have an iterator of name-value pairs, my preference is to use the NamedTuple(iterator) constructor:

keys = (:a, :b, :c); values = (1, 2, 3)
it = zip(keys, values)

julia> NamedTuple(it)
(a = 1, b = 2, c = 3)

But the (; ...) syntax is more flexible, it’s super useful in more complex cases:

keys = (:a, :b, :c); values = (1, 2, 3)
it = zip(keys, values)

A = [:c => 300, :d => 400]

mykey = Symbol("e", rand(3:5))
myval = 5000

other = (e="X", f="Y", g="Z")

julia> (; it..., b=20, A..., mykey => myval, other.f)
(a = 1, b = 20, c = 300, d = 400, e4 = 5000, f = "Y")

This takes the key-values from it, then overrides the b value, then takes the key-values from A (overriding c and adding d), then adds an element with a dynamically generated name, and finally adds an element from other, reusing the name.

3 Likes