Skip to content

Commit

Permalink
Merge pull request #136 from andyferris/copy-indices
Browse files Browse the repository at this point in the history
Copy indices
  • Loading branch information
andyferris authored Jan 30, 2024
2 parents 259a3f0 + 524f1a8 commit 544201c
Show file tree
Hide file tree
Showing 17 changed files with 105 additions and 218 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "Dictionaries"
uuid = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4"
authors = ["Andy Ferris <[email protected]>"]
version = "0.3.29"
version = "0.4.0"

[deps]
Indexing = "313cdc1a-70c2-5d6a-ae34-0150d3930a38"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,7 @@ an iterative approach rather than using multiple hash-table lookups per element,
relatively snappy.

```julia
julia> @btime map(+, d1, $(Dictionary(copy(keys(d2)), d2)));
julia> @btime map(+, d1, $(copy(d2)));
61.615 ms (20 allocations: 76.29 MiB)
```

Expand Down
36 changes: 16 additions & 20 deletions src/AbstractDictionary.jl
Original file line number Diff line number Diff line change
Expand Up @@ -373,23 +373,21 @@ function Base.merge(d1::AbstractDictionary, others::AbstractDictionary...)
return out
end

if isdefined(Base, :mergewith) # Julia 1.5+
function Base.mergewith(combiner, d1::AbstractDictionary, others::AbstractDictionary...)
# Note: need to copy the keys
K = promote_type(keytype(d1), keytype.(others)...)
T = promote_op_valtype(combiner, d1, others...)
out = similar(copy(keys(d1), K), T)
copyto!(out, d1)
mergewith!(combiner, out, others...)
return out
end
promote_op_valtype(combiner, d1::AbstractDictionary{<:Any,T}, others::AbstractDictionary...) where {T} =
promote_op_valtype(T, combiner, d1, others...)
promote_op_valtype(T::Type, combiner, d1::AbstractDictionary, others::AbstractDictionary...) =
promote_op_valtype(promote_op_valtype(T, combiner, d1), combiner, others...)
promote_op_valtype(T::Type, combiner, ::AbstractDictionary{<:Any,T´}) where {T´} =
promote_type(T, T´, Base.promote_op(combiner, T, T´))
function Base.mergewith(combiner, d1::AbstractDictionary, others::AbstractDictionary...)
# Note: need to copy the keys
K = promote_type(keytype(d1), keytype.(others)...)
T = promote_op_valtype(combiner, d1, others...)
out = similar(copy(keys(d1), K), T)
copyto!(out, d1)
mergewith!(combiner, out, others...)
return out
end
promote_op_valtype(combiner, d1::AbstractDictionary{<:Any,T}, others::AbstractDictionary...) where {T} =
promote_op_valtype(T, combiner, d1, others...)
promote_op_valtype(T::Type, combiner, d1::AbstractDictionary, others::AbstractDictionary...) =
promote_op_valtype(promote_op_valtype(T, combiner, d1), combiner, others...)
promote_op_valtype(T::Type, combiner, ::AbstractDictionary{<:Any,T´}) where {T´} =
promote_type(T, T´, Base.promote_op(combiner, T, T´))

# fill! and fill

Expand Down Expand Up @@ -486,18 +484,16 @@ function Random.randn(rng::AbstractRNG, ::Type{T}, dict::AbstractDictionary) whe
end


# Copying - note that this doesn't necessarily copy the indices! (`copy(keys(dict))` can do that)
"""
copy(dict::AbstractDictionary)
copy(dict::AbstractDictionary, ::Type{T})
Create a shallow copy of the values of `dict`. Note that `keys(dict)` is not copied, and
therefore care must be taken that inserting/deleting elements. A new element type `T` can
Create a shallow copy of the values and keys of `dict`. A new element type `T` can
optionally be specified.
"""
Base.copy(dict::AbstractDictionary) = copy(dict, eltype(dict))
function Base.copy(d::AbstractDictionary, ::Type{T}) where {T}
out = similar(d, T)
out = similar(copy(keys(d)), T)
copyto!(out, d)
return out
end
Expand Down
22 changes: 1 addition & 21 deletions src/AbstractIndices.jl
Original file line number Diff line number Diff line change
Expand Up @@ -353,28 +353,8 @@ function randtoken(::Random.AbstractRNG, i::AbstractIndices)
error("randtoken is not implemented for $(typeof(i))")
end

