Skip to content

Commit 28471a6

Browse files
committed
Merge branch 'pr/36265' into PR36265(cleaned)
2 parents ad129a9 + 5669d63 commit 28471a6

File tree

8 files changed

+124
-73
lines changed

8 files changed

+124
-73
lines changed

NEWS.md

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

base/compiler/compiler.jl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,24 @@ include("operators.jl")
5555
include("pointer.jl")
5656
include("refvalue.jl")
5757

58+
# required for bootstrap
59+
extrema(itr) = extrema(identity, itr)
60+
function extrema(f, itr)
61+
y = iterate(itr)
62+
y === nothing && throw(ArgumentError("collection must be non-empty"))
63+
(v, s) = y
64+
vmin = vmax = f(v)
65+
while true
66+
y = iterate(itr, s)
67+
y === nothing && break
68+
(x, s) = y
69+
fx = f(x)
70+
vmax = max(fx, vmax)
71+
vmin = min(fx, vmin)
72+
end
73+
return (vmin, vmax)
74+
end
75+
5876
# checked arithmetic
5977
const checked_add = +
6078
const checked_sub = -

base/multidimensional.jl

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1703,9 +1703,13 @@ _unique_dims(A::AbstractArray, dims::Colon) = invoke(unique, Tuple{Any}, A)
17031703
end
17041704

17051705
"""
1706-
extrema(A::AbstractArray; dims) -> Array{Tuple}
1706+
extrema([f,] A::AbstractArray; dims, [init]) -> Array{Tuple}
17071707
1708-
Compute the minimum and maximum elements of an array over the given dimensions.
1708+
Compute the minimum and maximum elements of `A` over dimensions `dims`.
1709+
If `f` is provided, return the minimum and maximum elements after applying `f` to them.
1710+
1711+
!!! compat "Julia 1.2"
1712+
The `extrema(f, A)` method requires Julia 1.2 or later.
17091713
17101714
# Examples
17111715
```jldoctest
@@ -1728,22 +1732,20 @@ julia> extrema(A, dims = (1,2))
17281732
(9, 15)
17291733
```
17301734
"""
1731-
extrema(A::AbstractArray; dims = :) = _extrema_dims(identity, A, dims)
1732-
1733-
"""
1734-
extrema(f, A::AbstractArray; dims) -> Array{Tuple}
1735-
1736-
Compute the minimum and maximum of `f` applied to each element in the given dimensions
1737-
of `A`.
1738-
1739-
!!! compat "Julia 1.2"
1740-
This method requires Julia 1.2 or later.
1741-
"""
1742-
extrema(f, A::AbstractArray; dims=:) = _extrema_dims(f, A, dims)
1743-
1744-
_extrema_dims(f, A::AbstractArray, ::Colon) = _extrema_itr(f, A)
1745-
1746-
function _extrema_dims(f, A::AbstractArray, dims)
1735+
extrema(f::F, A::AbstractArray; dims=:, init=_InitialValue()) where {F} =
1736+
_extrema_dims(f, A, dims, init)
1737+
1738+
_extrema_dims(f::F, A::AbstractArray, ::Colon, init) where {F} =
1739+
mapreduce(_DupY(f), _extrema_rf, A; init = init)
1740+
_extrema_dims(f::F, A::AbstractArray, ::Colon, ::_InitialValue) where {F} =
1741+
mapreduce(_DupY(f), _extrema_rf, A)
1742+
# Note: not passing `init = _InitialValue()` since user-defined
1743+
# `reduce`/`foldl` cannot be aware of `Base._InitialValue` that is an
1744+
# internal implementation detail.
1745+
1746+
_extrema_dims(f::F, A::AbstractArray, dims, init) where {F} =
1747+
mapreduce(_DupY(f), _extrema_rf, A; dims = dims, init = init)
1748+
function _extrema_dims(f::F, A::AbstractArray, dims, ::_InitialValue) where {F}
17471749
sz = size(A)
17481750
for d in dims
17491751
sz = setindex(sz, 1, d)

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: 70 additions & 1 deletion
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)
@@ -785,6 +785,75 @@ Inf
785785
"""
786786
minimum(a; kw...) = mapreduce(identity, min, a; kw...)
787787

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

790859
"""

stdlib/SparseArrays/test/higherorderfns.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -709,8 +709,8 @@ end
709709
@test extrema(f, x) == extrema(f, y)
710710
@test extrema(spzeros(n, n)) == (0.0, 0.0)
711711
@test extrema(spzeros(n)) == (0.0, 0.0)
712-
@test_throws ArgumentError extrema(spzeros(0, 0))
713-
@test_throws ArgumentError extrema(spzeros(0))
712+
@test_throws "reducing over an empty" extrema(spzeros(0, 0))
713+
@test_throws "reducing over an empty" extrema(spzeros(0))
714714
@test extrema(sparse(ones(n, n))) == (1.0, 1.0)
715715
@test extrema(sparse(ones(n))) == (1.0, 1.0)
716716
@test extrema(A; dims=:) == extrema(B; dims=:)

test/reduce.jl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,23 +244,32 @@ prod2(itr) = invoke(prod, Tuple{Any}, itr)
244244

245245
@test_throws "reducing over an empty" maximum(Int[])
246246
@test_throws "reducing over an empty" minimum(Int[])
247+
@test_throws "reducing over an empty" extrema(Int[])
247248

248249
@test maximum(Int[]; init=-1) == -1
249250
@test minimum(Int[]; init=-1) == -1
251+
@test extrema(Int[]; init=(1, -1)) == (1, -1)
252+
253+
@test maximum(sin, []; init=-1) == -1
254+
@test minimum(sin, []; init=1) == 1
255+
@test extrema(sin, []; init=(1, -1)) == (1, -1)
250256

251257
@test maximum(5) == 5
252258
@test minimum(5) == 5
253259
@test extrema(5) == (5, 5)
254260
@test extrema(abs2, 5) == (25, 25)
261+
@test Core.Compiler.extrema(abs2, 5) == (25, 25)
255262

256263
let x = [4,3,5,2]
257264
@test maximum(x) == 5
258265
@test minimum(x) == 2
259266
@test extrema(x) == (2, 5)
267+
@test Core.Compiler.extrema(x) == (2, 5)
260268

261269
@test maximum(abs2, x) == 25
262270
@test minimum(abs2, x) == 4
263271
@test extrema(abs2, x) == (4, 25)
272+
@test Core.Compiler.extrema(abs2, x) == (4, 25)
264273
end
265274

266275
@test maximum([-0.,0.]) === 0.0

test/reducedim.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ A = Array{Int}(undef, 0, 3)
9393
@test_throws "reducing over an empty collection is not allowed" maximum(A; dims=1)
9494
@test maximum(A; dims=1, init=-1) == reshape([-1,-1,-1], 1, 3)
9595

96+
@test maximum(zeros(0, 2); dims=1, init=-1) == fill(-1, 1, 2)
97+
@test minimum(zeros(0, 2); dims=1, init=1) == ones(1, 2)
98+
@test extrema(zeros(0, 2); dims=1, init=(1, -1)) == fill((1, -1), 1, 2)
99+
96100
# Test reduction along first dimension; this is special-cased for
97101
# size(A, 1) >= 16
98102
Breduc = rand(64, 3)

0 commit comments

Comments
 (0)