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 merge and friends with arbitrary number of args #130

Merged
merged 2 commits into from
Jan 22, 2024
Merged
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
31 changes: 20 additions & 11 deletions src/AbstractDictionary.jl
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ function Base.isless(dict1::AbstractDictionary, dict2::AbstractDictionary)
(p1, s1) = tmp1
(p2, s2) = tmp2
c = cmp(p1, p2)

if c == -1
return true
elseif c == 1
Expand Down Expand Up @@ -244,7 +244,7 @@ function Base.cmp(dict1::AbstractDictionary, dict2::AbstractDictionary)
(p1, s1) = tmp1
(p2, s2) = tmp2
c = cmp(p1, p2)

if c == -1
return -1
elseif c == 1
Expand All @@ -270,7 +270,7 @@ function Base.hash(dict::AbstractDictionary, h::UInt)
h1 = hash(i, h1)
h2 = hash(v, h2)
end

return hash(hash(UInt === UInt64 ? 0x8955a87bc313a509 : 0xa9cff5d1, h1), h2)
end

Expand Down Expand Up @@ -363,23 +363,32 @@ end
empty_type(::Type{<:AbstractDictionary}, ::Type{I}, ::Type{T}) where {I, T} = Dictionary{I, T}
Base.empty(dict::AbstractDictionary, ::Type{I}, ::Type{T}) where {I, T} = empty_type(typeof(dict), I, T)()

function Base.merge(d1::AbstractDictionary{K1, T1}, d2::AbstractDictionary{K2, T2}) where {K1, T1, K2, T2}
function Base.merge(d1::AbstractDictionary, others::AbstractDictionary...)
# Note: need to copy the keys
out = similar(copy(keys(d1), promote_type(K1, K2)), promote_type(T1, T2))
K = promote_type(keytype(d1), keytype.(others)...)
T = promote_type(valtype(d1), valtype.(others)...)
out = similar(copy(keys(d1), K), T)
copyto!(out, d1)
merge!(out, d2)
merge!(out, others...)
return out
end

if isdefined(Base, :mergewith) # Julia 1.5+
function Base.mergewith(combiner, d1::AbstractDictionary{K1, T1}, d2::AbstractDictionary{K2, T2}) where {K1, T1, K2, T2}
function Base.mergewith(combiner, d1::AbstractDictionary, others::AbstractDictionary...)
# Note: need to copy the keys
T3 = Base.promote_op(combiner, T1, T2)
out = similar(copy(keys(d1), promote_type(K1, K2)), promote_type(T1, T2, T3))
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, d2)
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´))
end

# fill! and fill
Expand Down Expand Up @@ -483,7 +492,7 @@ end
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
therefore care must be taken that inserting/deleting elements. A new element type `T` can
optionally be specified.
"""
Base.copy(dict::AbstractDictionary) = copy(dict, eltype(dict))
Expand Down
12 changes: 8 additions & 4 deletions src/insertion.jl
Original file line number Diff line number Diff line change
Expand Up @@ -323,15 +323,17 @@ function unset!(d::AbstractDictionary{I}, i::I) where {I}
end

### Non-scalar insertion/deletion
function Base.merge!(d::AbstractDictionary, d2::AbstractDictionary)
for (i, v) in pairs(d2)
set!(d, i, v)
function Base.merge!(d::AbstractDictionary, others::AbstractDictionary...)
for other in others
for (i, v) in pairs(other)
set!(d, i, v)
end
end
return d
end

if isdefined(Base, :mergewith) # Julia 1.5+
function Base.mergewith!(combiner::Callable, d::AbstractDictionary, d2::AbstractDictionary)
function Base.mergewith!(combiner, d::AbstractDictionary, d2::AbstractDictionary)
for (i, v) in pairs(d2)
(hasindex, token) = gettoken!(d, i)
if hasindex
Expand All @@ -342,6 +344,8 @@ if isdefined(Base, :mergewith) # Julia 1.5+
end
return d
end
Base.mergewith!(combiner, d::AbstractDictionary, others::AbstractDictionary...) =
foldl(mergewith!(combiner), others, init = d)
end

# TODO some kind of exclusive merge (throw on key clash like `insert!`)
Expand Down
28 changes: 22 additions & 6 deletions test/Dictionary.jl
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@
@test isequal(Dictionary(copy(keys(d)), d), d)
@test !isequal(d, empty(d))
@test !isequal(empty(d), d)
@test !isequal(fill(0, d), d)
@test !isequal(fill(0, d), d)
@test !isequal(d, fill(0, d))
@test !isequal(fill(0, copy(keys(d))), d)
@test !isequal(fill(0, copy(keys(d))), d)
@test !isequal(d, fill(0, copy(keys(d))))
@test !isless(d, d)
@test !isless(copy(d), d)
Expand Down Expand Up @@ -113,15 +113,15 @@
@test get!(d, 10, 14.0) == 14
@test d[10] == 14
delete!(d, 10)

for i = 2:2:1000
insert!(d, i, i+1)
@test d[i] == i + 1 ? true : (@show i; false)
end
@test all(in(i, d) == !iseven(i) for i in 2:2:1000)
@test all(in(i, keys(d)) == iseven(i) for i in 2:2:1000)
@test isempty(empty!(d))

@test get!(() -> 15, d, 10) == 15
@test get!(() -> 16, d, 10) == 15

Expand Down Expand Up @@ -159,7 +159,7 @@
dmut_ser = deserialize(open(path, "r"))
@test all(k -> haskey(dmut_ser, k), keys(dmut_ser))
end

d5 = Dictionary(['a','b'],[1,missing])
@test isdictequal(d5, d5) === missing
@test (d5 == d5) === missing
Expand All @@ -169,7 +169,7 @@

@test isequal(merge(d, d), d)
@test isequal(merge(d, d2), d)

@test isequal(merge(d, Dictionary([:c], [3])), Dictionary([:a, :b, :c], [1, 2, 3]))
@test isequal(merge(d, Dictionary([:b, :c], [4, 3])), Dictionary([:a, :b, :c], [1, 4, 3]))

Expand Down Expand Up @@ -377,4 +377,20 @@
@test dictcopy == Dictionary([3, 2, 1], ['a', 'b', 'c'])
@test all(isassigned(dictcopy, i) for i in keys(dictcopy))
end

@testset "merge" begin
d1 = Dictionary([1, 2, 3], [1, 2, 3])
d2 = Float32.(d1)
d3 = Float64.(d1) .+ 0.5
@test merge(d1, d2) isa Dictionary{Int, Float32}
@test merge(d1, d2, d3) isa Dictionary{Int, Float64}
@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
end
end
Loading