@static if VERSION < v"1.5-"
"""
disjoint(set1, set2)
Return `true` if `set1` and `set2` are disjoint or `false`. Two sets are disjoint if no
elements of `set1` is in `set2`, and vice-versa. Somewhat equivalent to, but faster than,
`isempty(intersect(set1, set2))`.
"""
function disjoint(set1, set2)
for i in set1
if i in set2
return false
end
end
return true
end
else
@deprecate disjoint(set1, set2) isdisjoint(set1, set2)
end

function Base.sort(inds::AbstractIndices{I}; kwargs...) where {I}
ks = collect(inds)
sort!(ks; kwargs...)
return empty_type(typeof(inds), I)(ks)
end
end
2 changes: 1 addition & 1 deletion src/ArrayDictionary.jl
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ function Base.similar(inds::ArrayIndices, ::Type{T}) where {T}
return ArrayDictionary(inds, similar(parent(inds), T))
end

Base.copy(dict::ArrayDictionary) = ArrayDictionary(dict.indices, copy(dict.values))
Base.copy(dict::ArrayDictionary) = ArrayDictionary(copy(dict.indices), copy(dict.values))

# insertable interface
isinsertable(::ArrayDictionary) = true # Need an array trait for this...
Expand Down
2 changes: 1 addition & 1 deletion src/Dictionary.jl
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ function Base.convert(::Type{Dictionary{I, T}}, dict::Dictionary) where {I, T}
end
Base.convert(::Type{T}, dict::T) where {T<:Dictionary} = dict

Base.copy(dict::Dictionary) = Dictionary(dict.indices, copy(dict.values))
Base.copy(dict::Dictionary) = Dictionary(copy(dict.indices), copy(dict.values))

function Base.deepcopy_internal(dict::Dictionary{I,T}, id::IdDict) where {I,T}
if haskey(id, dict)
Expand Down
2 changes: 1 addition & 1 deletion src/UnorderedDictionary.jl
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ function gettoken!(d::UnorderedDictionary{T}, key::T) where {T}
end

function Base.copy(d::UnorderedDictionary{I, T}) where {I, T}
return UnorderedDictionary{I, T}(d.indices, copy(d.values), nothing)
return UnorderedDictionary{I, T}(copy(d.indices), copy(d.values), nothing)
end

tokenized(d::UnorderedDictionary) = d.values
Expand Down
22 changes: 10 additions & 12 deletions src/insertion.jl
Original file line number Diff line number Diff line change
Expand Up @@ -336,21 +336,19 @@ function Base.merge!(d::AbstractDictionary, others::AbstractDictionary...)
return d
end

if isdefined(Base, :mergewith) # Julia 1.5+
function Base.mergewith!(combiner, d::AbstractDictionary, d2::AbstractDictionary)
for (i, v) in pairs(d2)
(hasindex, token) = gettoken!(d, i)
if hasindex
@inbounds settokenvalue!(d, token, combiner(gettokenvalue(d, token), v))
else
@inbounds settokenvalue!(d, token, v)
end
function Base.mergewith!(combiner, d::AbstractDictionary, d2::AbstractDictionary)
for (i, v) in pairs(d2)
(hasindex, token) = gettoken!(d, i)
if hasindex
@inbounds settokenvalue!(d, token, combiner(gettokenvalue(d, token), v))
else
@inbounds settokenvalue!(d, token, v)
end
return d
end
Base.mergewith!(combiner, d::AbstractDictionary, others::AbstractDictionary...) =
foldl(mergewith!(combiner), others, init = d)
return d
end
Base.mergewith!(combiner, d::AbstractDictionary, others::AbstractDictionary...) =
foldl(mergewith!(combiner), others, init = d)

# TODO some kind of exclusive merge (throw on key clash like `insert!`)

Expand Down
10 changes: 4 additions & 6 deletions src/map.jl
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,8 @@ end

empty_type(::Type{<:MappedDictionary{<:Any, <:Any, <:Any, <:Tuple{D, Vararg{AbstractDictionary}}}}, ::Type{I}, ::Type{T}) where {I, T, D} = empty_type(D, I, T)

