diff --git a/base/reduce.jl b/base/reduce.jl index 61a0f466b2902..6582532be1dfa 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -345,8 +345,17 @@ reduce_empty(::typeof(+), ::Type{Bool}) = zero(Int) reduce_empty(::typeof(*), ::Type{Union{}}) = _empty_reduce_error(*, Union{}) reduce_empty(::typeof(*), ::Type{T}) where {T} = one(T) reduce_empty(::typeof(*), ::Type{<:AbstractChar}) = "" -reduce_empty(::typeof(&), ::Type{Bool}) = true -reduce_empty(::typeof(|), ::Type{Bool}) = false + +reduce_empty(::typeof(&), ::Type{Union{}}) = _empty_reduce_error(&, Union{}) +reduce_empty(::typeof(&), ::Type{T}) where T<:Integer = -1 % T +reduce_empty(::typeof(|), ::Type{Union{}}) = _empty_reduce_error(|, Union{}) +reduce_empty(::typeof(xor), ::Type{Union{}}) = _empty_reduce_error(xor, Union{}) +reduce_empty(::Union{typeof(|),typeof(xor)}, ::Type{T}) where T<:Integer = zero(T) + +reduce_empty(::typeof(min), ::Type{Union{}}) = _empty_reduce_error(min, Union{}) +reduce_empty(::typeof(min), ::Type{T}) where T = typemax(T) +reduce_empty(::typeof(max), ::Type{Union{}}) = _empty_reduce_error(max, Union{}) +reduce_empty(::typeof(max), ::Type{T}) where T = typemin(T) reduce_empty(::typeof(add_sum), ::Type{Union{}}) = _empty_reduce_error(add_sum, Union{}) reduce_empty(::typeof(add_sum), ::Type{T}) where {T} = reduce_empty(+, T) @@ -460,7 +469,7 @@ initial value `init` must be a neutral element for `op` that will be returned fo collections. It is unspecified whether `init` is used for non-empty collections. For empty collections, providing `init` will be necessary, except for some special cases -(e.g. when `op` is one of `+`, `*`, `max`, `min`, `&`, `|`) when Julia can determine the +(e.g. when `op` is one of `+`, `*`, `max`, `min`, `&`, `|`, `xor`) when Julia can determine the neutral element of `op`. Reductions for certain commonly-used operators may have special implementations, and @@ -739,7 +748,9 @@ Return 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. +for non-empty collections. If `init` is not given for an empty collection +with element type `T`, then `maximum` returns `typemin(T)`, which must +therefore be defined. !!! compat "Julia 1.6" Keyword argument `init` requires Julia 1.6 or later. @@ -752,12 +763,14 @@ julia> maximum(-20.5:10) julia> maximum([1,2,3]) 3 -julia> maximum(()) -ERROR: MethodError: reducing over an empty collection is not allowed; consider supplying `init` to the reducer -Stacktrace: +julia> maximum([]) +ERROR: MethodError: no method matching typemin(::Type{Any}) [...] -julia> maximum((); init=-Inf) +julia> maximum([]; init=-Inf) +-Inf + +julia> maximum(Float64[]) -Inf ``` """ @@ -771,7 +784,9 @@ Return 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. +for non-empty collections. If `init` is not given for an empty collection +with element type `T`, then `minimum` returns `typemax(T)`, which must +therefore be defined. !!! compat "Julia 1.6" Keyword argument `init` requires Julia 1.6 or later. @@ -785,12 +800,14 @@ julia> minimum([1,2,3]) 1 julia> minimum([]) -ERROR: MethodError: reducing over an empty collection is not allowed; consider supplying `init` to the reducer -Stacktrace: +ERROR: MethodError: no method matching typemax(::Type{Any}) [...] julia> minimum([]; init=Inf) Inf + +julia> minimum(Float64[]) +Inf ``` """ minimum(a; kw...) = mapreduce(identity, min, a; kw...) @@ -872,6 +889,9 @@ function _extrema_rf(x::NTuple{2,T}, y::NTuple{2,T}) where {T<:IEEEFloat} z1, z2 end +mapreduce_empty(f::ExtremaMap, ::typeof(_extrema_rf), T) = + (mapreduce_empty(f.f, min, T), mapreduce_empty(f.f, max, T)) + ## findmax, findmin, argmax & argmin """ diff --git a/test/reduce.jl b/test/reduce.jl index 4c05b179edcff..baf699fa31475 100644 --- a/test/reduce.jl +++ b/test/reduce.jl @@ -248,9 +248,9 @@ prod2(itr) = invoke(prod, Tuple{Any}, itr) # maximum & minimum & extrema -@test_throws "reducing over an empty" maximum(Int[]) -@test_throws "reducing over an empty" minimum(Int[]) -@test_throws "reducing over an empty" extrema(Int[]) +@test_throws MethodError maximum(Vector{Int}[]) +@test_throws MethodError minimum(String[]) +@test_throws MethodError extrema(String[]) @test maximum(Int[]; init=-1) == -1 @test minimum(Int[]; init=-1) == -1 @@ -626,8 +626,6 @@ test18695(r) = sum( t^2 for t in r ) # test neutral element not picked incorrectly for &, | @test @inferred(foldl(&, Int[1])) === 1 -@test_throws ["reducing over an empty", - "consider supplying `init`"] foldl(&, Int[]) # prod on Chars @test prod(Char[]) == "" @@ -705,3 +703,25 @@ let a = NamedTuple(Symbol(:x,i) => i for i in 1:33), b = (a...,) @test fold_alloc(a) == fold_alloc(b) == 0 end + +@testset "reduce over empty Integer lists with &" begin + for T in (Bool, Int8, UInt16, Int32, UInt64, Int128, BigInt) + x = reduce(&, T[]) + @test x isa T && x == ~zero(T) + end +end + +@testset "extrema of empty Real lists" begin + for T in (Bool, Int8, UInt16, Int32, UInt64, Int128, Float16, Float32, Float64, BigFloat) + x = extrema(T[]) + @test x isa Tuple{T,T} && x == (typemax(T), typemin(T)) + end +end + +@testset "reducing over empty tuples and vectors of type Union{}" begin + for f in (+, *, &, |, xor, min, max, Base.add_sum, Base.mul_prod) + s = "reducing with $f over an empty collection of element type Union{} is not allowed" + @test_throws s reduce(f, ()) + @test_throws s reduce(f, Union{}[]) + end +end diff --git a/test/reducedim.jl b/test/reducedim.jl index daa0a3fbe1f92..3751c33a3b3bd 100644 --- a/test/reducedim.jl +++ b/test/reducedim.jl @@ -182,9 +182,8 @@ end A = Matrix{Int}(undef, 0,1) @test sum(A) === 0 @test prod(A) === 1 - @test_throws ["reducing over an empty", - "consider supplying `init`"] minimum(A) - @test_throws "consider supplying `init`" maximum(A) + @test minimum(A) === typemax(Int) + @test maximum(A) === typemin(Int) @test isequal(sum(A, dims=1), zeros(Int, 1, 1)) @test isequal(sum(A, dims=2), zeros(Int, 0, 1))