Skip to content

Commit

Permalink
Add mergewith[!](combine, dicts...) (#34296)
Browse files Browse the repository at this point in the history
  • Loading branch information
tkf authored and KristofferC committed Apr 11, 2020
1 parent dce58b3 commit 97a9cb6
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 15 deletions.
4 changes: 4 additions & 0 deletions NEWS.md
Expand Up @@ -43,6 +43,10 @@ Build system changes
New library functions
---------------------

* New functions `mergewith` and `mergewith!` supersede `merge` and `merge!` with `combine`
argument. They don't have the restriction for `combine` to be a `Function` and also
provide one-argument method that returns a closure. The old methods of `merge` and
`merge!` are still available for backward compatibility ([#34296]).
* The new `isdisjoint` function indicates whether two collections are disjoint ([#34427]).

New library features
Expand Down
51 changes: 43 additions & 8 deletions base/abstractdict.jl
Expand Up @@ -184,36 +184,52 @@ function merge!(d::AbstractDict, others::AbstractDict...)
end

"""
merge!(combine, d::AbstractDict, others::AbstractDict...)
mergewith!(combine, d::AbstractDict, others::AbstractDict...) -> d
mergewith!(combine)
merge!(combine, d::AbstractDict, others::AbstractDict...) -> d
Update collection with pairs from the other collections.
Values with the same key will be combined using the
combiner function.
combiner function. The curried form `mergewith!(combine)` returns the
function `(args...) -> mergewith!(combine, args...)`.
Method `merge!(combine::Union{Function,Type}, args...)` as an alias of
`mergewith!(combine, args...)` is still available for backward
compatibility.
!!! compat "Julia 1.5"
`mergewith!` requires Julia 1.5 or later.
# Examples
```jldoctest
julia> d1 = Dict(1 => 2, 3 => 4);
julia> d2 = Dict(1 => 4, 4 => 5);
julia> merge!(+, d1, d2);
julia> mergewith!(+, d1, d2);
julia> d1
Dict{Int64,Int64} with 3 entries:
4 => 5
3 => 4
1 => 6
julia> merge!(-, d1, d1);
julia> mergewith!(-, d1, d1);
julia> d1
Dict{Int64,Int64} with 3 entries:
4 => 0
3 => 0
1 => 0
julia> foldl(mergewith!(+), [d1, d2]; init=Dict{Int64,Int64}())
Dict{Int64,Int64} with 3 entries:
4 => 5
3 => 0
1 => 4
```
"""
function merge!(combine::Function, d::AbstractDict, others::AbstractDict...)
function mergewith!(combine, d::AbstractDict, others::AbstractDict...)
for other in others
for (k,v) in other
d[k] = haskey(d, k) ? combine(d[k], v) : v
Expand All @@ -222,6 +238,10 @@ function merge!(combine::Function, d::AbstractDict, others::AbstractDict...)
return d
end

mergewith!(combine) = (args...) -> mergewith!(combine, args...)

merge!(combine::Callable, args...) = mergewith!(combine, args...)

"""
keytype(type)
Expand Down Expand Up @@ -287,12 +307,21 @@ merge(d::AbstractDict, others::AbstractDict...) =
merge!(_typeddict(d, others...), others...)

"""
mergewith(combine, d::AbstractDict, others::AbstractDict...)
mergewith(combine)
merge(combine, d::AbstractDict, others::AbstractDict...)
Construct a merged collection from the given collections. If necessary, the
types of the resulting collection will be promoted to accommodate the types of
the merged collections. Values with the same key will be combined using the
combiner function.
combiner function. The curried form `mergewith(combine)` returns the function
`(args...) -> mergewith(combine, args...)`.
Method `merge(combine::Union{Function,Type}, args...)` as an alias of
`mergewith(combine, args...)` is still available for backward compatibility.
!!! compat "Julia 1.5"
`mergewith` requires Julia 1.5 or later.
# Examples
```jldoctest
Expand All @@ -306,14 +335,20 @@ Dict{String,Int64} with 2 entries:
"bar" => 4711
"baz" => 17
julia> merge(+, a, b)
julia> mergewith(+, a, b)
Dict{String,Float64} with 3 entries:
"bar" => 4753.0
"baz" => 17.0
"foo" => 0.0
julia> ans == mergewith(+)(a, b)
true
```
"""
merge(combine::Function, d::AbstractDict, others::AbstractDict...) =
mergewith(combine, d::AbstractDict, others::AbstractDict...) =
mergewith!(combine, _typeddict(d, others...), others...)
mergewith(combine) = (args...) -> mergewith(combine, args...)
merge(combine::Callable, d::AbstractDict, others::AbstractDict...) =
merge!(combine, _typeddict(d, others...), others...)

promoteK(K) = K
Expand Down
2 changes: 2 additions & 0 deletions base/exports.jl
Expand Up @@ -515,7 +515,9 @@ export
mapfoldr,
mapreduce,
merge!,
mergewith!,
merge,
mergewith,
pairs,
reduce,
setdiff!,
Expand Down
5 changes: 3 additions & 2 deletions doc/src/base/collections.md
Expand Up @@ -210,8 +210,9 @@ Base.keys
Base.values
Base.pairs
Base.merge
Base.merge!(::AbstractDict, ::AbstractDict...)
Base.merge!(::Function, ::AbstractDict, ::AbstractDict...)
Base.mergewith
Base.merge!
Base.mergewith!
Base.sizehint!
Base.keytype
Base.valtype
Expand Down
25 changes: 20 additions & 5 deletions test/dict.jl
Expand Up @@ -924,14 +924,22 @@ let
end
end

struct NonFunctionCallable end
(::NonFunctionCallable)(args...) = +(args...)

@testset "Dict merge" begin
d1 = Dict("A" => 1, "B" => 2)
d2 = Dict("B" => 3.0, "C" => 4.0)
@test @inferred merge(d1, d2) == Dict("A" => 1, "B" => 3, "C" => 4)
# merge with combiner function
@test @inferred mergewith(+, d1, d2) == Dict("A" => 1, "B" => 5, "C" => 4)
@test @inferred mergewith(*, d1, d2) == Dict("A" => 1, "B" => 6, "C" => 4)
@test @inferred mergewith(-, d1, d2) == Dict("A" => 1, "B" => -1, "C" => 4)
@test @inferred mergewith(NonFunctionCallable(), d1, d2) == Dict("A" => 1, "B" => 5, "C" => 4)
@test foldl(mergewith(+), [d1, d2]; init=Dict{Union{},Union{}}()) ==
Dict("A" => 1, "B" => 5, "C" => 4)
# backward compatibility
@test @inferred merge(+, d1, d2) == Dict("A" => 1, "B" => 5, "C" => 4)
@test @inferred merge(*, d1, d2) == Dict("A" => 1, "B" => 6, "C" => 4)
@test @inferred merge(-, d1, d2) == Dict("A" => 1, "B" => -1, "C" => 4)
end

@testset "Dict merge!" begin
Expand All @@ -940,12 +948,19 @@ end
@inferred merge!(d1, d2)
@test d1 == Dict("A" => 1, "B" => 3, "C" => 4)
# merge! with combiner function
@inferred merge!(+, d1, d2)
@inferred mergewith!(+, d1, d2)
@test d1 == Dict("A" => 1, "B" => 6, "C" => 8)
@inferred merge!(*, d1, d2)
@inferred mergewith!(*, d1, d2)
@test d1 == Dict("A" => 1, "B" => 18, "C" => 32)
@inferred merge!(-, d1, d2)
@inferred mergewith!(-, d1, d2)
@test d1 == Dict("A" => 1, "B" => 15, "C" => 28)
@inferred mergewith!(NonFunctionCallable(), d1, d2)
@test d1 == Dict("A" => 1, "B" => 18, "C" => 32)
@test foldl(mergewith!(+), [d1, d2]; init=empty(d1)) ==
Dict("A" => 1, "B" => 21, "C" => 36)
# backward compatibility
merge!(+, d1, d2)
@test d1 == Dict("A" => 1, "B" => 21, "C" => 36)
end

@testset "Dict reduce merge" begin
Expand Down

0 comments on commit 97a9cb6

Please sign in to comment.