if VERSION > v"1.6-"
function Iterators.map(f, d::AbstractDictionary)
I = keytype(d)
T = Core.Compiler.return_type(f, Tuple{eltype(d)}) # Base normally wouldn't invoke inference for something like this...
return MappedDictionary{I, T, typeof(f), Tuple{typeof(d)}}(f, (d,))
end
function Iterators.map(f, d::AbstractDictionary)
I = keytype(d)
T = Core.Compiler.return_type(f, Tuple{eltype(d)}) # Base normally wouldn't invoke inference for something like this...
return MappedDictionary{I, T, typeof(f), Tuple{typeof(d)}}(f, (d,))
end
26 changes: 10 additions & 16 deletions test/ArrayDictionary.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,7 @@
@test get(() -> 15, d, "10") == 15
@test length(unset!(d, 10)) == 0
io = IOBuffer(); print(io, d); @test String(take!(io)) == "{}"
if VERSION < v"1.6-"
io = IOBuffer(); show(io, MIME"text/plain"(), d); @test String(take!(io)) == "0-element ArrayDictionary{Int64,Int64,ArrayIndices{Int64,Array{Int64,1}},Array{Int64,1}}"
else
io = IOBuffer(); show(io, MIME"text/plain"(), d); @test String(take!(io)) == "0-element ArrayDictionary{Int64, Int64, ArrayIndices{Int64, Vector{Int64}}, Vector{Int64}}"
end
io = IOBuffer(); show(io, MIME"text/plain"(), d); @test String(take!(io)) == "0-element ArrayDictionary{Int64, Int64, ArrayIndices{Int64, Vector{Int64}}, Vector{Int64}}"
@test_throws IndexError d[10] = 11
@test_throws IndexError delete!(d, 10)

Expand Down Expand Up @@ -55,11 +51,7 @@
d[10.0] = 13.0
@test d[10] == 13
io = IOBuffer(); print(io, d); @test String(take!(io)) == "{10 = 13}"
if VERSION < v"1.6-"
io = IOBuffer(); show(io, MIME"text/plain"(), d); @test String(take!(io)) == "1-element ArrayDictionary{Int64,Int64,ArrayIndices{Int64,Array{Int64,1}},Array{Int64,1}}\n 10 │ 13"
else
io = IOBuffer(); show(io, MIME"text/plain"(), d); @test String(take!(io)) == "1-element ArrayDictionary{Int64, Int64, ArrayIndices{Int64, Vector{Int64}}, Vector{Int64}}\n 10 │ 13"
end
io = IOBuffer(); show(io, MIME"text/plain"(), d); @test String(take!(io)) == "1-element ArrayDictionary{Int64, Int64, ArrayIndices{Int64, Vector{Int64}}, Vector{Int64}}\n 10 │ 13"
@test !isequal(d, empty(d))
@test isequal(d, copy(d))
@test isempty(empty(d))
Expand Down Expand Up @@ -91,16 +83,18 @@

dictcopy = copy(dict)
@test dict isa ArrayDictionary{Int64, String}
@test sharetokens(dict, dictcopy)
if VERSION < v"1.6-"
io = IOBuffer(); show(io, MIME"text/plain"(), dict); @test String(take!(io)) == "2-element ArrayDictionary{Int64,String,ArrayIndices{Int64,Array{Int64,1}},Array{String,1}}\n 1 │ #undef\n 2 │ #undef"
else
io = IOBuffer(); show(io, MIME"text/plain"(), dict); @test String(take!(io)) == "2-element ArrayDictionary{Int64, String, ArrayIndices{Int64, Vector{Int64}}, Vector{String}}\n 1 │ #undef\n 2 │ #undef"
end
@test !sharetokens(dict, dictcopy)
io = IOBuffer(); show(io, MIME"text/plain"(), dict); @test String(take!(io)) == "2-element ArrayDictionary{Int64, String, ArrayIndices{Int64, Vector{Int64}}, Vector{String}}\n 1 │ #undef\n 2 │ #undef"
@test all(!isassigned(dict, i) for i in collect(keys(dict)))
@test all(!isassigned(dictcopy, i) for i in collect(keys(dictcopy)))
@test sharetokens(dict, ArrayDictionary{Int64, String}(dict))
@test pairs(dict) isa Dictionaries.PairDictionary{Int64, String}

dict = ArrayDictionary([1,2,3,4,5], [1,3,2,4,5])
dictcopy = copy(dict)
set!(dict, 6, 7)
@test length(dict) == 6
@test length(dictcopy) == 5
end

