diff --git a/src/DataStructures.jl b/src/DataStructures.jl index 9812b49ab..7c9b8013b 100644 --- a/src/DataStructures.jl +++ b/src/DataStructures.jl @@ -43,6 +43,8 @@ module DataStructures export excludelast, tokens export orderobject, Lt + export MultiDict, enumerateall + if VERSION < v"0.4.0-dev" using Docile @@ -68,6 +70,8 @@ module DataStructures include("balancedTree.jl") include("tokens.jl") + include("multidict.jl") + import .Tokens: Token, IntSemiToken, semi, container, assemble import .Tokens: deref_key, deref_value, deref, status import .Tokens: advance, regress diff --git a/src/multidict.jl b/src/multidict.jl new file mode 100644 index 000000000..95392d5ab --- /dev/null +++ b/src/multidict.jl @@ -0,0 +1,129 @@ +# multi-value dictionary (multidict) + +import Base: haskey, get, get!, getkey, delete!, pop!, empty!, + setindex!, getindex, length, isempty, start, + next, done, keys, values, copy, similar, push!, + count, size + +immutable MultiDict{K,V} <: Associative{K,V} + d::Dict{K,Vector{V}} + + MultiDict() = new(Dict{K,Vector{V}}()) + MultiDict(kvs) = new(Dict{K,Vector{V}}(kvs)) + if VERSION >= v"0.4.0-dev+980" + MultiDict(ps::Pair{K,Vector{V}}...) = new(Dict{K,Vector{V}}(ps...)) + end +end + +MultiDict() = MultiDict{Any,Any}() +MultiDict(kv::@compat Tuple{}) = MultiDict() +MultiDict(kvs) = multi_dict_with_eltype(kvs, eltype(kvs)) + +multi_dict_with_eltype{K,V}(kvs, ::Type{@compat Tuple{K,Vector{V}}}) = MultiDict{K,V}(kvs) +function multi_dict_with_eltype{K,V}(kvs, ::Type{@compat Tuple{K,V}}) + md = MultiDict{K,V}() + for (k,v) in kvs + setindex!(md, v, k) + end + return md +end +multi_dict_with_eltype(kvs, t) = MultiDict{Any,Any}(kvs) + +if VERSION >= v"0.4.0-dev+980" + MultiDict{K,V<:AbstractArray}(ps::Pair{K,V}...) = MultiDict{K, eltype(V)}(ps) + MultiDict{K,V}(kv::AbstractArray{Pair{K,V}}) = MultiDict(kv...) + function MultiDict{K,V}(ps::Pair{K,V}...) + md = MultiDict{K,V}() + for (k,v) in ps + setindex!(md, v, k) + end + return md + end +end + +## Functions + +## Most functions are simply delegated to the wrapped Dict + +@delegate MultiDict.d [ haskey, get, get!, getkey, delete!, + empty!, getindex, length, isempty, + start, next, done, keys, values ] + +sizehint(d::MultiDict, sz::Integer) = (sizehint(d.d, sz); d) +copy(d::MultiDict) = MultiDict(d) +similar{K,V}(d::MultiDict{K,V}) = MultiDict{K,V}() + +function setindex!{K,V}(d::MultiDict{K,V}, v, k) + if !haskey(d.d, k) + d.d[k] = isa(v, AbstractArray) ? eltype(v)[] : V[] + end + if isa(v, AbstractArray) + append!(d.d[k], v) + else + push!(d.d[k], v) + end + return d +end + +function in{K,V}(pr::(@compat Tuple{Any,Any}), d::MultiDict{K,V}) + k = convert(K, pr[1]) + v = get(d,k,Base.secret_table_token) + !is(v, Base.secret_table_token) && (isa(pr[2], AbstractArray) ? v == pr[2] : pr[2] in v) +end + +function pop!(d::MultiDict, key, default) + vs = get(d, key, Base.secret_table_token) + if is(vs, Base.secret_table_token) + if !is(default, Base.secret_table_token) + return default + else + throw(KeyError(key)) + end + end + v = pop!(vs) + (length(vs) == 0) && delete!(d, key) + return v +end +pop!(d::MultiDict, key) = pop!(d, key, Base.secret_table_token) + +if VERSION >= v"0.4.0-dev+980" + push!(d::MultiDict, kv::Pair) = setindex!(d, kv[2], kv[1]) + #push!(d::MultiDict, kv::Pair, kv2::Pair) = (push!(d.d, kv, kv2); d) + #push!(d::MultiDict, kv::Pair, kv2::Pair, kv3::Pair...) = (push!(d.d, kv, kv2, kv3...); d) +end + +push!(d::MultiDict, kv) = setindex!(d, kv[2], kv[1]) +#push!(d::MultiDict, kv, kv2...) = (push!(d.d, kv, kv2...); d) + +count(d::MultiDict) = length(keys(d)) == 0 ? 0 : mapreduce(k -> length(d[k]), +, keys(d)) +size(d::MultiDict) = (length(keys(d)), count(d::MultiDict)) + +# enumerate + +immutable EnumerateAll + d::MultiDict +end +enumerateall(d::MultiDict) = EnumerateAll(d) + +length(e::EnumerateAll) = count(e.d) + +function start(e::EnumerateAll) + V = eltype(eltype(values(e.d))) + vs = V[] + (start(e.d.d), nothing, vs, start(vs)) +end + +function done(e::EnumerateAll, s) + dst, k, vs, vst = s + done(vs, vst) && done(e.d.d, dst) +end + +function next(e::EnumerateAll, s) + dst, k, vs, vst = s + while done(vs, vst) + ((k, vs), dst) = next(e.d.d, dst) + vst = start(vs) + end + v, vst = next(vs, vst) + ((k, v), (dst, k, vs, vst)) +end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index f4c003878..d4eb4cbd1 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -10,7 +10,8 @@ tests = ["deque", "ordereddict", "orderedset", "trie", - "list"] + "list", + "multidict"] for t in tests fp = joinpath(dirname(@__FILE__), "test_$t.jl") diff --git a/test/test_multidict.jl b/test/test_multidict.jl new file mode 100644 index 000000000..8555e704a --- /dev/null +++ b/test/test_multidict.jl @@ -0,0 +1,118 @@ +using DataStructures +using Base.Test + +# construction +KVS = ('a',[1]) +KV = ('a',1) +@test typeof(MultiDict()) == MultiDict{Any,Any} +@test typeof(MultiDict(())) == MultiDict{Any,Any} +@test typeof(MultiDict([KVS])) == MultiDict{Char,Int} +@test typeof(MultiDict([KV])) == MultiDict{Char,Int} +@test typeof(MultiDict([KV, KVS])) == MultiDict{Char,Any} + +if VERSION >= v"0.4.0-dev+980" + PVS = 1 => [1.0] + PV = 1 => 1.0 + @test eltype(MultiDict{Char,Int}()) == Tuple{Char,Int} + @test typeof(MultiDict(PVS)) == MultiDict{Int,Float64} + @test typeof(MultiDict(PVS, PVS)) == MultiDict{Int,Float64} + @test typeof(MultiDict([PVS, PVS])) == MultiDict{Int,Float64} + @test typeof(MultiDict(PV)) == MultiDict{Int,Float64} + @test typeof(MultiDict(PV, PV)) == MultiDict{Int,Float64} + @test typeof(MultiDict([PV, PV])) == MultiDict{Int,Float64} +end + +# setindex!, getindex, length, isempty, empty!, in +# copy, similar, get, haskey, getkey, start, next, done +d = MultiDict{Char,Int}() + +@test length(d) == 0 +@test isempty(d) + +@test setindex!(d, 1, 'a') == MultiDict{Char,Int}([('a', [1])]) +@test getindex(d, 'a') == [1] +@test setindex!(d, [2,3], 'a') == MultiDict{Char,Int}([('a', [1,2,3])]) +@test getindex(d, 'a') == [1,2,3] + +@test_throws KeyError d['c'] == 1 +d['c'] = 1 +@test collect(keys(d)) == ['c', 'a'] +@test collect(values(d)) == Array{Int,1}[[1],[1,2,3]] + +@test get(d, 'a', 0) == [1,2,3] +@test get(d, 'b', 0) == 0 + +@test haskey(d, 'a') +@test !haskey(d, 'b') +@test getkey(d, 'a', 0) == 'a' +@test getkey(d, 'b', 0) == 0 + +@test copy(d) == d +@test similar(d) == MultiDict{Char,Int}() + +@test [kv for kv in d] == [('c',[1]), ('a',[1,2,3])] + +@test in(('c', 1), d) +@test in(('a', 1), d) +@test in(('c', [1]), d) +@test in(('a', [1,2,3]), d) + +@test !isempty(d) +empty!(d) +@test isempty(d) + +# pop! +d = MultiDict{Char,Int}([('a', [1,2,3]), ('c', [1])]) +@test_throws KeyError pop!(d, 'b') +@test pop!(d, 'a') == 3 +@test pop!(d, 'a') == 2 +@test pop!(d, 'a') == 1 +@test !haskey(d, 'a') +@test pop!(d, 'b', 0) == 0 + +# delete! +d = MultiDict{Char,Int}([('a', [1,2,3]), ('c', [1])]) +@test delete!(d, 'b') == d +@test delete!(d, 'a') == MultiDict{Char,Int}([('c', [1])]) + +# setindex! +d = MultiDict{Char,Int}() +@test setindex!(d, 1, 'a') == MultiDict{Char,Int}([('a', [1])]) +@test setindex!(d, 1, 'a') == MultiDict{Char,Int}([('a', [1, 1])]) + +# push! +d = MultiDict{Char,Int}() +@test push!(d, ('a',[1])) == MultiDict{Char,Int}([('a', [1])]) +@test push!(d, ('a',[1])) == MultiDict{Char,Int}([('a', [1,1])]) +empty!(d) +@test push!(d, ('a',1)) == MultiDict{Char,Int}([('a', [1])]) +@test push!(d, ('a',1)) == MultiDict{Char,Int}([('a', [1,1])]) + +if VERSION >= v"0.4.0-dev+980" + empty!(d) + @test push!(d,'a'=>[1]) == MultiDict{Char,Int}([('a', [1])]) + @test push!(d, 'a'=>1) == MultiDict{Char,Int}([('a', [1,1])]) +end + +# get! +@test get!(d, 'a', []) == [1,1] +@test get!(d, 'b', []) == Int[] +@test get!(d, 'c', [1]) == [1] +@test_throws MethodError get!(d, 'd', 1) + +# special functions: count, enumerateall +d = MultiDict{Char,Int}() +@test count(d) == 0 +for i in 1:15 + d[rand('a':'f')] = rand()>0.5 ? rand(1:10) : rand(1:10, rand(1:3)) +end +@test 15 <= count(d) <=45 +@test size(d) == (length(d), count(d)) + +allvals = [kv for kv in enumerateall(d)] +@test length(allvals) == count(d) +@test all([in(kv,d) for kv in enumerateall(d)]) + +# @test length(d) == 15 +# @test length(values(d)) == 15 +# @test length(keys(d)) <= 6