-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
make findmin/findmax behavior match min/max (fix #23209) #23227
Changes from 3 commits
b4113e7
172c9ef
915d719
519184b
faf4927
2ad37fe
829f0c5
57329bf
37d8bf3
6c64a20
d6cd550
78bbd14
6532ba8
be0f449
a52c2aa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -57,24 +57,33 @@ function reduced_indices0(inds::Indices{N}, region) where N | |
tuple(rinds...)::typeof(inds) | ||
end | ||
|
||
|
||
###### Generic reduction functions ##### | ||
|
||
## extensions to the standard functions for BigInt, String and others | ||
|
||
zero2(t::Type) = zero(t) | ||
zero2(::Type{T}) where T<:Number = zero(T) | ||
zero2(::Type{T}) where T<:AbstractString = "" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. requiring this is a bit of a red flag, is there any way to avoid needing this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The way is not to rely on zero behavior at all, but to use the first slice as the start value. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let me improve this! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The definition of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As long as this isn't exported, it's not a feature addition. It just seems fishy to need a function like zero but for which |
||
|
||
## initialization | ||
|
||
for (Op, initfun) in ((:(typeof(+)), :zero), (:(typeof(*)), :one), (:(typeof(scalarmax)), :typemin), (:(typeof(scalarmin)), :typemax), (:(typeof(max)), :typemin), (:(typeof(min)), :typemax)) | ||
@eval initarray!(a::AbstractArray{T}, ::$(Op), init::Bool) where {T} = (init && fill!(a, $(initfun)(T)); a) | ||
for (Op, initfun) in ((:(typeof(+)), :zero), (:(typeof(*)), :one)) | ||
@eval initarray!(a::AbstractArray{T}, ::$(Op), init::Bool, src::AbstractArray) where {T} = (init && fill!(a, $(initfun)(T)); a) | ||
end | ||
|
||
for Op in (:(typeof(scalarmax)), :(typeof(scalarmin)), :(typeof(max)), :(typeof(min))) | ||
@eval initarray!(a::AbstractArray{T}, ::$(Op), init::Bool, src::AbstractArray) where {T} = (init && copyfirst!(a, src); a) | ||
end | ||
|
||
for (Op, initval) in ((:(typeof(&)), true), (:(typeof(|)), false)) | ||
@eval initarray!(a::AbstractArray, ::$(Op), init::Bool) = (init && fill!(a, $initval); a) | ||
@eval initarray!(a::AbstractArray, ::$(Op), init::Bool, src::AbstractArray) = (init && fill!(a, $initval); a) | ||
end | ||
|
||
reducedim_initarray(A::AbstractArray, region, v0, ::Type{R}) where {R} = fill!(similar(A,R,reduced_indices(A,region)), v0) | ||
reducedim_initarray(A::AbstractArray, region, v0::T) where {T} = reducedim_initarray(A, region, v0, T) | ||
|
||
reducedim_initarray0(A::AbstractArray, region, v0, ::Type{R}) where {R} = fill!(similar(A,R,reduced_indices0(A,region)), v0) | ||
reducedim_initarray0(A::AbstractArray, region, v0::T) where {T} = reducedim_initarray0(A, region, v0, T) | ||
reducedim_initarray0(A::AbstractArray, region, f) = f.(getindex(A, reduced_indices0(A, region)...)) | ||
reducedim_initarray0(A::AbstractArray, region, ::typeof(identity)) = getindex(A, reduced_indices0(A, region)...) | ||
|
||
# TODO: better way to handle reducedim initialization | ||
# | ||
|
@@ -91,7 +100,7 @@ function reducedim_init(f, op::typeof(*), A::AbstractArray, region) | |
end | ||
function _reducedim_init(f, op, fv, fop, A, region) | ||
T = promote_union(eltype(A)) | ||
if method_exists(zero, Tuple{Type{T}}) | ||
if applicable(zero, T) | ||
x = f(zero(T)) | ||
z = op(fv(x), fv(x)) | ||
Tr = typeof(z) == typeof(x) && !isbits(T) ? T : typeof(z) | ||
|
@@ -106,8 +115,8 @@ reducedim_init(f, op::typeof(max), A::AbstractArray, region) = reducedim_init(f, | |
reducedim_init(f, op::typeof(min), A::AbstractArray, region) = reducedim_init(f, scalarmin, A, region) | ||
reducedim_init(f::Union{typeof(abs),typeof(abs2)}, op::typeof(max), A::AbstractArray, region) = reducedim_init(f, scalarmax, A, region) | ||
|
||
reducedim_init(f, op::typeof(scalarmax), A::AbstractArray{T}, region) where {T} = reducedim_initarray0(A, region, typemin(f(zero(T)))) | ||
reducedim_init(f, op::typeof(scalarmin), A::AbstractArray{T}, region) where {T} = reducedim_initarray0(A, region, typemax(f(zero(T)))) | ||
reducedim_init(f, op::typeof(scalarmax), A::AbstractArray{T}, region) where {T} = reducedim_initarray0(A, region, f) | ||
reducedim_init(f, op::typeof(scalarmin), A::AbstractArray{T}, region) where {T} = reducedim_initarray0(A, region, f) | ||
reducedim_init(f::Union{typeof(abs),typeof(abs2)}, op::typeof(scalarmax), A::AbstractArray{T}, region) where {T} = | ||
reducedim_initarray(A, region, zero(f(zero(T)))) | ||
|
||
|
@@ -132,7 +141,6 @@ end | |
reducedim_init(f::Union{typeof(identity),typeof(abs),typeof(abs2)}, op::typeof(+), A::AbstractArray{Bool}, region) = | ||
reducedim_initarray(A, region, 0) | ||
|
||
|
||
## generic (map)reduction | ||
|
||
has_fast_linear_indexing(a::AbstractArray) = false | ||
|
@@ -169,6 +177,20 @@ function check_reducedims(R, A) | |
return lsiz | ||
end | ||
|
||
""" | ||
Extract first entry of slices of array A into existing array R. | ||
""" | ||
function copyfirst!(R::AbstractArray, A::AbstractArray) | ||
lsiz = check_reducedims(R, A) | ||
iA = collect(map(length, indices(A))) | ||
iR = map!(length, ones(ndims(A)), collect(indices(R))) | ||
t = [] | ||
for i in 1:length(iR) | ||
push!(t, iA[i] == iR[i] ? Colon() : 1) | ||
end | ||
copy!(R, view(A, t...)) | ||
end | ||
|
||
function _mapreducedim!(f, op, R::AbstractArray, A::AbstractArray) | ||
lsiz = check_reducedims(R,A) | ||
isempty(A) && return R | ||
|
@@ -211,7 +233,7 @@ mapreducedim!(f, op, R::AbstractArray, A::AbstractArray) = | |
(_mapreducedim!(f, op, R, A); R) | ||
|
||
reducedim!(op, R::AbstractArray{RT}, A::AbstractArray) where {RT} = | ||
mapreducedim!(identity, op, R, A, zero(RT)) | ||
mapreducedim!(identity, op, R, A, zero2(RT)) | ||
|
||
""" | ||
mapreducedim(f, op, A, region[, v0]) | ||
|
@@ -277,7 +299,6 @@ julia> reducedim(max, a, 1) | |
reducedim(op, A::AbstractArray, region, v0) = mapreducedim(identity, op, A, region, v0) | ||
reducedim(op, A::AbstractArray, region) = mapreducedim(identity, op, A, region) | ||
|
||
|
||
##### Specific reduction functions ##### | ||
""" | ||
sum(A, dims) | ||
|
@@ -577,7 +598,7 @@ for (fname, op) in [(:sum, :+), (:prod, :*), | |
fname! = Symbol(fname, '!') | ||
@eval begin | ||
$(fname!)(f::Function, r::AbstractArray, A::AbstractArray; init::Bool=true) = | ||
mapreducedim!(f, $(op), initarray!(r, $(op), init), A) | ||
mapreducedim!(f, $(op), initarray!(r, $(op), init, A), A) | ||
$(fname!)(r::AbstractArray, A::AbstractArray; init::Bool=true) = $(fname!)(identity, r, A; init=init) | ||
|
||
$(fname)(f::Function, A::AbstractArray, region) = | ||
|
@@ -586,9 +607,9 @@ for (fname, op) in [(:sum, :+), (:prod, :*), | |
end | ||
end | ||
|
||
|
||
##### findmin & findmax ##### | ||
|
||
# The initial values of Rval are not used if the correponding indices in Rind are 0. | ||
# | ||
function findminmax!(f, Rval, Rind, A::AbstractArray{T,N}) where {T,N} | ||
(isempty(Rval) || isempty(A)) && return Rval, Rind | ||
lsiz = check_reducedims(Rval, A) | ||
|
@@ -609,7 +630,7 @@ function findminmax!(f, Rval, Rind, A::AbstractArray{T,N}) where {T,N} | |
for i in indices(A,1) | ||
k += 1 | ||
tmpAv = A[i,IA] | ||
if f(tmpAv, tmpRv) | ||
if tmpRi == 0 || (tmpRv == tmpRv && (tmpAv != tmpAv || f(tmpAv, tmpRv))) | ||
tmpRv = tmpAv | ||
tmpRi = k | ||
end | ||
|
@@ -623,7 +644,9 @@ function findminmax!(f, Rval, Rind, A::AbstractArray{T,N}) where {T,N} | |
for i in indices(A, 1) | ||
k += 1 | ||
tmpAv = A[i,IA] | ||
if f(tmpAv, Rval[i,IR]) | ||
tmpRv = Rval[i,IR] | ||
tmpRi = Rind[i,IR] | ||
if tmpRi == 0 || (tmpRv == tmpRv && (tmpAv != tmpAv || f(tmpAv, tmpRv))) | ||
Rval[i,IR] = tmpAv | ||
Rind[i,IR] = k | ||
end | ||
|
@@ -633,82 +656,87 @@ function findminmax!(f, Rval, Rind, A::AbstractArray{T,N}) where {T,N} | |
Rval, Rind | ||
end | ||
|
||
|
||
""" | ||
findmin!(rval, rind, A, [init=true]) -> (minval, index) | ||
|
||
Find the minimum of `A` and the corresponding linear index along singleton | ||
dimensions of `rval` and `rind`, and store the results in `rval` and `rind`. | ||
`NaN` is treated as less than all other values. | ||
""" | ||
function findmin!(rval::AbstractArray, rind::AbstractArray, A::AbstractArray; | ||
init::Bool=true) | ||
findminmax!(<, initarray!(rval, scalarmin, init), rind, A) | ||
findminmax!(isless, init && !isempty(A) ? fill!(rval, first(A)) : rval, fill!(rind,0), A) | ||
end | ||
|
||
""" | ||
findmin(A, region) -> (minval, index) | ||
|
||
For an array input, returns the value and index of the minimum over the given region. | ||
`NaN` is treated as less than all other values. | ||
|
||
# Examples | ||
```jldoctest | ||
julia> A = [1 2; 3 4] | ||
julia> A = [1.0 2; 3 4] | ||
2×2 Array{Int64,2}: | ||
1 2 | ||
3 4 | ||
1.0 2.0 | ||
3.0 4.0 | ||
|
||
julia> findmin(A, 1) | ||
([1 2], [1 3]) | ||
([1.0 2.0], [1 3]) | ||
|
||
julia> findmin(A, 2) | ||
([1; 3], [1; 2]) | ||
([1.0; 3.0], [1; 2]) | ||
``` | ||
""" | ||
function findmin(A::AbstractArray{T}, region) where T | ||
if isempty(A) | ||
return (similar(A, reduced_indices0(A, region)), | ||
similar(dims->zeros(Int, dims), reduced_indices0(A, region))) | ||
end | ||
return findminmax!(<, reducedim_initarray0(A, region, typemax(T)), | ||
return findminmax!(isless, fill!(similar(A, reduced_indices0(A, region)), first(A)), | ||
similar(dims->zeros(Int, dims), reduced_indices0(A, region)), A) | ||
end | ||
|
||
isgt(a, b) = isless(b,a) | ||
|
||
""" | ||
findmax!(rval, rind, A, [init=true]) -> (maxval, index) | ||
|
||
Find the maximum of `A` and the corresponding linear index along singleton | ||
dimensions of `rval` and `rind`, and store the results in `rval` and `rind`. | ||
`NaN` is treated as greater than all other values. | ||
""" | ||
function findmax!(rval::AbstractArray, rind::AbstractArray, A::AbstractArray; | ||
init::Bool=true) | ||
findminmax!(>, initarray!(rval, scalarmax, init), rind, A) | ||
findminmax!(isgt, init && !isempty(A) ? fill!(rval, first(A)) : rval, fill!(rind,0), A) | ||
end | ||
|
||
""" | ||
findmax(A, region) -> (maxval, index) | ||
|
||
For an array input, returns the value and index of the maximum over the given region. | ||
`NaN` is treated as greater than all other values. | ||
|
||
# Examples | ||
```jldoctest | ||
julia> A = [1 2; 3 4] | ||
julia> A = [1.0 2; 3 4] | ||
2×2 Array{Int64,2}: | ||
1 2 | ||
3 4 | ||
1.0 2.0 | ||
3.0 4.0 | ||
|
||
julia> findmax(A,1) | ||
([3 4], [2 4]) | ||
([3.0 4.0], [2 4]) | ||
|
||
julia> findmax(A,2) | ||
([2; 4], [3; 4]) | ||
([2.0; 4.0], [3; 4]) | ||
``` | ||
""" | ||
function findmax(A::AbstractArray{T}, region) where T | ||
if isempty(A) | ||
return (similar(A, reduced_indices0(A,region)), | ||
similar(dims->zeros(Int, dims), reduced_indices0(A,region))) | ||
end | ||
return findminmax!(>, reducedim_initarray0(A, region, typemin(T)), | ||
return findminmax!(isgt, fill!(similar(A, reduced_indices0(A, region)), first(A)), | ||
similar(dims->zeros(Int, dims), reduced_indices0(A, region)), A) | ||
end | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the
ai != ai
case this can short-circuit, right?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if
ai != ai
I expectisless
is not called. That shall cover theNaN
- case. This can occur at most once, since the next loop circle is broken by them != m
condition few lines before.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, yes, I didn't notice the
m != m && break
check previously.