@testset "sort" begin
Expand Down
12 changes: 2 additions & 10 deletions test/ArrayIndices.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,7 @@
@test_throws IndexError inds[10]
@test length(unset!(inds, 10)) == 0
io = IOBuffer(); print(io, inds); @test String(take!(io)) == "{}"
if VERSION < v"1.6-"
io = IOBuffer(); show(io, MIME"text/plain"(), inds); @test String(take!(io)) == "0-element ArrayIndices{Int64,Array{Int64,1}}"
else
io = IOBuffer(); show(io, MIME"text/plain"(), inds); @test String(take!(io)) == "0-element ArrayIndices{Int64, Vector{Int64}}"
end
io = IOBuffer(); show(io, MIME"text/plain"(), inds); @test String(take!(io)) == "0-element ArrayIndices{Int64, Vector{Int64}}"
@test_throws IndexError delete!(inds, 10)

insert!(inds, 10)
Expand All @@ -30,11 +26,7 @@
@test length(set!(inds, 10)) == 1
@test_throws IndexError insert!(inds, 10)
io = IOBuffer(); print(io, inds); @test String(take!(io)) == "{10}"
if VERSION < v"1.6-"
io = IOBuffer(); show(io, MIME"text/plain"(), inds); @test String(take!(io)) == "1-element ArrayIndices{Int64,Array{Int64,1}}\n 10"
else
io = IOBuffer(); show(io, MIME"text/plain"(), inds); @test String(take!(io)) == "1-element ArrayIndices{Int64, Vector{Int64}}\n 10"
end
io = IOBuffer(); show(io, MIME"text/plain"(), inds); @test String(take!(io)) == "1-element ArrayIndices{Int64, Vector{Int64}}\n 10"
@test !isequal(inds, empty(inds))
@test isequal(inds, copy(inds))
@test isempty(empty(inds))
Expand Down
52 changes: 23 additions & 29 deletions test/Dictionary.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,24 @@
@test isempty(keys(d))
@test d == d
@test d == copy(d)
@test d == Dictionary(copy(keys(d)), d)
@test d == map(identity, d)
@test isequal(d, d)
@test isequal(copy(d), d)
@test isequal(Dictionary(copy(keys(d)), d), d)
@test isequal(map(identity, d), d)
@test !isless(d, d)
@test !isless(copy(d), d)
@test !isless(Dictionary(copy(keys(d)), d), d)
@test !isless(map(identity, d), d)
@test cmp(d, d) == 0
@test cmp(copy(d), d) == 0
@test cmp(Dictionary(copy(keys(d)), d), d) == 0
@test cmp(map(identity, d), d) == 0
@test_throws IndexError d[10]
@test get(d, 10, 15) == 15
@test get(() -> 15, d, 10) == 15
@test get(d, "10", 15) == 15
@test get(() -> 15, d, "10") == 15
@test length(unset!(d, 10)) == 0
io = IOBuffer(); print(io, d); @test String(take!(io)) == "{}"
if VERSION < v"1.6-"
io = IOBuffer(); show(io, MIME"text/plain"(), d); @test String(take!(io)) == "0-element Dictionary{Int64,Int64}"
else
io = IOBuffer(); show(io, MIME"text/plain"(), d); @test String(take!(io)) == "0-element Dictionary{Int64, Int64}"
end
io = IOBuffer(); show(io, MIME"text/plain"(), d); @test String(take!(io)) == "0-element Dictionary{Int64, Int64}"
@test_throws IndexError d[10] = 11
@test_throws IndexError delete!(d, 10)

