478 lines
12 KiB
Julia
478 lines
12 KiB
Julia
# This file is a part of Julia. License is MIT: https://julialang.org/license
|
|
|
|
# generic operations on associative collections
|
|
|
|
const secret_table_token = :__c782dbf1cf4d6a2e5e3865d7e95634f2e09b5902__
|
|
|
|
haskey(d::Associative, k) = in(k,keys(d))
|
|
|
|
function in(p::Pair, a::Associative, valcmp=(==))
|
|
v = get(a,p[1],secret_table_token)
|
|
if v !== secret_table_token
|
|
valcmp(v, p[2]) && return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
function in(p, a::Associative)
|
|
error("""Associative collections only contain Pairs;
|
|
Either look for e.g. A=>B instead, or use the `keys` or `values`
|
|
function if you are looking for a key or value respectively.""")
|
|
end
|
|
|
|
function summary(t::Associative)
|
|
n = length(t)
|
|
return string(typeof(t), " with ", n, (n==1 ? " entry" : " entries"))
|
|
end
|
|
|
|
struct KeyIterator{T<:Associative}
|
|
dict::T
|
|
end
|
|
struct ValueIterator{T<:Associative}
|
|
dict::T
|
|
end
|
|
|
|
summary{T<:Union{KeyIterator,ValueIterator}}(iter::T) =
|
|
string(T.name, " for a ", summary(iter.dict))
|
|
|
|
show(io::IO, iter::Union{KeyIterator,ValueIterator}) = show(io, collect(iter))
|
|
|
|
length(v::Union{KeyIterator,ValueIterator}) = length(v.dict)
|
|
isempty(v::Union{KeyIterator,ValueIterator}) = isempty(v.dict)
|
|
_tt1(::Type{Pair{A,B}}) where {A,B} = A
|
|
_tt2(::Type{Pair{A,B}}) where {A,B} = B
|
|
eltype(::Type{KeyIterator{D}}) where {D} = _tt1(eltype(D))
|
|
eltype(::Type{ValueIterator{D}}) where {D} = _tt2(eltype(D))
|
|
|
|
start(v::Union{KeyIterator,ValueIterator}) = start(v.dict)
|
|
done(v::Union{KeyIterator,ValueIterator}, state) = done(v.dict, state)
|
|
|
|
function next(v::KeyIterator, state)
|
|
n = next(v.dict, state)
|
|
n[1][1], n[2]
|
|
end
|
|
|
|
function next(v::ValueIterator, state)
|
|
n = next(v.dict, state)
|
|
n[1][2], n[2]
|
|
end
|
|
|
|
in(k, v::KeyIterator) = get(v.dict, k, secret_table_token) !== secret_table_token
|
|
|
|
|
|
"""
|
|
keys(a::Associative)
|
|
|
|
Return an iterator over all keys in a collection.
|
|
`collect(keys(a))` returns an array of keys.
|
|
Since the keys are stored internally in a hash table,
|
|
the order in which they are returned may vary.
|
|
But `keys(a)` and `values(a)` both iterate `a` and
|
|
return the elements in the same order.
|
|
|
|
```jldoctest
|
|
julia> a = Dict('a'=>2, 'b'=>3)
|
|
Dict{Char,Int64} with 2 entries:
|
|
'b' => 3
|
|
'a' => 2
|
|
|
|
julia> collect(keys(a))
|
|
2-element Array{Char,1}:
|
|
'b'
|
|
'a'
|
|
```
|
|
"""
|
|
keys(a::Associative) = KeyIterator(a)
|
|
eachindex(a::Associative) = KeyIterator(a)
|
|
|
|
"""
|
|
values(a::Associative)
|
|
|
|
Return an iterator over all values in a collection.
|
|
`collect(values(a))` returns an array of values.
|
|
Since the values are stored internally in a hash table,
|
|
the order in which they are returned may vary.
|
|
But `keys(a)` and `values(a)` both iterate `a` and
|
|
return the elements in the same order.
|
|
|
|
```jldoctest
|
|
julia> a = Dict('a'=>2, 'b'=>3)
|
|
Dict{Char,Int64} with 2 entries:
|
|
'b' => 3
|
|
'a' => 2
|
|
|
|
julia> collect(values(a))
|
|
2-element Array{Int64,1}:
|
|
3
|
|
2
|
|
```
|
|
"""
|
|
values(a::Associative) = ValueIterator(a)
|
|
|
|
function copy(a::Associative)
|
|
b = similar(a)
|
|
for (k,v) in a
|
|
b[k] = v
|
|
end
|
|
return b
|
|
end
|
|
|
|
"""
|
|
merge!(d::Associative, others::Associative...)
|
|
|
|
Update collection with pairs from the other collections.
|
|
See also [`merge`](@ref).
|
|
|
|
```jldoctest
|
|
julia> d1 = Dict(1 => 2, 3 => 4);
|
|
|
|
julia> d2 = Dict(1 => 4, 4 => 5);
|
|
|
|
julia> merge!(d1, d2);
|
|
|
|
julia> d1
|
|
Dict{Int64,Int64} with 3 entries:
|
|
4 => 5
|
|
3 => 4
|
|
1 => 4
|
|
```
|
|
"""
|
|
function merge!(d::Associative, others::Associative...)
|
|
for other in others
|
|
for (k,v) in other
|
|
d[k] = v
|
|
end
|
|
end
|
|
return d
|
|
end
|
|
|
|
"""
|
|
merge!(combine, d::Associative, others::Associative...)
|
|
|
|
Update collection with pairs from the other collections.
|
|
Values with the same key will be combined using the
|
|
combiner function.
|
|
|
|
```jldoctest
|
|
julia> d1 = Dict(1 => 2, 3 => 4);
|
|
|
|
julia> d2 = Dict(1 => 4, 4 => 5);
|
|
|
|
julia> merge!(+, d1, d2);
|
|
|
|
julia> d1
|
|
Dict{Int64,Int64} with 3 entries:
|
|
4 => 5
|
|
3 => 4
|
|
1 => 6
|
|
|
|
julia> merge!(-, d1, d1);
|
|
|
|
julia> d1
|
|
Dict{Int64,Int64} with 3 entries:
|
|
4 => 0
|
|
3 => 0
|
|
1 => 0
|
|
```
|
|
"""
|
|
function merge!(combine::Function, d::Associative, others::Associative...)
|
|
for other in others
|
|
for (k,v) in other
|
|
d[k] = haskey(d, k) ? combine(d[k], v) : v
|
|
end
|
|
end
|
|
return d
|
|
end
|
|
|
|
# very similar to `merge!`, but accepts any iterable and extends code
|
|
# that would otherwise only use `copy!` with arrays.
|
|
function copy!(dest::Union{Associative,AbstractSet}, src)
|
|
for x in src
|
|
push!(dest, x)
|
|
end
|
|
return dest
|
|
end
|
|
|
|
"""
|
|
keytype(type)
|
|
|
|
Get the key type of an associative collection type. Behaves similarly to [`eltype`](@ref).
|
|
|
|
```jldoctest
|
|
julia> keytype(Dict(Int32(1) => "foo"))
|
|
Int32
|
|
```
|
|
"""
|
|
keytype(::Type{Associative{K,V}}) where {K,V} = K
|
|
keytype(a::Associative) = keytype(typeof(a))
|
|
keytype(::Type{A}) where {A<:Associative} = keytype(supertype(A))
|
|
|
|
"""
|
|
valtype(type)
|
|
|
|
Get the value type of an associative collection type. Behaves similarly to [`eltype`](@ref).
|
|
|
|
```jldoctest
|
|
julia> valtype(Dict(Int32(1) => "foo"))
|
|
String
|
|
```
|
|
"""
|
|
valtype(::Type{Associative{K,V}}) where {K,V} = V
|
|
valtype{A<:Associative}(::Type{A}) = valtype(supertype(A))
|
|
valtype(a::Associative) = valtype(typeof(a))
|
|
|
|
"""
|
|
merge(d::Associative, others::Associative...)
|
|
|
|
Construct a merged collection from the given collections. If necessary, the
|
|
types of the resulting collection will be promoted to accommodate the types of
|
|
the merged collections. If the same key is present in another collection, the
|
|
value for that key will be the value it has in the last collection listed.
|
|
|
|
```jldoctest
|
|
julia> a = Dict("foo" => 0.0, "bar" => 42.0)
|
|
Dict{String,Float64} with 2 entries:
|
|
"bar" => 42.0
|
|
"foo" => 0.0
|
|
|
|
julia> b = Dict("baz" => 17, "bar" => 4711)
|
|
Dict{String,Int64} with 2 entries:
|
|
"bar" => 4711
|
|
"baz" => 17
|
|
|
|
julia> merge(a, b)
|
|
Dict{String,Float64} with 3 entries:
|
|
"bar" => 4711.0
|
|
"baz" => 17.0
|
|
"foo" => 0.0
|
|
|
|
julia> merge(b, a)
|
|
Dict{String,Float64} with 3 entries:
|
|
"bar" => 42.0
|
|
"baz" => 17.0
|
|
"foo" => 0.0
|
|
```
|
|
"""
|
|
merge(d::Associative, others::Associative...) =
|
|
merge!(emptymergedict(d, others...), d, others...)
|
|
|
|
"""
|
|
merge(combine, d::Associative, others::Associative...)
|
|
|
|
Construct a merged collection from the given collections. If necessary, the
|
|
types of the resulting collection will be promoted to accommodate the types of
|
|
the merged collections. Values with the same key will be combined using the
|
|
combiner function.
|
|
|
|
```jldoctest
|
|
julia> a = Dict("foo" => 0.0, "bar" => 42.0)
|
|
Dict{String,Float64} with 2 entries:
|
|
"bar" => 42.0
|
|
"foo" => 0.0
|
|
|
|
julia> b = Dict("baz" => 17, "bar" => 4711)
|
|
Dict{String,Int64} with 2 entries:
|
|
"bar" => 4711
|
|
"baz" => 17
|
|
|
|
julia> merge(+, a, b)
|
|
Dict{String,Float64} with 3 entries:
|
|
"bar" => 4753.0
|
|
"baz" => 17.0
|
|
"foo" => 0.0
|
|
```
|
|
"""
|
|
merge(combine::Function, d::Associative, others::Associative...) =
|
|
merge!(combine, emptymergedict(d, others...), d, others...)
|
|
|
|
promoteK(K) = K
|
|
promoteV(V) = V
|
|
promoteK(K, d, ds...) = promoteK(promote_type(K, keytype(d)), ds...)
|
|
promoteV(V, d, ds...) = promoteV(promote_type(V, valtype(d)), ds...)
|
|
function emptymergedict(d::Associative, others::Associative...)
|
|
K = promoteK(keytype(d), others...)
|
|
V = promoteV(valtype(d), others...)
|
|
Dict{K,V}()
|
|
end
|
|
|
|
function filter!(f, d::Associative)
|
|
badkeys = Vector{keytype(d)}(0)
|
|
for (k,v) in d
|
|
# don't delete!(d, k) here, since associative types
|
|
# may not support mutation during iteration
|
|
f(k,v) || push!(badkeys, k)
|
|
end
|
|
for k in badkeys
|
|
delete!(d, k)
|
|
end
|
|
return d
|
|
end
|
|
function filter(f, d::Associative)
|
|
# don't just do filter!(f, copy(d)): avoid making a whole copy of d
|
|
df = similar(d)
|
|
for (k,v) in d
|
|
if f(k,v)
|
|
df[k] = v
|
|
end
|
|
end
|
|
return df
|
|
end
|
|
|
|
eltype(::Type{Associative{K,V}}) where {K,V} = Pair{K,V}
|
|
|
|
function isequal(l::Associative, r::Associative)
|
|
l === r && return true
|
|
if isa(l,ObjectIdDict) != isa(r,ObjectIdDict)
|
|
return false
|
|
end
|
|
if length(l) != length(r) return false end
|
|
for pair in l
|
|
if !in(pair, r, isequal)
|
|
return false
|
|
end
|
|
end
|
|
true
|
|
end
|
|
|
|
function ==(l::Associative, r::Associative)
|
|
l === r && return true
|
|
if isa(l,ObjectIdDict) != isa(r,ObjectIdDict)
|
|
return false
|
|
end
|
|
if length(l) != length(r) return false end
|
|
for pair in l
|
|
if !in(pair, r, ==)
|
|
return false
|
|
end
|
|
end
|
|
true
|
|
end
|
|
|
|
const hasha_seed = UInt === UInt64 ? 0x6d35bb51952d5539 : 0x952d5539
|
|
function hash(a::Associative, h::UInt)
|
|
hv = hasha_seed
|
|
for (k,v) in a
|
|
hv ⊻= hash(k, hash(v))
|
|
end
|
|
hash(hv, h)
|
|
end
|
|
|
|
function getindex(t::Associative, key)
|
|
v = get(t, key, secret_table_token)
|
|
if v === secret_table_token
|
|
throw(KeyError(key))
|
|
end
|
|
return v
|
|
end
|
|
|
|
# t[k1,k2,ks...] is syntactic sugar for t[(k1,k2,ks...)]. (Note
|
|
# that we need to avoid dispatch loops if setindex!(t,v,k) is not defined.)
|
|
getindex(t::Associative, k1, k2, ks...) = getindex(t, tuple(k1,k2,ks...))
|
|
setindex!(t::Associative, v, k1, k2, ks...) = setindex!(t, v, tuple(k1,k2,ks...))
|
|
|
|
push!(t::Associative, p::Pair) = setindex!(t, p.second, p.first)
|
|
push!(t::Associative, p::Pair, q::Pair) = push!(push!(t, p), q)
|
|
push!(t::Associative, p::Pair, q::Pair, r::Pair...) = push!(push!(push!(t, p), q), r...)
|
|
|
|
# hashing objects by identity
|
|
|
|
"""
|
|
ObjectIdDict([itr])
|
|
|
|
`ObjectIdDict()` constructs a hash table where the keys are (always)
|
|
object identities. Unlike `Dict` it is not parameterized on its key
|
|
and value type and thus its `eltype` is always `Pair{Any,Any}`.
|
|
|
|
See [`Dict`](@ref) for further help.
|
|
"""
|
|
mutable struct ObjectIdDict <: Associative{Any,Any}
|
|
ht::Vector{Any}
|
|
ndel::Int
|
|
ObjectIdDict() = new(Vector{Any}(32), 0)
|
|
|
|
function ObjectIdDict(itr)
|
|
d = ObjectIdDict()
|
|
for (k,v) in itr; d[k] = v; end
|
|
d
|
|
end
|
|
|
|
function ObjectIdDict(pairs::Pair...)
|
|
d = ObjectIdDict()
|
|
for (k,v) in pairs; d[k] = v; end
|
|
d
|
|
end
|
|
|
|
ObjectIdDict(o::ObjectIdDict) = new(copy(o.ht))
|
|
end
|
|
|
|
similar(d::ObjectIdDict) = ObjectIdDict()
|
|
|
|
function rehash!(t::ObjectIdDict, newsz = length(t.ht))
|
|
t.ht = ccall(:jl_idtable_rehash, Any, (Any, Csize_t), t.ht, newsz)
|
|
t
|
|
end
|
|
|
|
function sizehint!(t::ObjectIdDict, newsz)
|
|
newsz = _tablesz(newsz*2) # *2 for keys and values in same array
|
|
oldsz = length(t.ht)
|
|
# grow at least 25%
|
|
if newsz < (oldsz*5)>>2
|
|
return t
|
|
end
|
|
rehash!(t, newsz)
|
|
end
|
|
|
|
function setindex!(t::ObjectIdDict, v::ANY, k::ANY)
|
|
if t.ndel >= ((3*length(t.ht))>>2)
|
|
rehash!(t, max(length(t.ht)>>1, 32))
|
|
t.ndel = 0
|
|
end
|
|
t.ht = ccall(:jl_eqtable_put, Array{Any,1}, (Any, Any, Any), t.ht, k, v)
|
|
return t
|
|
end
|
|
|
|
get(t::ObjectIdDict, key::ANY, default::ANY) =
|
|
ccall(:jl_eqtable_get, Any, (Any, Any, Any), t.ht, key, default)
|
|
|
|
function pop!(t::ObjectIdDict, key::ANY, default::ANY)
|
|
val = ccall(:jl_eqtable_pop, Any, (Any, Any, Any), t.ht, key, default)
|
|
# TODO: this can underestimate `ndel`
|
|
val === default || (t.ndel += 1)
|
|
return val
|
|
end
|
|
|
|
function pop!(t::ObjectIdDict, key::ANY)
|
|
val = pop!(t, key, secret_table_token)
|
|
val !== secret_table_token ? val : throw(KeyError(key))
|
|
end
|
|
|
|
function delete!(t::ObjectIdDict, key::ANY)
|
|
pop!(t, key, secret_table_token)
|
|
t
|
|
end
|
|
|
|
function empty!(t::ObjectIdDict)
|
|
resize!(t.ht, 32)
|
|
ccall(:memset, Ptr{Void}, (Ptr{Void}, Cint, Csize_t), t.ht, 0, sizeof(t.ht))
|
|
t.ndel = 0
|
|
return t
|
|
end
|
|
|
|
_oidd_nextind(a, i) = reinterpret(Int,ccall(:jl_eqtable_nextind, Csize_t, (Any, Csize_t), a, i))
|
|
|
|
start(t::ObjectIdDict) = _oidd_nextind(t.ht, 0)
|
|
done(t::ObjectIdDict, i) = (i == -1)
|
|
next(t::ObjectIdDict, i) = (Pair{Any,Any}(t.ht[i+1],t.ht[i+2]), _oidd_nextind(t.ht, i+2))
|
|
|
|
function length(d::ObjectIdDict)
|
|
n = 0
|
|
for pair in d
|
|
n+=1
|
|
end
|
|
n
|
|
end
|
|
|
|
copy(o::ObjectIdDict) = ObjectIdDict(o)
|
|
|
|
get!(o::ObjectIdDict, key, default) = (o[key] = get(o, key, default))
|