From 97a9cb60fd1290b357dcb50edb1802783f6ce916 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Tue, 28 Jan 2020 13:47:51 -0800 Subject: [PATCH] Add mergewith[!](combine, dicts...) (#34296) --- NEWS.md | 4 +++ base/abstractdict.jl | 51 +++++++++++++++++++++++++++++++------ base/exports.jl | 2 ++ doc/src/base/collections.md | 5 ++-- test/dict.jl | 25 ++++++++++++++---- 5 files changed, 72 insertions(+), 15 deletions(-) diff --git a/NEWS.md b/NEWS.md index c219828398cca..2621c549c2c12 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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 diff --git a/base/abstractdict.jl b/base/abstractdict.jl index cc372fb4b3c32..14c39417607d2 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