diff --git a/NEWS.md b/NEWS.md index 723819f15cbd4..4876f87c01513 100644 --- a/NEWS.md +++ b/NEWS.md @@ -31,6 +31,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 diff --git a/base/abstractdict.jl b/base/abstractdict.jl index 94fb94de2315e..92d2ff06aeec8 100644 --- a/base/abstractdict.jl +++ b/base/abstractdict.jl @@ -184,11 +184,21 @@ 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 @@ -196,7 +206,7 @@ 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: @@ -204,16 +214,22 @@ Dict{Int64,Int64} with 3 entries: 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 @@ -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) @@ -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 @@ -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 diff --git a/base/exports.jl b/base/exports.jl index edc1dcaff8713..ff541ca5404c0 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -515,7 +515,9 @@ export mapfoldr, mapreduce, merge!, + mergewith!, merge, + mergewith, pairs, reduce, setdiff!, diff --git a/doc/src/base/collections.md b/doc/src/base/collections.md index 123a797d41133..fae621b302871 100644 --- a/doc/src/base/collections.md +++ b/doc/src/base/collections.md @@ -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 diff --git a/test/dict.jl b/test/dict.jl index ff3bac0124ce5..eaf258a1f04a2 100644 --- a/test/dict.jl +++ b/test/dict.jl @@ -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 @@ -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