Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add LittleDict #19

Merged
merged 19 commits into from
Mar 29, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
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: 3 additions & 1 deletion src/OrderedCollections.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ module OrderedCollections
valtype, lastindex, nextind,
copymutable, emptymutable

export OrderedDict, OrderedSet
export OrderedDict, OrderedSet, LittleDict
export freeze

include("dict_support.jl")
include("ordered_dict.jl")
include("little_dict.jl")
include("ordered_set.jl")
include("dict_sorting.jl")

Expand Down
13 changes: 13 additions & 0 deletions src/dict_sorting.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,16 @@ end

sort(d::OrderedDict; args...) = sort!(copy(d); args...)
sort(d::Dict; args...) = sort!(OrderedDict(d); args...)

function sort!(d::LittleDict; byvalue::Bool=false, args...)
if byvalue
p = sortperm(d.vals; args...)
oxinabox marked this conversation as resolved.
Show resolved Hide resolved
else
p = sortperm(d.keys; args...)
end
d.keys = d.keys[p]
oxinabox marked this conversation as resolved.
Show resolved Hide resolved
d.vals = d.vals[p]
return d
end

sort(d::LittleDict; args) = sort!(copy(d,args))
89 changes: 89 additions & 0 deletions src/little_dict.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"""
LittleDict(keys, vals)<:AbstractDict

A ordered dictionary type for small numbers of keys.
Rather than using `hash` or some other sophisicated measure
to store the vals in a clever arrangement,
it just keeps everything in a pair of lists.

While theoretically this has expected time complexity _O(n)_,
vs the hash-based `OrderDict`/`Dict`'s expected time complexity _O(1)_,
and the search-tree-based `SortedDict`'s expected time complcity _O(log(n))_.
In practice it is really fast, because it is cache & SIMD friendly.

It is reasonable to expect it to outperform an `OrderedDict`,
with up to around 30 elements in general;
or with up to around 50 elements if using a `LittleDict` backed by `Tuples`
(see [`freeze`](@ref))
However, this depends on exactly how long `isequal` and `hash` take,
as well as on how many hash collisions occur etc.
"""
struct LittleDict{K,V,KS,VS} <: AbstractDict{K, V}
keys::KS
vals::VS
kmsquire marked this conversation as resolved.
Show resolved Hide resolved
end

function LittleDict(ks::KS, vs::VS) where {KS,VS}
return LittleDict{eltype(KS), eltype(VS), KS, VS}(ks, vs)
end


function LittleDict{K,V}(itr) where {K,V}
ks = K[]
vs = V[]
for (k, v) in itr
push!(ks, k)
push!(vs, v)
end
return LittleDict(ks, vs)
end

LittleDict{K,V}() where {K,V} = LittleDict{K,V}(tuple())
LittleDict() = LittleDict{Any, Any}()
LittleDict(itr::T) where T = LittleDict{kvtype(eltype(T))...}(itr)

kvtype(::Any) = (Any, Any)
kvtype(::Type{<:Pair{K,V}}) where {K,V} = (K,V)
kvtype(::Type{<:Tuple{K,V}}) where {K,V} = (K,V)


Base.length(dd::LittleDict) = length(dd.keys)
Base.sizehint!(dd::LittleDict) = (sizehint!(dd.ks); sizehint!(dd.vs))

function Base.getindex(dd::LittleDict, key)
@assert length(dd.keys) == length(dd.vals)
@simd for ii in 1:length(dd.keys)
oxinabox marked this conversation as resolved.
Show resolved Hide resolved
cand = @inbounds dd.keys[ii]
isequal(cand, key) && return @inbounds(dd.vals[ii])
end
throw(KeyError(key))
end

function Base.setindex!(dd::LittleDict, value, key)
@assert length(dd.keys) == length(dd.vals)
@simd for ii in 1:length(dd.keys)
oxinabox marked this conversation as resolved.
Show resolved Hide resolved
cand = @inbounds dd.keys[ii]
isequal(cand, key) && return @inbounds(dd.vals[ii] = value)
end
# Not found, add to the end
push!(dd.keys, key)
push!(dd.vals, value)
return value
end

Base.iterate(dd::LittleDict, args...) = iterate(zip(dd.keys, dd.vals), args...)
oxinabox marked this conversation as resolved.
Show resolved Hide resolved


"""
freeze(dd::AbstractDict)
oxinabox marked this conversation as resolved.
Show resolved Hide resolved
Render an dictionary immutable by converting it to a `Tuple` backed
`LittleDict`.
This will make it faster if it is small enough.
In particular the `Tuple` backed `LittleDict` is faster than the
`Vector` backed `LittleDict`.
oxinabox marked this conversation as resolved.
Show resolved Hide resolved
"""
function freeze(dd::AbstractDict)
ks = Tuple(keys(dd))
vs = Tuple(values(dd))
return LittleDict(ks, vs)
end