Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/DataStructures.jl
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ module DataStructures
export excludelast, tokens
export orderobject, Lt

export MultiDict, enumerateall


if VERSION < v"0.4.0-dev"
using Docile
Expand All @@ -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
Expand Down
129 changes: 129 additions & 0 deletions src/multidict.jl
Original file line number Diff line number Diff line change
@@ -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
3 changes: 2 additions & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ tests = ["deque",
"ordereddict",
"orderedset",
"trie",
"list"]
"list",
"multidict"]

for t in tests
fp = joinpath(dirname(@__FILE__), "test_$t.jl")
Expand Down
118 changes: 118 additions & 0 deletions test/test_multidict.jl
Original file line number Diff line number Diff line change
@@ -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