JeffreySarnoff / NamedTupleTools.jl

some utilities for working with NamedTuples
MIT License
81 stars 11 forks source link

NamedTupleTools.jl

Some NamedTuple utilities

Copyright © 2015-2023 by Jeffrey Sarnoff. This work is released under The MIT License.


Build Status   codecov   Docs   

Package Downloads


Overview

NamedTuples are built from fieldnames, given as Symbols and field values, as they may be given. These utilities make some uses of NamedTuples more straightforward. This package benefits greatly from others (see Credits).

Operations

Construction

Reconstruction

Selection

Deletion

Merging

Recursive Merging

Splitting


Functions

Construction from names and values

julia> using NamedTupleTools
julia> namesofvalues  = (:instrument, :madeby)
julia> matchingvalues = ("violin", "Stradivarius")

julia> nt = namedtuple(namesofvalues, matchingvalues)
(instrument = "violin", madeby = "Stradivarius")

Selecting Aspects of Elements

julia> using NamedTupleTools

julia> nt = NamedTuple{(:a, :b)}(1.0, "two")
(a = 1.0, b = "two")

julia> typeof(nt) == NamedTuple{(:a, :b),Tuple{Float64,String}}
true

julia> propertynames(nt) == (:a, :b)
true

julia> fieldnames(nt) == (:a, :b)             # synonym for the moment
true

julia> fieldtypes(nt) == (Float64, String)
true

julia> valtype(nt) == Tuple{Float64, String}
true

julia> fieldvalues(nt) == (1.0, "two")
true

Use NamedTuple prototypes

using NamedTupleTools

julia> namedtuple(:a, :b, :c)(1, 2.0, "three")
(a = 1, b = 2.0, c = "three")

#=
    namedtuple(  name1, name2, ..  )
    namedtuple( (name1, name2, ..) )
       where the `names` are all `Symbols` or all `Strings`

Generate a NamedTuple prototype by specifying or obtaining the fieldnames.
The prototype is applied to fieldvalues, giving a completed NamedTuple.
=#
julia> nt = (a = 1, b = "two")
(a = 1, b = "two")

julia> nt_prototype = prototype(nt)
NamedTuple{(:a, :b),T} where T<:Tuple

julia> nt_prototype = namedtuple(:a, :b)
NamedTuple{(:a, :b),T} where T<:Tuple

julia> nt = nt_prototype(1, 2)
(a = 1, b = 2)

julia> nt = nt_prototype("A", 3)
(a = "A", b = 3)

julia> isprototype(nt_prototype)
true

julia> isprototype(nt)
false

Select

using NamedTupleTools

julia> nt = (a = 1, b = 2, y = 25, z = 26)
(a = 1, b = 2, y = 25, z = 26)

julia> ay = select(nt, (:a, :y))
(a = 1, y = 25)

Delete

using NamedTupleTools

julia> ntproto = namedtuple( :a, :b, :c );
NamedTuple{(:a, :b, :c),T} where T<:Tuple

julia> delete(ntproto, :b) === namedtuple(:a, :c)
true

julia> fieldnames(delete(ntproto, :b))
NamedTuple{(:a, :c),T} where T<:Tuple

