Reputation: 979
I have a Tuple
of elements, some of which are NamedTuple
s. I would like to flatten the NamedTuple
s like so:
julia> nt = (a="a", b="b")
(a = "a", b = "b")
julia> t = (1, 2, 3, nt)
(1, 2, 3, (a = "a", b = "b"))
julia> res = tuple(1, 2, 3, nt...)
(1, 2, 3, "a", "b")
How to do this programmatically? I tried the following:
julia> exprs = [x isa NamedTuple ? Meta.parse("$x...") : x for x in t]
4-element Array{Any,1}:
1
2
3
:((a = "a", b = "b")...)
julia> res = tuple(eval(ex) for ex in exprs)
(Base.Generator{Array{Any,1},typeof(eval)}(Base.MainInclude.eval, Any[1, 2, 3, :((a = "a", b = "b")...)]),)
But it doesn't quite give what I would like:
(1, 2, 3, "a", "b")
Upvotes: 3
Views: 652
Reputation: 69839
The simplest way to do it would be to write:
julia> Tuple(Iterators.flatten((1,2,3, (a="a",b="b"))))
(1, 2, 3, "a", "b")
this has a downside that it is type unstable and that it will flatten all iterables (not only NamedTuple
s).
If you want only NamedTuple
s flattened then something like this could be used:
julia> Tuple(reduce((a,b) -> (b isa NamedTuple ? append! : push!)(a, b), (1,2,3, (a="a",b="b")), init=[]))
(1, 2, 3, "a", "b")
(still it will be type unstable)
If you want something type stable you can use recursion e.g. like this:
flatten() = ()
flatten(a::NamedTuple) = Tuple(a)
flatten(a) = (a,)
flatten(a::NamedTuple, b...) = tuple(a..., flatten(b...)...)
flatten(a, b...) = tuple(a, flatten(b...)...)
flatten_tuple(x::Tuple) = flatten(x...)
and now you have:
julia> flatten_tuple((1,2,3, (a="a",b="b")))
(1, 2, 3, "a", "b")
julia> @code_warntype flatten_tuple((1,2,3, (a="a",b="b")))
Variables
#self#::Core.Compiler.Const(flatten_tuple, false)
x::Tuple{Int64,Int64,Int64,NamedTuple{(:a, :b),Tuple{String,String}}}
Body::Tuple{Int64,Int64,Int64,String,String}
1 ─ %1 = Core._apply_iterate(Base.iterate, Main.flatten, x)::Tuple{Int64,Int64,Int64,String,String}
└── return %1
Upvotes: 7