From ed1cfc91779a6bd158523214bccd210fc89496d1 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 11 May 2020 06:25:35 -0500 Subject: [PATCH] Support `init` keyword in `maximum`/`minimum` --- base/reduce.jl | 52 +++++++++++++++++++++++++++++++++++++---------- base/reducedim.jl | 28 ++++++++++++------------- test/reduce.jl | 3 +++ test/reducedim.jl | 5 +++++ 4 files changed, 63 insertions(+), 25 deletions(-) diff --git a/base/reduce.jl b/base/reduce.jl index 414bac5099f78..5c3fe4c2e1ff3 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -45,7 +45,7 @@ function mapfoldl_impl(f::F, op::OP, nt, itr) where {F,OP} end function foldl_impl(op::OP, nt, itr) where {OP} - v = _foldl_impl(op, get(nt, :init, _InitialValue()), itr) + v = _foldl_impl(op, nt, itr) v isa _InitialValue && return reduce_empty_iter(op, itr) return v end @@ -157,7 +157,7 @@ Like [`mapreduce`](@ref), but with guaranteed left associativity, as in [`foldl` If provided, the keyword argument `init` will be used exactly once. In general, it will be necessary to provide `init` to work with empty collections. """ -mapfoldl(f, op, itr; kw...) = mapfoldl_impl(f, op, kw.data, itr) +mapfoldl(f, op, itr; init=_InitialValue()) = mapfoldl_impl(f, op, init, itr) """ foldl(op, itr; [init]) @@ -200,7 +200,7 @@ Like [`mapreduce`](@ref), but with guaranteed right associativity, as in [`foldr provided, the keyword argument `init` will be used exactly once. In general, it will be necessary to provide `init` to work with empty collections. """ -mapfoldr(f, op, itr; kw...) = mapfoldr_impl(f, op, kw.data, itr) +mapfoldr(f, op, itr; init=_InitialValue()) = mapfoldr_impl(f, op, init, itr) """ @@ -606,35 +606,47 @@ function mapreduce_impl(f, op::Union{typeof(max), typeof(min)}, end """ - maximum(f, itr) + maximum(f, itr; [init]) Returns the largest result of calling function `f` on each element of `itr`. +If provided, `init` must be a neutral element for `max` that will be returned +for empty collections. # Examples ```jldoctest julia> maximum(length, ["Julion", "Julia", "Jule"]) 6 + +julia> maximum(length, []; init=-1) +-1 ``` """ -maximum(f, a) = mapreduce(f, max, a) +maximum(f, a; kw...) = mapreduce(f, max, a; kw...) """ - minimum(f, itr) + minimum(f, itr; [init]) Returns the smallest result of calling function `f` on each element of `itr`. +If provided, `init` must be a neutral element for `min` that will be returned +for empty collections. # Examples ```jldoctest julia> minimum(length, ["Julion", "Julia", "Jule"]) 4 + +julia> minimum(length, []; init=-1) +-1 ``` """ -minimum(f, a) = mapreduce(f, min, a) +minimum(f, a; kw...) = mapreduce(f, min, a; kw...) """ - maximum(itr) + maximum(itr; [init]) Returns the largest element in a collection. +If provided, `init` must be a neutral element for `max` that will be returned +for empty collections. # Examples ```jldoctest @@ -643,14 +655,24 @@ julia> maximum(-20.5:10) julia> maximum([1,2,3]) 3 + +julia> maximum(()) +ERROR: ArgumentError: reducing over an empty collection is not allowed +Stacktrace: +[...] + +julia> maximum((); init=-1) +-1 ``` """ -maximum(a) = mapreduce(identity, max, a) +maximum(a; kw...) = mapreduce(identity, max, a; kw...) """ - minimum(itr) + minimum(itr; [init]) Returns the smallest element in a collection. +If provided, `init` must be a neutral element for `min` that will be returned +for empty collections. # Examples ```jldoctest @@ -659,9 +681,17 @@ julia> minimum(-20.5:10) julia> minimum([1,2,3]) 1 + +julia> minimum([]) +ERROR: ArgumentError: reducing over an empty collection is not allowed +Stacktrace: +[...] + +julia> minimum([]; init=-1) +-1 ``` """ -minimum(a) = mapreduce(identity, min, a) +minimum(a; kw...) = mapreduce(identity, min, a; kw...) ## all & any diff --git a/base/reducedim.jl b/base/reducedim.jl index d1e5001492fc4..140e004cc5457 100644 --- a/base/reducedim.jl +++ b/base/reducedim.jl @@ -307,21 +307,21 @@ julia> mapreduce(isodd, |, a, dims=1) 1 1 1 1 ``` """ -mapreduce(f, op, A::AbstractArrayOrBroadcasted; dims=:, kw...) = - _mapreduce_dim(f, op, kw.data, A, dims) +mapreduce(f, op, A::AbstractArrayOrBroadcasted; dims=:, init=_InitialValue()) = + _mapreduce_dim(f, op, init, A, dims) mapreduce(f, op, A::AbstractArrayOrBroadcasted...; kw...) = reduce(op, map(f, A...); kw...) -_mapreduce_dim(f, op, nt::NamedTuple{(:init,)}, A::AbstractArrayOrBroadcasted, ::Colon) = - mapfoldl(f, op, A; nt...) +_mapreduce_dim(f, op, nt, A::AbstractArrayOrBroadcasted, ::Colon) = + mapfoldl_impl(f, op, nt, A) -_mapreduce_dim(f, op, ::NamedTuple{()}, A::AbstractArrayOrBroadcasted, ::Colon) = +_mapreduce_dim(f, op, ::_InitialValue, A::AbstractArrayOrBroadcasted, ::Colon) = _mapreduce(f, op, IndexStyle(A), A) -_mapreduce_dim(f, op, nt::NamedTuple{(:init,)}, A::AbstractArrayOrBroadcasted, dims) = - mapreducedim!(f, op, reducedim_initarray(A, dims, nt.init), A) +_mapreduce_dim(f, op, nt, A::AbstractArrayOrBroadcasted, dims) = + mapreducedim!(f, op, reducedim_initarray(A, dims, nt), A) -_mapreduce_dim(f, op, ::NamedTuple{()}, A::AbstractArrayOrBroadcasted, dims) = +_mapreduce_dim(f, op, ::_InitialValue, A::AbstractArrayOrBroadcasted, dims) = mapreducedim!(f, op, reducedim_init(f, op, A, dims), A) """ @@ -717,12 +717,12 @@ for (fname, _fname, op) in [(:sum, :_sum, :add_sum), (:prod, :_prod, (:maximum, :_maximum, :max), (:minimum, :_minimum, :min)] @eval begin # User-facing methods with keyword arguments - @inline ($fname)(a::AbstractArray; dims=:) = ($_fname)(a, dims) - @inline ($fname)(f, a::AbstractArray; dims=:) = ($_fname)(f, a, dims) + @inline ($fname)(a::AbstractArray; dims=:, kw...) = ($_fname)(a, dims; kw...) + @inline ($fname)(f, a::AbstractArray; dims=:, kw...) = ($_fname)(f, a, dims; kw...) # Underlying implementations using dispatch - ($_fname)(a, ::Colon) = ($_fname)(identity, a, :) - ($_fname)(f, a, ::Colon) = mapreduce(f, $op, a) + ($_fname)(a, ::Colon; kw...) = ($_fname)(identity, a, :; kw...) + ($_fname)(f, a, ::Colon; kw...) = mapreduce(f, $op, a; kw...) end end @@ -743,8 +743,8 @@ for (fname, op) in [(:sum, :add_sum), (:prod, :mul_prod), mapreducedim!(f, $(op), initarray!(r, $(op), init, A), A) $(fname!)(r::AbstractArray, A::AbstractArray; init::Bool=true) = $(fname!)(identity, r, A; init=init) - $(_fname)(A, dims) = $(_fname)(identity, A, dims) - $(_fname)(f, A, dims) = mapreduce(f, $(op), A, dims=dims) + $(_fname)(A, dims; kw...) = $(_fname)(identity, A, dims; kw...) + $(_fname)(f, A, dims; kw...) = mapreduce(f, $(op), A; dims=dims, kw...) end end diff --git a/test/reduce.jl b/test/reduce.jl index 69b8b1911e7ea..6c787bec82ac7 100644 --- a/test/reduce.jl +++ b/test/reduce.jl @@ -208,6 +208,9 @@ prod2(itr) = invoke(prod, Tuple{Any}, itr) @test_throws ArgumentError maximum(Int[]) @test_throws ArgumentError minimum(Int[]) +@test maximum(Int[]; init=-1) == -1 +@test minimum(Int[]; init=-1) == -1 + @test maximum(5) == 5 @test minimum(5) == 5 @test extrema(5) == (5, 5) diff --git a/test/reducedim.jl b/test/reducedim.jl index 3f59ae6e2570a..e51a075496f1c 100644 --- a/test/reducedim.jl +++ b/test/reducedim.jl @@ -75,6 +75,11 @@ safe_minabs(A::Array{T}, region) where {T} = safe_mapslices(minimum, abs.(A), re @test @inferred(count(!, Breduc, dims=region)) ≈ safe_count(.!Breduc, region) end +# Combining dims and init +A = Array{Int}(undef, 0, 3) +@test_throws ArgumentError maximum(A; dims=1) +@test maximum(A; dims=1, init=-1) == reshape([-1,-1,-1], 1, 3) + # Test reduction along first dimension; this is special-cased for # size(A, 1) >= 16 Breduc = rand(64, 3)