Skip to content

Commit 00ad5cf

Browse files
N5N3nalimilansimeonschaubtkftimholy
committed
Fix extrema(x; dims) for inputs with NaN/missing (JuliaLang#43604)
* Define `extrema` using `mapreduce`; support `init` * Fix tests for SparseArrays * API clean and export `extrema!` * Re-implement `reducedim_init` for extrema * Merge `master` to pull in JuliaSparse/SparseArrays.jl#63 * Mark `BigInt` tests as broken Co-authored-by: Milan Bouchet-Valat <nalimilan@club.fr> Co-authored-by: Simeon Schaub <simeondavidschaub99@gmail.com> Co-authored-by: Takafumi Arakaki <aka.tkf@gmail.com> Co-authored-by: Tim Holy <tim.holy@gmail.com>
1 parent 0993c30 commit 00ad5cf

File tree

10 files changed

+266
-149
lines changed

10 files changed

+266
-149
lines changed

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ Standard library changes
9999
arithmetic to error if the result may be wrapping. Or use a package such as SaferIntegers.jl when
100100
constructing the range. ([#40382])
101101
* TCP socket objects now expose `closewrite` functionality and support half-open mode usage ([#40783]).
102+
* `extrema` now supports `init` keyword argument ([#36265], [#43604]).
102103
* Intersect returns a result with the eltype of the type-promoted eltypes of the two inputs ([#41769]).
103104
* `Iterators.countfrom` now accepts any type that defines `+`. ([#37747])
104105

base/compiler/compiler.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,19 @@ include("compiler/abstractinterpretation.jl")
131131
include("compiler/typeinfer.jl")
132132
include("compiler/optimize.jl") # TODO: break this up further + extract utilities
133133

134+
# required for bootstrap
135+
# TODO: find why this is needed and remove it.
136+
function extrema(x::Array)
137+
isempty(x) && throw(ArgumentError("collection must be non-empty"))
138+
vmin = vmax = x[1]
139+
for i in 2:length(x)
140+
xi = x[i]
141+
vmax = max(vmax, xi)
142+
vmin = min(vmin, xi)
143+
end
144+
return vmin, vmax
145+
end
146+
134147
include("compiler/bootstrap.jl")
135148
ccall(:jl_set_typeinf_func, Cvoid, (Any,), typeinf_ext_toplevel)
136149

base/exports.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,7 @@ export
385385
eachindex,
386386
eachrow,
387387
eachslice,
388+
extrema!,
388389
extrema,
389390
fill!,
390391
fill,

base/multidimensional.jl

Lines changed: 0 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1695,80 +1695,6 @@ _unique_dims(A::AbstractArray, dims::Colon) = invoke(unique, Tuple{Any}, A)
16951695
end
16961696
end
16971697

1698-
"""
1699-
extrema(A::AbstractArray; dims) -> Array{Tuple}
1700-
1701-
Compute the minimum and maximum elements of an array over the given dimensions.
1702-
1703-
# Examples
1704-
```jldoctest
1705-
julia> A = reshape(Vector(1:2:16), (2,2,2))
1706-
2×2×2 Array{Int64, 3}:
1707-
[:, :, 1] =
1708-
1 5
1709-
3 7
1710-
1711-
[:, :, 2] =
1712-
9 13
1713-
11 15
1714-
1715-
julia> extrema(A, dims = (1,2))
1716-
1×1×2 Array{Tuple{Int64, Int64}, 3}:
1717-
[:, :, 1] =
1718-
(1, 7)
1719-
1720-
[:, :, 2] =
1721-
(9, 15)
1722-
```
1723-
"""
1724-
extrema(A::AbstractArray; dims = :) = _extrema_dims(identity, A, dims)
1725-
1726-
"""
1727-
extrema(f, A::AbstractArray; dims) -> Array{Tuple}
1728-
1729-
Compute the minimum and maximum of `f` applied to each element in the given dimensions
1730-
of `A`.
1731-
1732-
!!! compat "Julia 1.2"
1733-
This method requires Julia 1.2 or later.
1734-
"""
1735-
extrema(f, A::AbstractArray; dims=:) = _extrema_dims(f, A, dims)
1736-
1737-
_extrema_dims(f, A::AbstractArray, ::Colon) = _extrema_itr(f, A)
1738-
1739-
function _extrema_dims(f, A::AbstractArray, dims)
1740-
sz = size(A)
1741-
for d in dims
1742-
sz = setindex(sz, 1, d)
1743-
end
1744-
T = promote_op(f, eltype(A))
1745-
B = Array{Tuple{T,T}}(undef, sz...)
1746-
return extrema!(f, B, A)
1747-
end
1748-
1749-
@noinline function extrema!(f, B, A)
1750-
require_one_based_indexing(B, A)
1751-
sA = size(A)
1752-
sB = size(B)
1753-
for I in CartesianIndices(sB)
1754-
fAI = f(A[I])
1755-
B[I] = (fAI, fAI)
1756-
end
1757-
Bmax = CartesianIndex(sB)
1758-
@inbounds @simd for I in CartesianIndices(sA)
1759-
J = min(Bmax,I)
1760-
BJ = B[J]
1761-
fAI = f(A[I])
1762-
if fAI < BJ[1]
1763-
B[J] = (fAI, BJ[2])
1764-
elseif fAI > BJ[2]
1765-
B[J] = (BJ[1], fAI)
1766-
end
1767-
end
1768-
return B
1769-
end
1770-
extrema!(B, A) = extrema!(identity, B, A)
1771-
17721698
# Show for pairs() with Cartesian indices. Needs to be here rather than show.jl for bootstrap order
17731699
function Base.showarg(io::IO, r::Iterators.Pairs{<:Integer, <:Any, <:Any, T}, toplevel) where T <: Union{AbstractVector, Tuple}
17741700
print(io, "pairs(::$T)")

base/operators.jl

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -504,58 +504,6 @@ julia> minmax('c','b')
504504
"""
505505
minmax(x,y) = isless(y, x) ? (y, x) : (x, y)
506506

507-
"""
508-
extrema(itr) -> Tuple
509-
510-
Compute both the minimum and maximum element in a single pass, and return them as a 2-tuple.
511-
512-
# Examples
513-
```jldoctest
514-
julia> extrema(2:10)
515-
(2, 10)
516-
517-
julia> extrema([9,pi,4.5])
518-
(3.141592653589793, 9.0)
519-
```
520-
"""
521-
extrema(itr) = _extrema_itr(identity, itr)
522-
523-
"""
524-
extrema(f, itr) -> Tuple
525-
526-
Compute both the minimum and maximum of `f` applied to each element in `itr` and return
527-
them as a 2-tuple. Only one pass is made over `itr`.
528-
529-
!!! compat "Julia 1.2"
530-
This method requires Julia 1.2 or later.
531-
532-
# Examples
533-
```jldoctest
534-
julia> extrema(sin, 0:π)
535-
(0.0, 0.9092974268256817)
536-
```
537-
"""
538-
extrema(f, itr) = _extrema_itr(f, itr)
539-
540-
function _extrema_itr(f, itr)
541-
y = iterate(itr)
542-
y === nothing && throw(ArgumentError("collection must be non-empty"))
543-
(v, s) = y
544-
vmin = vmax = f(v)
545-
while true
546-
y = iterate(itr, s)
547-
y === nothing && break
548-
(x, s) = y
549-
fx = f(x)
550-
vmax = max(fx, vmax)
551-
vmin = min(fx, vmin)
552-
end
553-
return (vmin, vmax)
554-
end
555-
556-
extrema(x::Real) = (x, x)
557-
extrema(f, x::Real) = (y = f(x); (y, y))
558-
559507
## definitions providing basic traits of arithmetic operators ##
560508

561509
"""

base/reduce.jl

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,7 @@ julia> prod(1:5; init = 1.0)
604604
"""
605605
prod(a; kw...) = mapreduce(identity, mul_prod, a; kw...)
606606

607-
## maximum & minimum
607+
## maximum, minimum, & extrema
608608
_fast(::typeof(min),x,y) = min(x,y)
609609
_fast(::typeof(max),x,y) = max(x,y)
610610
function _fast(::typeof(max), x::AbstractFloat, y::AbstractFloat)
@@ -634,11 +634,6 @@ function mapreduce_impl(f, op::Union{typeof(max), typeof(min)},
634634
start = first + 1
635635
simdstop = start + chunk_len - 4
636636
while simdstop <= last - 3
637-
# short circuit in case of NaN or missing
638-
(v1 == v1) === true || return v1
639-
(v2 == v2) === true || return v2
640-
(v3 == v3) === true || return v3
641-
(v4 == v4) === true || return v4
642637
@inbounds for i in start:4:simdstop
643638
v1 = _fast(op, v1, f(A[i+0]))
644639
v2 = _fast(op, v2, f(A[i+1]))
@@ -785,6 +780,76 @@ Inf
785780
"""
786781
minimum(a; kw...) = mapreduce(identity, min, a; kw...)
787782

783+
"""
784+
extrema(itr; [init]) -> (mn, mx)
785+
786+
Compute both the minimum `mn` and maximum `mx` element in a single pass, and return them
787+
as a 2-tuple.
788+
789+
The value returned for empty `itr` can be specified by `init`. It must be a 2-tuple whose
790+
first and second elements are neutral elements for `min` and `max` respectively
791+
(i.e. which are greater/less than or equal to any other element). As a consequence, when
792+
`itr` is empty the returned `(mn, mx)` tuple will satisfy `mn ≥ mx`. When `init` is
793+
specified it may be used even for non-empty `itr`.
794+
795+
!!! compat "Julia 1.8"
796+
Keyword argument `init` requires Julia 1.8 or later.
797+
798+
# Examples
799+
```jldoctest
800+
julia> extrema(2:10)
801+
(2, 10)
802+
803+
julia> extrema([9,pi,4.5])
804+
(3.141592653589793, 9.0)
805+
806+
julia> extrema([]; init = (Inf, -Inf))
807+
(Inf, -Inf)
808+
```
809+
"""
810+
extrema(itr; kw...) = extrema(identity, itr; kw...)
811+
812+
"""
813+
extrema(f, itr; [init]) -> (mn, mx)
814+
815+
Compute both the minimum `mn` and maximum `mx` of `f` applied to each element in `itr` and
816+
return them as a 2-tuple. Only one pass is made over `itr`.
817+
818+
The value returned for empty `itr` can be specified by `init`. It must be a 2-tuple whose
819+
first and second elements are neutral elements for `min` and `max` respectively
820+
(i.e. which are greater/less than or equal to any other element). It is used for non-empty
821+
collections. Note: it implies that, for empty `itr`, the returned value `(mn, mx)` satisfies
822+
`mn ≥ mx` even though for non-empty `itr` it satisfies `mn ≤ mx`. This is a "paradoxical"
823+
but yet expected result.
824+
825+
!!! compat "Julia 1.2"
826+
This method requires Julia 1.2 or later.
827+
828+
!!! compat "Julia 1.8"
829+
Keyword argument `init` requires Julia 1.8 or later.
830+
831+
# Examples
832+
```jldoctest
833+
julia> extrema(sin, 0:π)
834+
(0.0, 0.9092974268256817)
835+
836+
julia> extrema(sin, Real[]; init = (1.0, -1.0)) # good, since -1 ≤ sin(::Real) ≤ 1
837+
(1.0, -1.0)
838+
```
839+
"""
840+
extrema(f, itr; kw...) = mapreduce(ExtremaMap(f), _extrema_rf, itr; kw...)
841+
842+
# Not using closure since `extrema(type, itr)` is a very likely use-case and it's better
843+
# to avoid type-instability (#23618).
844+
struct ExtremaMap{F} <: Function
845+
f::F
846+
end
847+
ExtremaMap(::Type{T}) where {T} = ExtremaMap{Type{T}}(T)
848+
@inline (f::ExtremaMap)(x) = (y = f.f(x); (y, y))
849+
850+
# TODO: optimize for inputs <: AbstractFloat
851+
@inline _extrema_rf((min1, max1), (min2, max2)) = (min(min1, min2), max(max1, max2))
852+
788853
## findmax, findmin, argmax & argmin
789854

790855
"""

0 commit comments

Comments
 (0)