julia> fieldnames(delete(ntproto, (:a, :c)), fieldnames(delete(ntproto, :a, :c)
(:b,), (:b,)

julia> nt = ntproto(1, 2, 3)
(a = 1, b = 2, c = 3)

julia> delete(nt, :a)
(b = 2, c = 3)

julia> delete(nt, :a, :c)
(b = 2,)

Merge

# merge from 2..7 NamedTuples

julia> ntproto1 = namedtuple(:a, :b);
julia> ntproto2 = namedtuple(:b, :c);

julia> merge(ntproto1, ntproto2)
NamedTuple{(:a, :b, :c),T} where T<:Tuple
julia> nt1 = (a = 3, b = 5);
julia> nt2 = (c = 8,);

julia> merge(nt1, nt2)
(a = 3, b = 5, c = 8)

julia> nt1 = (a = 3, b = 5);
julia> nt2 = (b = 6, c = 8);

julia> merge(nt1, nt2)
(a = 3, b = 6, c = 8)

recursive_merge

#=
Recursively merge namedtuples. Where more than one of the namedtuple args share the same fieldname (same key),
    the leftmost argument's key's value will be propogated. Where each namedtuple has distinct fieldnames (keys),
    all of named fields will be gathered with their respective values. The named fields will appear in the same
    order they are encountered (leftmost arg, second leftmost arg, .., second rightmost arg, rightmost arg).

If there are no nested namedtuples, `merge(nt1, nts..., recursive=true)` is the same as `merge(nt1, nts...)`.
=#

a = (food = (fruits = (orange = "mango", white = "pear"),
             liquids = (water = "still", wine = "burgandy")))

b = (food = (fruits = (yellow = "banana", orange = "papaya"),
             liquids = (water = "sparkling", wine = "champagne"), 
             bread = "multigrain"))

merge(b,a)  == (fruits  = (orange = "mango", white = "pear"), 
                liquids = (water = "still", wine = "burgandy"),
                bread   = "multigrain")

merge_recursive(b,a) == 
               (fruits  = (yellow = "banana", orange = "mango", white = "pear"), 
                liquids = (water = "still", wine = "burgandy"),
                bread   = "multigrain")

merge(a,b)  == (fruits  = (yellow = "banana", orange = "papaya"),
                liquids = (water = "sparkling", wine = "champagne"),
                bread   = "multigrain")

merge_recursive(a,b) == 
               (fruits  = (orange = "papaya", white = "pear", yellow = "banana"),
                liquids = (water = "sparkling", wine = "champagne"),
                bread   = "multigrain")

Split

julia> using NamedTupleTools

julia> nt = (a = 1, b = 2, c = 3, d = 4);

julia> split(nt, :a)
((a = 1,), (b = 2, c = 3, d = 4))

julia> split(nt, (:a, :b))
((a = 1, b = 2), (c = 3, d = 4))

julia> merge(split(nt, (:a, :b))...) == nt
true

Struct construction, conversion

using NamedTupleTools

julia> struct MyStruct
           tally::Int
           team::String
       end

julia> mystruct = MyStruct(5, "hometeam")
MyStruct(5, "hometeam")

julia> mynamedtuple = ntfromstruct(mystruct)
(tally = 5, team = "hometeam")

julia> ntstruct = structfromnt(MyStruct, mynamedtuple)
MyStruct(5, "hometeam")

julia> mystruct == ntstruct
true

AbstractDict construction, reconstruction

julia> nt = (a = 1, b = 2)
(a = 1, b = 2)

julia> convert(Dict, nt)
Dict{Symbol,Int64} with 2 entries:
  :a => 1
  :b => 2

julia> adict = Dict(:a => 1, :b => "two")
Dict{Symbol,Any} with 2 entries:
  :a => 1
  :b => "two"

julia> nt = namedtuple(adict)
(a = 1, b = "two")

julia> convert(Dict, nt)
Dict{Symbol,Union{Int64, String}} with 2 entries:
  :a => 1
  :b => "two"

julia> nt = namedtuple(adict)
(a = 1, b = 2//11, c = "three")

julia> convert(Dict, nt)
Dict{Symbol,Union{Rational{Int64}, Int64, String}} with 3 entries:
  :a => 1
  :b => 2//11
  :c => "three"

julia> using OrderedCollections: OrderedDict, LittleDict

julia> ldict = OrderedDict(:a => 1, :b => "two")
OrderedDict{Symbol,Any} with 2 entries:
  :a => 1
  :b => "two"

julia> nt = namedtuple(ldict)
(a = 1, b = "two")

julia> convert(LittleDict, nt)
LittleDict{Symbol,Union{Int64, String},Array{Symbol,1},Array{Union{Int64, String},1}} with 2 entries:
  :a => 1
  :b => "two"

Vector of Pairs

julia> vec = [:a => 1, :b => 2]
2-element Array{Pair{Symbol,Int64},1}:
 :a => 1
 :b => 2

julia> nt = namedtuple(vec)
(a = 1, b = 2)

convert to Vector Of Pairs

julia> nt = (a=1, b=2);
julia> convert(Vector{Pair}, nt)
2-element Array{Pair{Symbol,Int64},1}:
 :a => 1
 :b => 2

nt = (a = 1, b = "two", c = 3.0);
vec = convert(Vector{Pair}, nt)
3-element Array{Pair{Symbol,B} where B,1}:
 :a => 1
 :b => "two"
 :c => 3.0

Variables mixed with standard syntax

julia> a, b, c, d, f = 1, 1.0, 1//1, "one", (g=1,)
(1, 1.0, 1//1, "one", (g = 1,))

julia> nt = @namedtuple(a, b, c, d, e = a + b, f...)
(a = 1, b = 1.0, c = 1//1, d = "one", e = 2.0, g = 1)

Credits