Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ New library features
Standard library changes
------------------------

* Empty dimensional reductions (e.g., `reduce` and `mapreduce` with the `dims` keyword
selecting one or more dimensions) now behave like their whole-array (`dims=:`) counterparts,
only returning values in unambiguous cases and erroring otherwise.

#### JuliaSyntaxHighlighting

#### LinearAlgebra
Expand Down
14 changes: 5 additions & 9 deletions base/reducedim.jl
Original file line number Diff line number Diff line change
Expand Up @@ -341,8 +341,10 @@ _mapreduce_dim(f, op, ::_InitialValue, A::AbstractArrayOrBroadcasted, ::Colon) =
_mapreduce_dim(f, op, nt, A::AbstractArrayOrBroadcasted, dims) =
mapreducedim!(f, op, reducedim_initarray(A, dims, nt), A)

_mapreduce_dim(f, op, ::_InitialValue, A::AbstractArrayOrBroadcasted, dims) =
function _mapreduce_dim(f, op, ::_InitialValue, A::AbstractArrayOrBroadcasted, dims)
isempty(A) && return fill(mapreduce_empty(f, op, eltype(A)), reduced_indices(A, dims))
mapreducedim!(f, op, reducedim_init(f, op, A, dims), A)
end

"""
reduce(f, A::AbstractArray; dims=:, [init])
Expand Down Expand Up @@ -1128,10 +1130,7 @@ findmin(f, A::AbstractArray; dims=:) = _findmin(f, A, dims)
function _findmin(f, A, region)
ri = reduced_indices0(A, region)
if isempty(A)
if prod(map(length, reduced_indices(A, region))) != 0
throw(ArgumentError("collection slices must be non-empty"))
end
similar(A, promote_op(f, eltype(A)), ri), zeros(eltype(keys(A)), ri)
_empty_reduce_error()
else
fA = f(first(A))
findminmax!(f, isgreater, fill!(similar(A, _findminmax_inittype(f, A), ri), fA),
Expand Down Expand Up @@ -1201,10 +1200,7 @@ findmax(f, A::AbstractArray; dims=:) = _findmax(f, A, dims)
function _findmax(f, A, region)
ri = reduced_indices0(A, region)
if isempty(A)
if prod(map(length, reduced_indices(A, region))) != 0
throw(ArgumentError("collection slices must be non-empty"))
end
similar(A, promote_op(f, eltype(A)), ri), zeros(eltype(keys(A)), ri)
_empty_reduce_error()
else
fA = f(first(A))
findminmax!(f, isless, fill!(similar(A, _findminmax_inittype(f, A), ri), fA),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dd827de0b31d4cc588c876fec4aaf61d
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cb912fd9142b6c40889c93026146cfc9b3425f1f895b3e23f5454b2f49582e1c91305080381047b4e6164738f47138582a8775b98e6ae0c014da8e70062dc467
8 changes: 4 additions & 4 deletions stdlib/SparseArrays.version
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
SPARSEARRAYS_BRANCH = main
SPARSEARRAYS_SHA1 = 30201abcb41e558a4b5cc23b11dc5676c5655c0b
SPARSEARRAYS_GIT_URL := https://github.com/JuliaSparse/SparseArrays.jl.git
SPARSEARRAYS_TAR_URL = https://api.github.com/repos/JuliaSparse/SparseArrays.jl/tarball/$1
SPARSEARRAYS_BRANCH = mb/mapreducedim_empty
SPARSEARRAYS_SHA1 = e7c66f8bbfecbb7adf581ea7c95171e97f49a294
SPARSEARRAYS_GIT_URL := https://github.com/mbauman/SparseArrays.jl.git
SPARSEARRAYS_TAR_URL = https://api.github.com/repos/mbauman/SparseArrays.jl/tarball/$1
74 changes: 60 additions & 14 deletions test/reducedim.jl
Original file line number Diff line number Diff line change
Expand Up @@ -207,23 +207,34 @@ end
@test isequal(prod(A, dims=(1, 2)), fill(1, 1, 1))
@test isequal(prod(A, dims=3), fill(1, 0, 1))

for f in (minimum, maximum)
for f in (minimum, maximum, findmin, findmax)
@test_throws "reducing over an empty collection is not allowed" f(A, dims=1)
@test isequal(f(A, dims=2), zeros(Int, 0, 1))
@test_throws "reducing over an empty collection is not allowed" f(A, dims=2)
@test_throws "reducing over an empty collection is not allowed" f(A, dims=(1, 2))
@test isequal(f(A, dims=3), zeros(Int, 0, 1))
end
for f in (findmin, findmax)
@test_throws ArgumentError f(A, dims=1)
@test isequal(f(A, dims=2), (zeros(Int, 0, 1), zeros(Int, 0, 1)))
@test_throws ArgumentError f(A, dims=(1, 2))
@test isequal(f(A, dims=3), (zeros(Int, 0, 1), zeros(Int, 0, 1)))
@test_throws ArgumentError f(abs2, A, dims=1)
@test isequal(f(abs2, A, dims=2), (zeros(Int, 0, 1), zeros(Int, 0, 1)))
@test_throws ArgumentError f(abs2, A, dims=(1, 2))
@test isequal(f(abs2, A, dims=3), (zeros(Int, 0, 1), zeros(Int, 0, 1)))
@test_throws "reducing over an empty collection is not allowed" f(A, dims=3)
if f === maximum
# maximum allows some empty reductions with abs/abs2
z = f(abs, A)
@test isequal(f(abs, A, dims=1), fill(z, (1,1)))
@test isequal(f(abs, A, dims=2), fill(z, (0,1)))
@test isequal(f(abs, A, dims=(1,2)), fill(z, (1,1)))
@test isequal(f(abs, A, dims=3), fill(z, (0,1)))
z = f(abs2, A)
@test isequal(f(abs2, A, dims=1), fill(z, (1,1)))
@test isequal(f(abs2, A, dims=2), fill(z, (0,1)))
@test isequal(f(abs2, A, dims=(1,2)), fill(z, (1,1)))
@test isequal(f(abs2, A, dims=3), fill(z, (0,1)))
else
@test_throws "reducing over an empty collection is not allowed" f(abs, A, dims=1)
@test_throws "reducing over an empty collection is not allowed" f(abs, A, dims=2)
@test_throws "reducing over an empty collection is not allowed" f(abs, A, dims=(1, 2))
@test_throws "reducing over an empty collection is not allowed" f(abs, A, dims=3)
@test_throws "reducing over an empty collection is not allowed" f(abs2, A, dims=1)
@test_throws "reducing over an empty collection is not allowed" f(abs2, A, dims=2)
@test_throws "reducing over an empty collection is not allowed" f(abs2, A, dims=(1, 2))
@test_throws "reducing over an empty collection is not allowed" f(abs2, A, dims=3)
end
end

end

## findmin/findmax/minimum/maximum
Expand Down Expand Up @@ -720,3 +731,38 @@ end
@test_broken @inferred(maximum(exp, A; dims = 1))[1] === missing
@test_broken @inferred(extrema(exp, A; dims = 1))[1] === (missing, missing)
end

some_exception(op) = try return (Some(op()), nothing); catch ex; return (nothing, ex); end
reduced_shape(sz, dims) = ntuple(d -> d in dims ? 1 : sz[d], length(sz))

@testset "Ensure that calling, e.g., sum(empty; dims) has the same behavior as sum(empty)" begin
@testset "$r(Array{$T}(undef, $sz); dims=$dims)" for
r in (minimum, maximum, findmin, findmax, extrema, sum, prod, all, any, count),
T in (Int, Union{Missing, Int}, Number, Union{Missing, Number}, Bool, Union{Missing, Bool}, Any),
sz in ((0,), (0,1), (1,0), (0,0), (0,0,1), (1,0,1)),
dims in (1, 2, 3, 4, (1,2), (1,3), (2,3,4), (1,2,3))

A = Array{T}(undef, sz)
rsz = reduced_shape(sz, dims)

v, ex = some_exception() do; r(A); end
if isnothing(v)
@test_throws typeof(ex) r(A; dims)
else
actual = fill(something(v), rsz)
@test isequal(r(A; dims), actual)
@test eltype(r(A; dims)) === eltype(actual)
end

for f in (identity, abs, abs2)
v, ex = some_exception() do; r(f, A); end
if isnothing(v)
@test_throws typeof(ex) r(f, A; dims)
else
actual = fill(something(v), rsz)
@test isequal(r(f, A; dims), actual)
@test eltype(r(f, A; dims)) === eltype(actual)
end
end
end
end