Expand All @@ -55,7 +51,7 @@
@test !isempty(d)
@test d == d
@test d == copy(d)
@test d == Dictionary(copy(keys(d)), d)
@test d == map(identity, d)
@test d != empty(d)
@test empty(d) != d
@test fill(0, d) != d
Expand All @@ -64,7 +60,7 @@
@test d != fill(0, copy(keys(d)))
@test isequal(d, d)
@test isequal(copy(d), d)
@test isequal(Dictionary(copy(keys(d)), d), d)
@test isequal(map(identity, d), d)
@test !isequal(d, empty(d))
@test !isequal(empty(d), d)
@test !isequal(fill(0, d), d)
Expand All @@ -73,7 +69,7 @@
@test !isequal(d, fill(0, copy(keys(d))))
@test !isless(d, d)
@test !isless(copy(d), d)
@test !isless(Dictionary(copy(keys(d)), d), d)
@test !isless(map(identity, d), d)
@test !isless(d, empty(d))
@test isless(empty(d), d)
@test isless(fill(0, d), d)
Expand All @@ -82,7 +78,7 @@
@test !isless(d, fill(0, copy(keys(d))))
@test cmp(d, d) == 0
@test cmp(copy(d), d) == 0
@test cmp(Dictionary(copy(keys(d)), d), d) == 0
@test cmp(map(identity, d), d) == 0
@test cmp(d, empty(d)) == 1
@test cmp(empty(d), d) == -1
@test cmp(fill(0, d), d) == -1
Expand All @@ -105,11 +101,7 @@
d[10.0] = 13.0
@test d[10] == 13
io = IOBuffer(); print(io, d); @test String(take!(io)) == "{10 = 13}"
if VERSION < v"1.6-"
io = IOBuffer(); show(io, MIME"text/plain"(), d); @test String(take!(io)) == "1-element Dictionary{Int64,Int64}\n 10 │ 13"
else
io = IOBuffer(); show(io, MIME"text/plain"(), d); @test String(take!(io)) == "1-element Dictionary{Int64, Int64}\n 10 │ 13"
end
io = IOBuffer(); show(io, MIME"text/plain"(), d); @test String(take!(io)) == "1-element Dictionary{Int64, Int64}\n 10 │ 13"
@test !isequal(d, empty(d))
@test isequal(d, copy(d))
@test isempty(empty(d))
Expand Down Expand Up @@ -204,12 +196,8 @@

dictcopy = copy(dict)
@test dict isa Dictionary{Int64, String}
@test sharetokens(dict, dictcopy)
if VERSION < v"1.6-"
io = IOBuffer(); show(io, MIME"text/plain"(), dict); @test String(take!(io)) == "2-element Dictionary{Int64,String}\n 1 │ #undef\n 2 │ #undef"
else
io = IOBuffer(); show(io, MIME"text/plain"(), dict); @test String(take!(io)) == "2-element Dictionary{Int64, String}\n 1 │ #undef\n 2 │ #undef"
end
@test !sharetokens(dict, dictcopy)
io = IOBuffer(); show(io, MIME"text/plain"(), dict); @test String(take!(io)) == "2-element Dictionary{Int64, String}\n 1 │ #undef\n 2 │ #undef"
@test all(!isassigned(dict, i) for i in collect(keys(dict)))
@test all(!isassigned(dictcopy, i) for i in collect(keys(dictcopy)))
@test sharetokens(dict, Dictionary{Int64, String}(dict))
Expand Down Expand Up @@ -285,6 +273,14 @@
end
end

@testset "copy" begin
dict = Dictionary([1,2,3,4,5], [1,3,2,4,5])
dictcopy = copy(dict)
set!(dict, 6, 7)
@test length(dict) == 6
@test length(dictcopy) == 5
end

@testset "dictionary" begin
res = Dictionary(['a','b','c'], [1,2,3])
@test isequal(dictionary(pairs(res)), res)
Expand Down Expand Up @@ -396,11 +392,9 @@
@test merge!(d2, d1) == d1
@test_throws InexactError merge!(d1, d2, d3)

if isdefined(Base, :mergewith) # Julia 1.5+
@test mergewith(+, d1, d2, d3) isa Dictionary{Int, Float64}
@test_throws InexactError mergewith!(+, d1, d2, d3)
@test mergewith(+, d3, d1, d2) isa Dictionary{Int, Float64}
end
@test mergewith(+, d1, d2, d3) isa Dictionary{Int, Float64}
@test_throws InexactError mergewith!(+, d1, d2, d3)
@test mergewith(+, d3, d1, d2) isa Dictionary{Int, Float64}
end

@testset "deepcopy" begin
Expand Down
Loading

3 comments on commit 544201c

@andyferris
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistartor register

@andyferris
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/99845

Tip: Release Notes

Did you know you can add release notes too? Just add markdown formatted text underneath the comment after the text
"Release notes:" and it will be added to the registry PR, and if TagBot is installed it will also be added to the
release that TagBot creates. i.e.

@JuliaRegistrator register

Release notes:

## Breaking changes

- blah

To add them here just re-invoke and the PR will be updated.

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.4.0 -m "<description of version>" 544201cd4257af01cfdf14283684b6716ff275ea
git push origin v0.4.0

Please sign in to comment.