https://github.com/JuliaLang/julia
Tip revision: 70d19e90efcf811be980a0b9eedda6439756f0e6 authored by Jeff Bezanson on 17 July 2018, 05:35:02 UTC
wip
wip
Tip revision: 70d19e9
namedtuple.jl
# This file is a part of Julia. License is MIT: https://julialang.org/license
"""
NamedTuple
`NamedTuple`s are, as their name suggests, named [`Tuple`](@ref)s. That is, they're a
tuple-like collection of values, where each entry has a unique name, represented as a
`Symbol`. Like `Tuple`s, `NamedTuple`s are immutable; neither the names nor the values
can be modified in place after construction.
Accessing the value associated with a name in a named tuple can be done using field
access syntax, e.g. `x.a`, or using [`getindex`](@ref), e.g. `x[:a]`. A tuple of the
names can be obtained using [`keys`](@ref), and a tuple of the values can be obtained
using [`values`](@ref).
!!! note
Iteration over `NamedTuple`s produces the *values* without the names. (See example
below.) To iterate over the name-value pairs, use the [`pairs`](@ref) function.
# Examples
```jldoctest
julia> x = (a=1, b=2)
(a = 1, b = 2)
julia> x.a
1
julia> x[:a]
1
julia> keys(x)
(:a, :b)
julia> values(x)
(1, 2)
julia> collect(x)
2-element Array{Int64,1}:
1
2
julia> collect(pairs(x))
2-element Array{Pair{Symbol,Int64},1}:
:a => 1
:b => 2
```
"""
Core.NamedTuple
if nameof(@__MODULE__) === :Base
"""
NamedTuple{names,T}(args::Tuple)
Construct a named tuple with the given `names` (a tuple of Symbols) and field types `T`
(a `Tuple` type) from a tuple of values.
"""
function NamedTuple{names,T}(args::Tuple) where {names, T <: Tuple}
if length(args) == length(names)
if @generated
N = length(names)
types = T.parameters
Expr(:new, :(NamedTuple{names,T}), Any[ :(convert($(types[i]), args[$i])) for i in 1:N ]...)
else
N = length(names)
NT = NamedTuple{names,T}
types = T.parameters
fields = Any[ convert(types[i], args[i]) for i = 1:N ]
ccall(:jl_new_structv, Any, (Any, Ptr{Cvoid}, UInt32), NT, fields, N)::NT
end
else
throw(ArgumentError("Wrong number of arguments to named tuple constructor."))
end
end
"""
NamedTuple{names}(nt::NamedTuple)
Construct a named tuple by selecting fields in `names` (a tuple of Symbols) from
another named tuple.
"""
function NamedTuple{names}(nt::NamedTuple) where {names}
if @generated
types = Tuple{(fieldtype(nt, n) for n in names)...}
Expr(:new, :(NamedTuple{names, $types}), Any[ :(getfield(nt, $(QuoteNode(n)))) for n in names ]...)
else
types = Tuple{(fieldtype(typeof(nt), n) for n in names)...}
NamedTuple{names, types}(Tuple(getfield(nt, n) for n in names))
end
end
NamedTuple{names, T}(itr) where {names, T <: Tuple} = NamedTuple{names, T}(T(itr))
NamedTuple{names}(itr) where {names} = NamedTuple{names}(Tuple(itr))
end # if Base
length(t::NamedTuple) = nfields(t)
iterate(t::NamedTuple, iter=1) = iter > nfields(t) ? nothing : (getfield(t, iter), iter + 1)
firstindex(t::NamedTuple) = 1
lastindex(t::NamedTuple) = nfields(t)
getindex(t::NamedTuple, i::Int) = getfield(t, i)
getindex(t::NamedTuple, i::Symbol) = getfield(t, i)
indexed_iterate(t::NamedTuple, i::Int, state=1) = (getfield(t, i), i+1)
isempty(::NamedTuple{()}) = true
isempty(::NamedTuple) = false
convert(::Type{NamedTuple{names,T}}, nt::NamedTuple{names,T}) where {names,T<:Tuple} = nt
convert(::Type{NamedTuple{names}}, nt::NamedTuple{names}) where {names} = nt
function convert(::Type{NamedTuple{names,T}}, nt::NamedTuple{names}) where {names,T<:Tuple}
NamedTuple{names,T}(T(nt))
end
if nameof(@__MODULE__) === :Base
function Tuple(nt::NamedTuple{names}) where {names}
if @generated
return Expr(:tuple, Any[:(getfield(nt, $(QuoteNode(n)))) for n in names]...)
else
return tuple(nt...)
end
end
(::Type{T})(nt::NamedTuple) where {T <: Tuple} = convert(T, Tuple(nt))
end
function show(io::IO, t::NamedTuple)
n = nfields(t)
for i = 1:n
# if field types aren't concrete, show full type
if typeof(getfield(t, i)) !== fieldtype(typeof(t), i)
show(io, typeof(t))
print(io, "(")
show(io, Tuple(t))
print(io, ")")
return
end
end
if n == 0
print(io, "NamedTuple()")
else
typeinfo = get(io, :typeinfo, Any)
print(io, "(")
for i = 1:n
print(io, fieldname(typeof(t),i), " = ")
show(IOContext(io, :typeinfo =>
t isa typeinfo <: NamedTuple ? fieldtype(typeinfo, i) : Any),
getfield(t, i))
if n == 1
print(io, ",")
elseif i < n
print(io, ", ")
end
end
print(io, ")")
end
end
eltype(::Type{NamedTuple{names,T}}) where {names,T} = eltype(T)
==(a::NamedTuple{n}, b::NamedTuple{n}) where {n} = Tuple(a) == Tuple(b)
==(a::NamedTuple, b::NamedTuple) = false
isequal(a::NamedTuple{n}, b::NamedTuple{n}) where {n} = isequal(Tuple(a), Tuple(b))
isequal(a::NamedTuple, b::NamedTuple) = false
_nt_names(::NamedTuple{names}) where {names} = names
_nt_names(::Type{T}) where {names,T<:NamedTuple{names}} = names
hash(x::NamedTuple, h::UInt) = xor(objectid(_nt_names(x)), hash(Tuple(x), h))
isless(a::NamedTuple{n}, b::NamedTuple{n}) where {n} = isless(Tuple(a), Tuple(b))
# TODO: case where one argument's names are a prefix of the other's
same_names(::NamedTuple{names}...) where {names} = true
same_names(::NamedTuple...) = false
function map(f, nt::NamedTuple{names}, nts::NamedTuple...) where names
if !same_names(nt, nts...)
throw(ArgumentError("Named tuple names do not match."))
end
# this method makes sure we don't define a map(f) method
NT = NamedTuple{names}
if @generated
N = length(names)
M = length(nts)
args = Expr[:(f($(Expr[:(getfield(nt, $j)), (:(getfield(nts[$i], $j)) for i = 1:M)...]...))) for j = 1:N]
:( NT(($(args...),)) )
else
NT(map(f, map(Tuple, (nt, nts...))...))
end
end
# a version of `in` for the older world these generated functions run in
@pure function sym_in(x::Symbol, itr::Tuple{Vararg{Symbol}})
for y in itr
y === x && return true
end
return false
end
@pure function merge_names(an::Tuple{Vararg{Symbol}}, bn::Tuple{Vararg{Symbol}})
names = Symbol[an...]
for n in bn
if !sym_in(n, an)
push!(names, n)
end
end
(names...,)
end
@pure function merge_types(names::Tuple{Vararg{Symbol}}, a::Type{<:NamedTuple}, b::Type{<:NamedTuple})
bn = _nt_names(b)
Tuple{Any[ fieldtype(sym_in(n, bn) ? b : a, n) for n in names ]...}
end
"""
merge(a::NamedTuple, b::NamedTuple)
Construct a new named tuple by merging two existing ones.
The order of fields in `a` is preserved, but values are taken from matching
fields in `b`. Fields present only in `b` are appended at the end.
```jldoctest
julia> merge((a=1, b=2, c=3), (b=4, d=5))
(a = 1, b = 4, c = 3, d = 5)
```
"""
function merge(a::NamedTuple{an}, b::NamedTuple{bn}) where {an, bn}
if @generated
names = merge_names(an, bn)
types = merge_types(names, a, b)
vals = Any[ :(getfield($(sym_in(n, bn) ? :b : :a), $(QuoteNode(n)))) for n in names ]
:( NamedTuple{$names,$types}(($(vals...),)) )
else
names = merge_names(an, bn)
types = merge_types(names, typeof(a), typeof(b))
NamedTuple{names,types}(map(n->getfield(sym_in(n, bn) ? b : a, n), names))
end
end
merge(a::NamedTuple{()}, b::NamedTuple) = b
merge(a::NamedTuple, b::Iterators.Pairs{<:Any,<:Any,<:Any,<:NamedTuple}) = merge(a, b.data)
"""
merge(a::NamedTuple, iterable)
Interpret an iterable of key-value pairs as a named tuple, and perform a merge.
```jldoctest
julia> merge((a=1, b=2, c=3), [:b=>4, :d=>5])
(a = 1, b = 4, c = 3, d = 5)
```
"""
function merge(a::NamedTuple, itr)
names = Symbol[]
vals = Any[]
inds = IdDict()
for (k,v) in itr
oldind = get(inds, k, 0)
if oldind > 0
vals[oldind] = v
else
push!(names, k)
push!(vals, v)
inds[k] = length(names)
end
end
merge(a, NamedTuple{(names...,)}((vals...,)))
end
keys(nt::NamedTuple{names}) where {names} = names
values(nt::NamedTuple) = Tuple(nt)
haskey(nt::NamedTuple, key::Union{Integer, Symbol}) = isdefined(nt, key)
get(nt::NamedTuple, key::Union{Integer, Symbol}, default) = haskey(nt, key) ? getfield(nt, key) : default
get(f::Callable, nt::NamedTuple, key::Union{Integer, Symbol}) = haskey(nt, key) ? getfield(nt, key) : f()
@pure function diff_names(an::Tuple{Vararg{Symbol}}, bn::Tuple{Vararg{Symbol}})
names = Symbol[]
for n in an
if !sym_in(n, bn)
push!(names, n)
end
end
(names...,)
end
"""
structdiff(a::NamedTuple{an}, b::Union{NamedTuple{bn},Type{NamedTuple{bn}}}) where {an,bn}
Construct a copy of named tuple `a`, except with fields that exist in `b` removed.
`b` can be a named tuple, or a type of the form `NamedTuple{field_names}`.
"""
function structdiff(a::NamedTuple{an}, b::Union{NamedTuple{bn}, Type{NamedTuple{bn}}}) where {an, bn}
if @generated
names = diff_names(an, bn)
types = Tuple{Any[ fieldtype(a, n) for n in names ]...}
vals = Any[ :(getfield(a, $(QuoteNode(n)))) for n in names ]
:( NamedTuple{$names,$types}(($(vals...),)) )
else
names = diff_names(an, bn)
types = Tuple{Any[ fieldtype(typeof(a), n) for n in names ]...}
NamedTuple{names,types}(map(n->getfield(a, n), names))
end
end