Skip to content

Instantly share code, notes, and snippets.

@chethega
Created July 5, 2019 13:25
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chethega/e4eb5f6dcae85576c46cc8b91c461dcf to your computer and use it in GitHub Desktop.
Save chethega/e4eb5f6dcae85576c46cc8b91c461dcf to your computer and use it in GitHub Desktop.
swissdict for julia
# This file is a part of Julia. License is MIT: https://julialang.org/license
function _truncate_at_width_or_chars(str, width, chars="", truncmark="…")
truncwidth = textwidth(truncmark)
(width <= 0 || width < truncwidth) && return ""
wid = truncidx = lastidx = 0
for (idx, c) in pairs(str)
lastidx = idx
wid += textwidth(c)
wid >= width - truncwidth && truncidx == 0 && (truncidx = lastidx)
(wid >= width || c in chars) && break
end
lastidx != 0 && str[lastidx] in chars && (lastidx = prevind(str, lastidx))
truncidx == 0 && (truncidx = lastidx)
if lastidx < lastindex(str)
return String(SubString(str, 1, truncidx) * truncmark)
else
return String(str)
end
end
function show(io::IO, t::AbstractDict{K,V}) where V where K
recur_io = IOContext(io, :SHOWN_SET => t,
:typeinfo => eltype(t))
limit::Bool = get(io, :limit, false)
# show in a Julia-syntax-like form: Dict(k=>v, ...)
print(io, typeinfo_prefix(io, t))
print(io, '(')
if !isempty(t) && !show_circular(io, t)
first = true
n = 0
for pair in t
first || print(io, ',')
first = false
show(recur_io, pair)
n+=1
limit && n >= 10 && (print(io, "…"); break)
end
end
print(io, ')')
end
# Dict
const _u8x16 = NTuple{16, VecElement{UInt8}}
"""
Dict([itr])
`Dict{K,V}()` constructs a hash table with keys of type `K` and values of type `V`.
Keys are compared with [`isequal`](@ref) and hashed with [`hash`](@ref).
Given a single iterable argument, constructs a [`Dict`](@ref) whose key-value pairs
are taken from 2-tuples `(key,value)` generated by the argument.
# Examples
```jldoctest
julia> Dict([("A", 1), ("B", 2)])
Dict{String,Int64} with 2 entries:
"B" => 2
"A" => 1
```
Alternatively, a sequence of pair arguments may be passed.
```jldoctest
julia> Dict("A"=>1, "B"=>2)
Dict{String,Int64} with 2 entries:
"B" => 2
"A" => 1
```
"""
mutable struct Dict{K,V} <: AbstractDict{K,V}
slots::Vector{_u8x16}
keys::Vector{K}
vals::Vector{V}
nbfull::Int
count::Int
age::UInt
idxfloor::Int # an index <= the indices of all used slots
function Dict{K,V}() where V where K
new(fill(_expand16(0x00),1), Vector{K}(undef, 16), Vector{V}(undef, 16), 0, 0, 0, 1)
end
function Dict{K,V}(d::Dict{K,V}) where V where K
new(copy(d.slots), copy(d.keys), copy(d.vals), d.nbfull, d.count, d.age,
d.idxfloor)
end
function Dict{K, V}(slots, keys, vals, nbfull, count, age, idxfloor) where {K, V}
new(slots, keys, vals, nbfull, count, age, idxfloor)
end
end
function Dict{K,V}(kv) where V where K
h = Dict{K,V}()
if IteratorSize(typeof(kv)) !== SizeUnknown()
sizehint!(h, length(kv))
end
for (k,v) in kv
h[k] = v
end
return h
end
Dict{K,V}(p::Pair) where {K,V} = setindex!(Dict{K,V}(), p.second, p.first)
function Dict{K,V}(ps::Pair...) where V where K
h = Dict{K,V}()
sizehint!(h, length(ps))
for p in ps
h[p.first] = p.second
end
return h
end
# Note the constructors of WeakKeyDict mirror these here, keep in sync.
Dict() = Dict{Any,Any}()
Dict(kv::Tuple{}) = Dict()
copy(d::Dict) = Dict(d)
const AnyDict = Dict{Any,Any}
Dict(ps::Pair{K,V}...) where {K,V} = Dict{K,V}(ps)
Dict(ps::Pair...) = Dict(ps)
function Dict(kv)
try
dict_with_eltype((K, V) -> Dict{K, V}, kv, eltype(kv))
catch
if !isiterable(typeof(kv)) || !all(x->isa(x,Union{Tuple,Pair}),kv)
throw(ArgumentError("Dict(kv): kv needs to be an iterator of tuples or pairs"))
else
rethrow()
end
end
end
function grow_to!(dest::AbstractDict{K, V}, itr) where V where K
y = iterate(itr)
y === nothing && return dest
((k,v), st) = y
dest2 = empty(dest, typeof(k), typeof(v))
dest2[k] = v
grow_to!(dest2, itr, st)
end
# this is a special case due to (1) allowing both Pairs and Tuples as elements,
# and (2) Pair being invariant. a bit annoying.
function grow_to!(dest::AbstractDict{K,V}, itr, st) where V where K
y = iterate(itr, st)
while y !== nothing
(k,v), st = y
if isa(k,K) && isa(v,V)
dest[k] = v
else
new = empty(dest, promote_typejoin(K,typeof(k)), promote_typejoin(V,typeof(v)))
merge!(new, dest)
new[k] = v
return grow_to!(new, itr, st)
end
y = iterate(itr, st)
end
return dest
end
empty(a::AbstractDict, ::Type{K}, ::Type{V}) where {K, V} = Dict{K, V}()
#hashindex(key, sz) = (((hash(key)%Int) & (sz-1)) + 1)::Int
##Simd utilities
@inline _expand16(u::UInt8) = ntuple(i->VecElement(u), Val(16))
_blsr(i::UInt32)= i & (i-Int32(1))
@inline _vcmp_eq(u::_u8x16, v::_u8x16) = Core.Intrinsics.llvmcall(("""
%cmp = icmp eq <16 x i8> %0, %1
%cmp16 = bitcast <16 x i1> %cmp to i16
%res = zext i16 %cmp16 to i32
ret i32 %res
"""), UInt32, Tuple{_u8x16,_u8x16}, u, v)
@inline _vcmp_le(u::_u8x16, v::_u8x16) = Core.Intrinsics.llvmcall(("""
%cmp = icmp ule <16 x i8> %0, %1
%cmp16 = bitcast <16 x i1> %cmp to i16
%res = zext i16 %cmp16 to i32
ret i32 %res
"""), UInt32, Tuple{_u8x16,_u8x16}, u, v)
@inline function _prefetchr(p::Ptr)
p = reinterpret(UInt, p)
if UInt === UInt64
Core.Intrinsics.llvmcall(("declare void @llvm.prefetch(i8*, i32, i32, i32)", """
%ptr = inttoptr i64 %0 to i8*
call void @llvm.prefetch(i8* %ptr, i32 0, i32 3, i32 1)
ret void
"""), Nothing, Tuple{UInt}, p)
else
Core.Intrinsics.llvmcall(("declare void @llvm.prefetch(i8*, i32, i32, i32)", """
%ptr = inttoptr i32 %0 to i8*
call void @llvm.prefetch(i8* %ptr, i32 0, i32 3, i32 1)
ret void
"""), Nothing, Tuple{UInt}, p)
end
end
@inline function _prefetchw(p::Ptr)
p = reinterpret(UInt, p)
if UInt === UInt64
Core.Intrinsics.llvmcall(("declare void @llvm.prefetch(i8*, i32, i32, i32)", """
%ptr = inttoptr i64 %0 to i8*
call void @llvm.prefetch(i8* %ptr, i32 1, i32 3, i32 1)
ret void
"""), Nothing, Tuple{UInt}, p)
else
Core.Intrinsics.llvmcall(("declare void @llvm.prefetch(i8*, i32, i32, i32)", """
%ptr = inttoptr i32 %0 to i8*
call void @llvm.prefetch(i8* %ptr, i32 1, i32 3, i32 1)
ret void
"""), Nothing, Tuple{UInt}, p)
end
end
@inline function _hashtag(u::Unsigned)
#extracts tag between 0x02 and 0xff from lower bits, rotates tag bits to front
u = u % UInt
tag = u % UInt8
if UInt === UInt64
hi = ((u>>8) | (u<<56)) % Int
else
hi = ((u>>8) | (u<<24)) % Int
end
tag = tag > 1 ? tag : tag+0x02
return (hi, tag)
end
@propagate_inbounds function _slotget(slots::Vector{_u8x16}, i::Int)
#@boundscheck 0 < i <= length(slots)*16 || throw(BoundsError(slots, 1 + (i-1)>>4)
GC.@preserve slots begin
return unsafe_load(convert(Ptr{UInt8}, pointer(slots)), i)
end
end
@propagate_inbounds function _slotset!(slots::Vector{_u8x16}, v::UInt8, i::Int)
# @boundscheck 0 < i <= length(slots)*16 || throw(BoundsError(slots, 1 + (i-1)>>4))
GC.@preserve slots begin
return unsafe_store!(convert(Ptr{UInt8}, pointer(slots)), v, i)
end
end
@inline function _find_candidates(v::_u8x16, tag::UInt8)
match = _vcmp_eq(v, _expand16(tag))
return (match, v[16].value === 0x00)
end
@inline _find_free(v::_u8x16) = _vcmp_le(v, _expand16(UInt8(1)))
##Basic operations
# get the index where a key is stored, or -1 if not present
ht_keyindex(h::Dict, key) = ht_keyindex(h::Dict, key, _hashtag(hash(key))...)
function ht_keyindex(h::Dict, key, i0, tag)
slots = h.slots
keys = h.keys
sz = length(slots)
i = i0 & (sz-1)
#_prefetchr(pointer(h.keys, i*16+1))
# _prefetchr(pointer(h.vals, i*16+1))
#Todo/discuss: _prefetchr(pointer(h.keys, i*16+9))?
@inbounds while true
msk = slots[i+1]
cands, done = _find_candidates(msk, tag)
while cands != 0
off = trailing_zeros(cands)
idx = i*16 + off + 1
isequal(keys[idx], key) && return idx
cands = _blsr(cands)
end
done && break
i = (i+1) & (sz-1)
end
return -1
end
# get the index where a key is stored, or -pos if not present
# and the key would be inserted at pos
# This version is for use by setindex! and get!. It never rehashes.
ht_keyindex2!(h::Dict, key) = ht_keyindex2!(h, key, _hashtag(hash(key))...)
@inline function ht_keyindex2!(h::Dict, key, i0, tag)
slots = h.slots
keys = h.keys
sz = length(slots)
i = i0 & (sz-1)
_prefetchw(pointer(h.keys, i*16+1))
_prefetchw(pointer(h.vals, i*16+1))
#Todo/discuss: _prefetchr(pointer(h.keys, i*16+9))?
@inbounds while true
msk = slots[i+1]
cands, done = _find_candidates(msk, tag)
while cands != 0
off = trailing_zeros(cands)
idx = i*16 + off + 1
isequal(keys[idx], key) && return idx, tag
cands = _blsr(cands)
end
done && break
i = (i+1) & (sz-1)
end
i = i0 & (sz-1)
@inbounds while true
msk = slots[i+1]
cands = _find_free(msk)
if cands != 0
off = trailing_zeros(cands)
idx = i*16 + off + 1
return -idx, tag
end
i = (i+1) & (sz-1)
end
end
@propagate_inbounds function _setindex!(h::Dict, v, key, index, tag)
h.keys[index] = key
h.vals[index] = v
h.count += 1
h.age += 1
so = _slotget(h.slots, index)
h.nbfull += (iszero(index & 0x0f) & (so==0x00))
_slotset!(h.slots, tag, index)
if index < h.idxfloor
h.idxfloor = index
end
maybe_rehash_grow!(h)
end
function _delete!(h::Dict{K,V}, index) where {K,V}
#Caller is responsible for maybe shrinking the Dict after the deletion.
isbitstype(K) || isbitsunion(K) || ccall(:jl_arrayunset, Cvoid, (Any, UInt), h.keys, index-1)
isbitstype(V) || isbitsunion(V) || ccall(:jl_arrayunset, Cvoid, (Any, UInt), h.vals, index-1)
isboundary = iszero(index & 0x0f) #boundaries: 16, 32, ...
@inbounds _slotset!(h.slots, ifelse(isboundary, 0x01, 0x00), index)
h.count -= 1
h.age += 1
return h
end
#fast iteration over active slots.
function _iterslots(h::Dict, start::Int)
i0 = ((start-1) & (length(h.keys)-1))>>4 + 1
off = (start-1) & 0x0f
@inbounds sl = _find_free(h.slots[i0>>4 + 1])
sl = ((~sl & 0xffff)>>off) << off
return _iterslots(h, (i0, sl))
end
function _iterslots(h::Dict, state)
i, sl = state
while iszero(sl)
i += 1
i <= length(h.slots) || return nothing
@inbounds msk = h.slots[i]
sl = _find_free(msk)
sl = (~sl & 0xffff)
end
return ((i-1)*16 + trailing_zeros(sl) + 1, (i, _blsr(sl)))
end
#Dictionary resize logic:
#Guarantee 40% of buckets and 15% of entries free, and at least 25% of entries filled
#growth when > 85% entries full or > 60% buckets full, shrink when <25% entries full.
#>60% bucket full should be super rare outside of very bad hash collisions or
#super long-lived Dictionaries (expected 0.85^16 = 7% buckets full at 85% entries full).
#worst-case hysteresis: shrink at 25% vs grow at 30% if all hashes collide.
#expected hysteresis is 25% to 42.5%.
function maybe_rehash_grow!(h::Dict)
sz = length(h.keys)
if h.count > sz * 0.75 || (h.nbfull-1) * 10 > sz * 6
rehash!(h, 2*sz)
end
nothing
end
function maybe_rehash_shrink!(h::Dict)
# sz = length(h.keys)
# if h.count*4 < sz && sz > 16
# rehash!(h, sz>>1)
# end
nothing
end
function _dictsizehint(sz)
sz<= 16 && return 16
nsz = _tablesz(sz)
sz > 0.85*nsz ? 2*nsz : nsz
end
function sizehint!(d::Dict{T}, newsz) where T
nsz = _dictsizehint(newsz)
if newsz > nsz
rehash!(d, nsz)
end
d
end
function rehash!(h::Dict{K,V}, newsz = length(h.keys)) where V where K
olds = h.slots
oldk = h.keys
oldv = h.vals
sz = length(oldk)
newsz = _tablesz(newsz)
newsz*0.95 > h.count || (newsz *= 2)
h.age += 1
h.idxfloor = 1
if h.count == 0
resize!(h.slots, newsz>>4)
fill!(h.slots, _expand16(0x00))
resize!(h.keys, newsz)
resize!(h.vals, newsz)
h.nbfull = 0
return h
end
nssz = newsz>>4
slots = fill(_expand16(0x00), nssz)
keys = Vector{K}(undef, newsz)
vals = Vector{V}(undef, newsz)
age0 = h.age
nbfull = 0
is = _iterslots(h, 1)
count = 0
@inbounds while is !== nothing
i, s = is
k = oldk[i]
v = oldv[i]
i0, t = _hashtag(hash(k))
i = i0 & (nssz-1)
idx = 0
while true
msk = slots[i + 1]
cands = _find_free(msk)
if cands != 0
off = trailing_zeros(cands)
idx = i*16 + off + 1
break
end
i = (i+1) & (nssz-1)
end
_slotset!(slots, t, idx)
keys[idx] = k
vals[idx] = v
nbfull += iszero(idx & 0x0f)
count += 1
if h.age != age0
return rehash!(h, newsz)
end
is = _iterslots(h, s)
end
h.slots = slots
h.keys = keys
h.vals = vals
h.nbfull = nbfull
@assert h.age == age0
@assert h.count == count
return h
end
"""
empty!(collection) -> collection
Remove all elements from a `collection`.
# Examples
```jldoctest
julia> A = Dict("a" => 1, "b" => 2)
Dict{String,Int64} with 2 entries:
"b" => 2
"a" => 1
julia> empty!(A);
julia> A
Dict{String,Int64} with 0 entries
```
"""
function empty!(h::Dict{K,V}) where V where K
fill!(h.slots, _expand16(0x00))
sz = length(h.keys)
empty!(h.keys)
empty!(h.vals)
resize!(h.keys, sz)
resize!(h.vals, sz)
h.nbfull = 0
h.count = 0
h.age += 1
h.idxfloor = 1
return h
end
function setindex!(h::Dict{K,V}, v0, key0) where V where K
key = convert(K, key0)
if !isequal(key, key0)
throw(ArgumentError("$(limitrepr(key0)) is not a valid key for type $K"))
end
setindex!(h, v0, key)
end
function setindex!(h::Dict{K,V}, v0, key::K) where V where K
v = convert(V, v0)
index, tag = ht_keyindex2!(h, key)
if index > 0
h.age += 1
@inbounds h.keys[index] = key
@inbounds h.vals[index] = v
else
@inbounds _setindex!(h, v, key, -index, tag)
end
return h
end
"""
get!(collection, key, default)
Return the value stored for the given key, or if no mapping for the key is present, store
`key => default`, and return `default`.
# Examples
```jldoctest
julia> d = Dict("a"=>1, "b"=>2, "c"=>3);
julia> get!(d, "a", 5)
1
julia> get!(d, "d", 4)
4
julia> d
Dict{String,Int64} with 4 entries:
"c" => 3
"b" => 2
"a" => 1
"d" => 4
```
"""
get!(collection, key, default)
get!(h::Dict{K,V}, key0, default) where {K,V} = get!(()->default, h, key0)
"""
get!(f::Function, collection, key)
Return the value stored for the given key, or if no mapping for the key is present, store
`key => f()`, and return `f()`.
This is intended to be called using `do` block syntax:
```julia
get!(Dict, key) do
# default value calculated here
time()
end
```
"""
get!(f::Function, collection, key)
function get!(default::Callable, h::Dict{K,V}, key0) where V where K
key = convert(K, key0)
if !isequal(key, key0)
throw(ArgumentError("$(limitrepr(key0)) is not a valid key for type $K"))
end
return get!(default, h, key)
end
function get!(default::Callable, h::Dict{K,V}, key::K) where V where K
index, tag = ht_keyindex2!(h, key)
index > 0 && return h.vals[index]
age0 = h.age
v = convert(V, default())
if h.age != age0
index, tag = ht_keyindex2!(h, key)
end
if index > 0
h.age += 1
@inbounds h.keys[index] = key
@inbounds h.vals[index] = v
else
@inbounds _setindex!(h, v, key, -index, tag)
end
return v
end
# NOTE: this macro is trivial, and should
# therefore not be exported as-is: it's for internal use only.
macro get!(h, key0, default)
return quote
get!(()->$(esc(default)), $(esc(h)), $(esc(key0)))
end
end
function getindex(h::Dict{K,V}, key) where V where K
index = ht_keyindex(h, key)
@inbounds return (index < 0) ? throw(KeyError(key)) : h.vals[index]::V
end
"""
get(collection, key, default)
Return the value stored for the given key, or the given default value if no mapping for the
key is present.
# Examples
```jldoctest
julia> d = Dict("a"=>1, "b"=>2);
julia> get(d, "a", 3)
1
julia> get(d, "c", 3)
3
```
"""
get(collection, key, default)
function get(h::Dict{K,V}, key, default) where V where K
index = ht_keyindex(h, key)
@inbounds return (index < 0) ? default : h.vals[index]::V
end
"""
get(f::Function, collection, key)
Return the value stored for the given key, or if no mapping for the key is present, return
`f()`. Use [`get!`](@ref) to also store the default value in the Dictionary.
This is intended to be called using `do` block syntax
```julia
get(Dict, key) do
# default value calculated here
time()
end
```
"""
get(::Function, collection, key)
function get(default::Callable, h::Dict{K,V}, key) where V where K
index = ht_keyindex(h, key)
@inbounds return (index < 0) ? default() : h.vals[index]::V
end
"""
haskey(collection, key) -> Bool
Determine whether a collection has a mapping for a given `key`.
# Examples
```jldoctest
julia> D = Dict('a'=>2, 'b'=>3)
Dict{Char,Int64} with 2 entries:
'a' => 2
'b' => 3
julia> haskey(D, 'a')
true
julia> haskey(D, 'c')
false
```
"""
haskey(h::Dict, key) = (ht_keyindex(h, key) > 0)
in(key, v::KeySet{<:Any, <:Dict}) = (ht_keyindex(v.dict, key) > 0)
"""
getkey(collection, key, default)
Return the key matching argument `key` if one exists in `collection`, otherwise return `default`.
# Examples
```jldoctest
julia> D = Dict('a'=>2, 'b'=>3)
Dict{Char,Int64} with 2 entries:
'a' => 2
'b' => 3
julia> getkey(D, 'a', 1)
'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
julia> getkey(D, 'd', 'a')
'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
```
"""
function getkey(h::Dict{K,V}, key, default) where V where K
index = ht_keyindex(h, key)
@inbounds return (index<0) ? default : h.keys[index]::K
end
function _pop!(h::Dict, index)
@inbounds val = h.vals[index]
_delete!(h, index)
maybe_rehash_shrink!(h)
return val
end
function pop!(h::Dict, key)
index = ht_keyindex(h, key)
return index > 0 ? _pop!(h, index) : throw(KeyError(key))
end
"""
pop!(collection, key[, default])
Delete and return the mapping for `key` if it exists in `collection`, otherwise return
`default`, or throw an error if `default` is not specified.
# Examples
```jldoctest
julia> d = Dict("a"=>1, "b"=>2, "c"=>3);
julia> pop!(d, "a")
1
julia> pop!(d, "d")
ERROR: KeyError: key "d" not found
Stacktrace:
[...]
julia> pop!(d, "e", 4)
4
```
"""
pop!(collection, key, default)
function pop!(h::Dict, key, default)
index = ht_keyindex(h, key)
return index > 0 ? _pop!(h, index) : default
end
function pop!(h::Dict)
isempty(h) && throw(ArgumentError("Dict must be non-empty"))
is = _iterslots(h, h.idxfloor)
@assert is !== nothing
idx, s = is
@inbounds key = h.keys[idx]
@inbounds val = h.vals[idx]
_delete!(h, idx)
h.idxfloor = idx
key => val
end
"""
delete!(collection, key)
Delete the mapping for the given key in a collection, and return the collection.
# Examples
```jldoctest
julia> d = Dict("a"=>1, "b"=>2)
Dict{String,Int64} with 2 entries:
"b" => 2
"a" => 1
julia> delete!(d, "b")
Dict{String,Int64} with 1 entry:
"a" => 1
```
"""
delete!(collection, key)
function delete!(h::Dict, key)
index = ht_keyindex(h, key)
if index > 0
_delete!(h, index)
end
maybe_rehash_shrink!(h)
return h
end
isempty(t::Dict) = (t.count == 0)
length(t::Dict) = t.count
@propagate_inbounds function iterate(h::Dict, state = h.idxfloor)
is = _iterslots(h, state)
is === nothing && return nothing
i, s = is
@inbounds p = h.keys[i] => h.vals[i]
return (p, s)
end
@propagate_inbounds function iterate(v::Union{KeySet{<:Any, <:Dict}, ValueIterator{<:Dict}}, state=v.dict.idxfloor)
is = _iterslots(v.dict, state)
is === nothing && return nothing
i, s = is
return (v isa KeySet ? v.dict.keys[i] : v.dict.vals[i], s)
end
function filter!(pred, h::Dict{K,V}) where {K,V}
h.count == 0 && return h
is = _iterslots(v, h.idxfloor)
while is !== nothing
i, s = is
@inbounds k = h.keys[i]
@inbounds v = h.vals[i]
if !pred(Pair{K,V}(k,v))
_delete!(h, i)
end
end
maybe_rehash_shrink!(h)
h
end
struct ImmutableDict{K,V} <: AbstractDict{K,V}
parent::ImmutableDict{K,V}
key::K
value::V
ImmutableDict{K,V}() where {K,V} = new() # represents an empty Dictionary
ImmutableDict{K,V}(key, value) where {K,V} = (empty = new(); new(empty, key, value))
ImmutableDict{K,V}(parent::ImmutableDict, key, value) where {K,V} = new(parent, key, value)
end
"""
ImmutableDict
ImmutableDict is a Dictionary implemented as an immutable linked list,
which is optimal for small Dictionaries that are constructed over many individual insertions
Note that it is not possible to remove a value, although it can be partially overridden and hidden
by inserting a new value with the same key
ImmutableDict(KV::Pair)
Create a new entry in the Immutable Dictionary for the key => value pair
- use `(key => value) in Dict` to see if this particular combination is in the properties set
- use `get(Dict, key, default)` to retrieve the most recent value for a particular key
"""
ImmutableDict
ImmutableDict(KV::Pair{K,V}) where {K,V} = ImmutableDict{K,V}(KV[1], KV[2])
ImmutableDict(t::ImmutableDict{K,V}, KV::Pair) where {K,V} = ImmutableDict{K,V}(t, KV[1], KV[2])
function in(key_value::Pair, Dict::ImmutableDict, valcmp=(==))
key, value = key_value
while isdefined(Dict, :parent)
if Dict.key == key
valcmp(value, Dict.value) && return true
end
Dict = Dict.parent
end
return false
end
function haskey(Dict::ImmutableDict, key)
while isdefined(Dict, :parent)
Dict.key == key && return true
Dict = Dict.parent
end
return false
end
function getindex(Dict::ImmutableDict, key)
while isdefined(Dict, :parent)
Dict.key == key && return Dict.value
Dict = Dict.parent
end
throw(KeyError(key))
end
function get(Dict::ImmutableDict, key, default)
while isdefined(Dict, :parent)
Dict.key == key && return Dict.value
Dict = Dict.parent
end
return default
end
# this actually defines reverse iteration (e.g. it should not be used for merge/copy/filter type operations)
function iterate(d::ImmutableDict{K,V}, t=d) where {K, V}
!isdefined(t, :parent) && return nothing
(Pair{K,V}(t.key, t.value), t.parent)
end
length(t::ImmutableDict) = count(x->true, t)
isempty(t::ImmutableDict) = !isdefined(t, :parent)
empty(::ImmutableDict, ::Type{K}, ::Type{V}) where {K, V} = ImmutableDict{K,V}()
_similar_for(c::Dict, ::Type{Pair{K,V}}, itr, isz) where {K, V} = empty(c, K, V)
_similar_for(c::AbstractDict, ::Type{T}, itr, isz) where {T} =
throw(ArgumentError("for AbstractDicts, similar requires an element type of Pair;\n if calling map, consider a comprehension instead"))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment