Skip to content

Commit

Permalink
Support init keyword in sum/prod/maximum/minimum (#36188)
Browse files Browse the repository at this point in the history
Co-authored-by: Tim Holy <tim.holy@gmail.com>
Co-authored-by: Milan Bouchet-Valat <nalimilan@club.fr>
  • Loading branch information
3 people committed Jun 11, 2020
1 parent 4c16ccb commit 70d8497
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 38 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Standard library changes
* The function `isapprox(x,y)` now accepts the `norm` keyword argument also for numeric (i.e., non-array) arguments `x` and `y` ([#35883]).
* `view`, `@view`, and `@views` now work on `AbstractString`s, returning a `SubString` when appropriate ([#35879]).
* All `AbstractUnitRange{<:Integer}`s now work with `SubString`, `view`, `@view` and `@views` on strings ([#35879]).
* `sum`, `prod`, `maximum`, and `minimum` now support `init` keyword argument ([#36188], [#35839]).

#### LinearAlgebra
* New method `LinearAlgebra.issuccess(::CholeskyPivoted)` for checking whether pivoted Cholesky factorization was successful ([#36002]).
Expand Down
139 changes: 117 additions & 22 deletions base/reduce.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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])
Expand Down Expand Up @@ -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)


"""
Expand Down Expand Up @@ -462,14 +462,21 @@ reduce(op, a::Number) = a # Do we want this?
## sum

"""
sum(f, itr)
sum(f, itr; [init])
Sum the results of calling function `f` on each element of `itr`.
The return type is `Int` for signed integers of less than system word size, and
`UInt` for unsigned integers of less than system word size. For all other
arguments, a common return type is found to which all arguments are promoted.
The value returned for empty `itr` can be specified by `init`. It must be
the additive identity (i.e. zero) as it is unspecified whether `init` is used
for non-empty collections.
!!! compat "Julia 1.6"
Keyword argument `init` requires Julia 1.6 or later.
# Examples
```jldoctest
julia> sum(abs2, [2; 3; 4])
Expand All @@ -491,60 +498,88 @@ In the former case, the integers are widened to system word size and therefore
the result is 128. In the latter case, no such widening happens and integer
overflow results in -128.
"""
sum(f, a) = mapreduce(f, add_sum, a)
sum(f, a; kw...) = mapreduce(f, add_sum, a; kw...)

"""
sum(itr)
sum(itr; [init])
Returns the sum of all elements in a collection.
The return type is `Int` for signed integers of less than system word size, and
`UInt` for unsigned integers of less than system word size. For all other
arguments, a common return type is found to which all arguments are promoted.
The value returned for empty `itr` can be specified by `init`. It must be
the additive identity (i.e. zero) as it is unspecified whether `init` is used
for non-empty collections.
!!! compat "Julia 1.6"
Keyword argument `init` requires Julia 1.6 or later.
# Examples
```jldoctest
julia> sum(1:20)
210
julia> sum(1:20; init = 0.0)
210.0
```
"""
sum(a) = sum(identity, a)
sum(a::AbstractArray{Bool}) = count(a)
sum(a; kw...) = sum(identity, a; kw...)
sum(a::AbstractArray{Bool}; kw...) =
kw.data === NamedTuple() ? count(a) : reduce(add_sum, a; kw...)

## prod
"""
prod(f, itr)
prod(f, itr; [init])
Returns the product of `f` applied to each element of `itr`.
The return type is `Int` for signed integers of less than system word size, and
`UInt` for unsigned integers of less than system word size. For all other
arguments, a common return type is found to which all arguments are promoted.
The value returned for empty `itr` can be specified by `init`. It must be the
multiplicative identity (i.e. one) as it is unspecified whether `init` is used
for non-empty collections.
!!! compat "Julia 1.6"
Keyword argument `init` requires Julia 1.6 or later.
# Examples
```jldoctest
julia> prod(abs2, [2; 3; 4])
576
```
"""
prod(f, a) = mapreduce(f, mul_prod, a)
prod(f, a; kw...) = mapreduce(f, mul_prod, a; kw...)

"""
prod(itr)
prod(itr; [init])
Returns the product of all elements of a collection.
The return type is `Int` for signed integers of less than system word size, and
`UInt` for unsigned integers of less than system word size. For all other
arguments, a common return type is found to which all arguments are promoted.
The value returned for empty `itr` can be specified by `init`. It must be the
multiplicative identity (i.e. one) as it is unspecified whether `init` is used
for non-empty collections.
!!! compat "Julia 1.6"
Keyword argument `init` requires Julia 1.6 or later.
# Examples
```jldoctest
julia> prod(1:20)
2432902008176640000
julia> prod(1:5)
120
julia> prod(1:5; init = 1.0)
120.0
```
"""
prod(a) = mapreduce(identity, mul_prod, a)
prod(a; kw...) = mapreduce(identity, mul_prod, a; kw...)

## maximum & minimum
_fast(::typeof(min),x,y) = min(x,y)
Expand Down Expand Up @@ -610,62 +645,122 @@ 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`.
The value returned for empty `itr` can be specified by `init`. It must be
a neutral element for `max` (i.e. which is less than or equal to any
other element) as it is unspecified whether `init` is used
for non-empty collections.
!!! compat "Julia 1.6"
Keyword argument `init` requires Julia 1.6 or later.
# Examples
```jldoctest
julia> maximum(length, ["Julion", "Julia", "Jule"])
6
julia> maximum(length, []; init=-1)
-1
julia> maximum(sin, Real[]; init=-1.0) # good, since output of sin is >= -1
-1.0
```
"""
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`.
The value returned for empty `itr` can be specified by `init`. It must be
a neutral element for `min` (i.e. which is greater than or equal to any
other element) as it is unspecified whether `init` is used
for non-empty collections.
!!! compat "Julia 1.6"
Keyword argument `init` requires Julia 1.6 or later.
# Examples
```jldoctest
julia> minimum(length, ["Julion", "Julia", "Jule"])
4
julia> minimum(length, []; init=typemax(Int64))
9223372036854775807
julia> minimum(sin, Real[]; init=1.0) # good, since output of sin is <= 1
1.0
```
"""
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.
The value returned for empty `itr` can be specified by `init`. It must be
a neutral element for `max` (i.e. which is less than or equal to any
other element) as it is unspecified whether `init` is used
for non-empty collections.
!!! compat "Julia 1.6"
Keyword argument `init` requires Julia 1.6 or later.
# Examples
```jldoctest
julia> maximum(-20.5:10)
9.5
julia> maximum([1,2,3])
3
julia> maximum(())
ERROR: ArgumentError: reducing over an empty collection is not allowed
Stacktrace:
[...]
julia> maximum((); init=-Inf)
-Inf
```
"""
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.
The value returned for empty `itr` can be specified by `init`. It must be
a neutral element for `min` (i.e. which is greater than or equal to any
other element) as it is unspecified whether `init` is used
for non-empty collections.
!!! compat "Julia 1.6"
Keyword argument `init` requires Julia 1.6 or later.
# Examples
```jldoctest
julia> minimum(-20.5:10)
-20.5
julia> minimum([1,2,3])
1
julia> minimum([])
ERROR: ArgumentError: reducing over an empty collection is not allowed
Stacktrace:
[...]
julia> minimum([]; init=Inf)
Inf
```
"""
minimum(a) = mapreduce(identity, min, a)
minimum(a; kw...) = mapreduce(identity, min, a; kw...)

## all & any

Expand Down
28 changes: 14 additions & 14 deletions base/reducedim.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)

"""
Expand Down Expand Up @@ -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

Expand All @@ -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

Expand Down
6 changes: 4 additions & 2 deletions base/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1221,10 +1221,12 @@ See also [`applicable`](@ref).
julia> hasmethod(length, Tuple{Array})
true
julia> hasmethod(sum, Tuple{Function, Array}, (:dims,))
julia> f(; oranges=0) = oranges;
julia> hasmethod(f, Tuple{}, (:oranges,))
true
julia> hasmethod(sum, Tuple{Function, Array}, (:apples, :bananas))
julia> hasmethod(f, Tuple{}, (:apples, :bananas))
false
julia> g(; xs...) = 4;
Expand Down
Loading

0 comments on commit 70d8497

Please sign in to comment.