Skip to content

Commit

Permalink
implement an ImmutableDict type
Browse files Browse the repository at this point in the history
this is designed to be used as the basis for the IOContext type

also fix a bug in testing equality of a self-referential dict (with added test)
  • Loading branch information
vtjnash committed Dec 24, 2015
1 parent cf862eb commit 1fe1391
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 7 deletions.
111 changes: 105 additions & 6 deletions base/dict.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,20 @@ const secret_table_token = :__c782dbf1cf4d6a2e5e3865d7e95634f2e09b5902__

haskey(d::Associative, k) = in(k,keys(d))

function in(p::Pair, a::Associative)
function in(p::Pair, a::Associative, valcmp=(==))
v = get(a,p[1],secret_table_token)
!is(v, secret_table_token) && (v == p[2])
if !is(v, secret_table_token)
if valcmp === is
is(v, p[2]) && return true
elseif valcmp === (==)
==(v, p[2]) && return true
elseif valcmp === isequal
isequal(v, p[2]) && return true
else
valcmp(v, p[2]) && return true
end
end
return false
end

function in(p, a::Associative)
Expand Down Expand Up @@ -245,25 +256,27 @@ filter(f, d::Associative) = filter!(f,copy(d))
eltype{K,V}(::Type{Associative{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 (key, value) in l
if !isequal(value, get(r, key, secret_table_token))
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 (key, value) in l
if value != get(r, key, secret_table_token)
for pair in l
if !in(pair, r, ==)
return false
end
end
Expand Down Expand Up @@ -855,3 +868,89 @@ function next{K,V}(t::WeakKeyDict{K,V}, i)
(Pair{K,V}(kv[1].value::K,kv[2]), i)
end
length(t::WeakKeyDict) = length(t.ht)


immutable ImmutableDict{K, V} <: Associative{K,V}
parent::ImmutableDict{K, V}
key::K
value::V
ImmutableDict() = new() # represents an empty dictionary
ImmutableDict(key, value) = (empty = new(); new(empty, key, value))
ImmutableDict(parent::ImmutableDict, key, value) = 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{K,V}(KV::Pair{K,V}) = ImmutableDict{K,V}(KV[1], KV[2])
ImmutableDict{K,V}(t::ImmutableDict{K,V}, KV::Pair) = 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
if valcmp === is
is(value, dict.value) && return true
elseif valcmp === (==)
==(value, dict.value) && return true
elseif valcmp === isequal
isequal(value, dict.value) && return true
else
valcmp(value, dict.value) && return true
end
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)
start(t::ImmutableDict) = t
next{K,V}(::ImmutableDict{K,V}, t) = (Pair{K,V}(t.key, t.value), t.parent)
done(::ImmutableDict, t) = !isdefined(t, :parent)
length(t::ImmutableDict) = count(x->1, t)
isempty(t::ImmutableDict) = done(t, start(t))
copy(t::ImmutableDict) = t
function similar(t::ImmutableDict)
while isdefined(t, :parent)
t = t.parent
end
return t
end
73 changes: 72 additions & 1 deletion test/dict.jl
Original file line number Diff line number Diff line change
Expand Up @@ -329,17 +329,19 @@ let
end

# issue #10647
type T10647{T}; x::T; end
let
a = ObjectIdDict()
a[1] = a
a[a] = 2
type T10647{T}; x::T; end
a[3] = T10647(a)
@test a == a
show(IOBuffer(), a)
Base.showdict(IOBuffer(), a)
Base.showdict(IOBuffer(), a; limit=true)
end


# Issue #7944
let d = Dict{Int,Int}()
get!(d, 0) do
Expand All @@ -358,3 +360,72 @@ d = Dict('a'=>1, 'b'=>1, 'c'=> 3)
@test_throws ArgumentError Dict(0)
@test_throws ArgumentError Dict([1])
@test_throws ArgumentError Dict([(1,2),0])

# ImmutableDict
import Base.ImmutableDict
let d = ImmutableDict{UTF8String, UTF8String}(),
k1 = UTF8String("key1"),
k2 = UTF8String("key2"),
v1 = UTF8String("value1"),
v2 = UTF8String("value2"),
d1 = ImmutableDict(d, k1 => v1),
d2 = ImmutableDict(d1, k2 => v2),
d3 = ImmutableDict(d2, k1 => v2),
d4 = ImmutableDict(d3, k2 => v1),
dnan = ImmutableDict{UTF8String, Float64}(k2, NaN),
dnum = ImmutableDict(dnan, k2 => 1)

@test isempty(collect(d))
@test !isempty(collect(d1))
@test isempty(d)
@test !isempty(d1)
@test length(d) == 0
@test length(d1) == 1
@test length(d2) == 2
@test length(d3) == 3
@test length(d4) == 4
@test !(k1 in keys(d))
@test k1 in keys(d1)
@test k1 in keys(d2)
@test k1 in keys(d3)
@test k1 in keys(d4)

@test !haskey(d, k1)
@test haskey(d1, k1)
@test haskey(d2, k1)
@test haskey(d3, k1)
@test haskey(d4, k1)
@test !(k2 in keys(d1))
@test k2 in keys(d2)
@test !(k1 in values(d4))
@test v1 in values(d4)
@test collect(d1) == [Pair(k1, v1)]
@test collect(d4) == reverse([Pair(k1, v1), Pair(k2, v2), Pair(k1, v2), Pair(k2, v1)])
@test d1 == ImmutableDict(d, k1 => v1)
@test !((k1 => v2) in d2)
@test (k1 => v2) in d3
@test (k1 => v1) in d4
@test (k1 => v2) in d4
@test !in(k2 => "value2", d4, is)
@test in(k2 => v2, d4, is)
@test in(k2 => NaN, dnan, isequal)
@test in(k2 => NaN, dnan, is)
@test !in(k2 => NaN, dnan, ==)
@test !in(k2 => 1, dnum, is)
@test in(k2 => 1.0, dnum, is)
@test !in(k2 => 1, dnum, <)
@test in(k2 => 0, dnum, <)
@test get(d1, "key1", :default) === v1
@test get(d4, "key1", :default) === v2
@test get(d4, "foo", :default) === :default
@test get(d, k1, :default) === :default
@test d1["key1"] === v1
@test d4["key1"] === v2
@test copy(d4) === d4
@test copy(d) === d
@test similar(d3) === d
@test similar(d) === d

@test_throws KeyError d[k1]
@test_throws KeyError d1["key2"]
end

0 comments on commit 1fe1391

Please sign in to comment.