Skip to content

Commit 7b0e72e

Browse files
committed
Define extrema using mapreduce; support init
1 parent 1e76111 commit 7b0e72e

File tree

6 files changed

+117
-71
lines changed

6 files changed

+117
-71
lines changed

base/compiler/compiler.jl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,24 @@ include("operators.jl")
4848
include("pointer.jl")
4949
include("refvalue.jl")
5050

51+
# required for bootstrap
52+
extrema(itr) = extrema(identity, itr)
53+
function extrema(f, itr)
54+
y = iterate(itr)
55+
y === nothing && throw(ArgumentError("collection must be non-empty"))
56+
(v, s) = y
57+
vmin = vmax = f(v)
58+
while true
59+
y = iterate(itr, s)
60+
y === nothing && break
61+
(x, s) = y
62+
fx = f(x)
63+
vmax = max(fx, vmax)
64+
vmin = min(fx, vmin)
65+
end
66+
return (vmin, vmax)
67+
end
68+
5169
# checked arithmetic
5270
const checked_add = +
5371
const checked_sub = -

base/multidimensional.jl

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

15681568
"""
1569-
extrema(A::AbstractArray; dims) -> Array{Tuple}
1569+
extrema([f,] A::AbstractArray; dims, [init]) -> Array{Tuple}
15701570
1571-
Compute the minimum and maximum elements of an array over the given dimensions.
1571+
Compute the minimum and maximum of `f` applied to each element (if given) in the given
1572+
dimensions of `A`.
1573+
1574+
!!! compat "Julia 1.2"
1575+
2-argument `extrema` method requires Julia 1.2 or later.
15721576
15731577
# Examples
15741578
```jldoctest
@@ -1591,22 +1595,19 @@ julia> extrema(A, dims = (1,2))
15911595
(9, 15)
15921596
```
15931597
"""
1594-
extrema(A::AbstractArray; dims = :) = _extrema_dims(identity, A, dims)
1595-
1596-
"""
1597-
extrema(f, A::AbstractArray; dims) -> Array{Tuple}
1598-
1599-
Compute the minimum and maximum of `f` applied to each element in the given dimensions
1600-
of `A`.
1601-
1602-
!!! compat "Julia 1.2"
1603-
This method requires Julia 1.2 or later.
1604-
"""
1605-
extrema(f, A::AbstractArray; dims=:) = _extrema_dims(f, A, dims)
1606-
1607-
_extrema_dims(f, A::AbstractArray, ::Colon) = _extrema_itr(f, A)
1608-
1609-
function _extrema_dims(f, A::AbstractArray, dims)
1598+
extrema(f::F, A::AbstractArray; dims=:, init=_InitialValue()) where {F} =
1599+
_extrema_dims(f, A, dims, init)
1600+
1601+
_extrema_dims(f::F, A::AbstractArray, ::Colon, init) where {F} =
1602+
mapreduce(_DupY(f), _extrema_rf, A; init = init)
1603+
_extrema_dims(f::F, A::AbstractArray, ::Colon, ::_InitialValue) where {F} =
1604+
mapreduce(_DupY(f), _extrema_rf, A)
1605+
# Note: not passing `init = _InitialValue()` since user-defined
1606+
# `reduce`/`foldl` could cannot be aware of `Base._InitialValue`.
1607+
1608+
_extrema_dims(f::F, A::AbstractArray, dims, init) where {F} =
1609+
mapreduce(_DupY(f), _extrema_rf, A; dims = dims, init = init)
1610+
function _extrema_dims(f::F, A::AbstractArray, dims, ::_InitialValue) where {F}
16101611
sz = [size(A)...]
16111612
for d in dims
16121613
sz[d] = 1

base/operators.jl

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

446-
"""
447-
extrema(itr) -> Tuple
448-
449-
Compute both the minimum and maximum element in a single pass, and return them as a 2-tuple.
450-
451-
# Examples
452-
```jldoctest
453-
julia> extrema(2:10)
454-
(2, 10)
455-
456-
julia> extrema([9,pi,4.5])
457-
(3.141592653589793, 9.0)
458-
```
459-
"""
460-
extrema(itr) = _extrema_itr(identity, itr)
461-
462-
"""
463-
extrema(f, itr) -> Tuple
464-
465-
Compute both the minimum and maximum of `f` applied to each element in `itr` and return
466-
them as a 2-tuple. Only one pass is made over `itr`.
467-
468-
!!! compat "Julia 1.2"
469-
This method requires Julia 1.2 or later.
470-
471-
# Examples
472-
```jldoctest
473-
julia> extrema(sin, 0:π)
474-
(0.0, 0.9092974268256817)
475-
```
476-
"""
477-
extrema(f, itr) = _extrema_itr(f, itr)
478-
479-
function _extrema_itr(f, itr)
480-
y = iterate(itr)
481-
y === nothing && throw(ArgumentError("collection must be non-empty"))
482-
(v, s) = y
483-
vmin = vmax = f(v)
484-
while true
485-
y = iterate(itr, s)
486-
y === nothing && break
487-
(x, s) = y
488-
fx = f(x)
489-
vmax = max(fx, vmax)
490-
vmin = min(fx, vmin)
491-
end
492-
return (vmin, vmax)
493-
end
494-
495-
extrema(x::Real) = (x, x)
496-
extrema(f, x::Real) = (y = f(x); (y, y))
497-
498446
## definitions providing basic traits of arithmetic operators ##
499447

500448
"""

base/reduce.jl

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,7 @@ julia> prod(1:5; init = 1.0)
581581
"""
582582
prod(a; kw...) = mapreduce(identity, mul_prod, a; kw...)
583583

584-
## maximum & minimum
584+
## maximum, minimum, & extrema
585585
_fast(::typeof(min),x,y) = min(x,y)
586586
_fast(::typeof(max),x,y) = max(x,y)
587587
function _fast(::typeof(max), x::AbstractFloat, y::AbstractFloat)
@@ -762,6 +762,75 @@ Inf
762762
"""
763763
minimum(a; kw...) = mapreduce(identity, min, a; kw...)
764764

765+
"""
766+
extrema(itr; [init]) -> Tuple
767+
768+
Compute both the minimum and maximum element in a single pass, and return them as a 2-tuple.
769+
770+
The value returned for empty `itr` can be specified by `init`. It must be a 2-tuple whose
771+
first/second element is a neutral element for `min`/`max` (i.e. which is greater/less than
772+
or equal to any other element). This is required because it is unspecified whether `init`
773+
is used for non-empty collections. Note: it implies that, for empty `itr`, the first
774+
element is typically _greater_ than the last element. This is a "paradoxical" but yet
775+
expected result.
776+
777+
!!! compat "Julia 1.6"
778+
Keyword argument `init` requires Julia 1.6 or later.
779+
780+
# Examples
781+
```jldoctest
782+
julia> extrema(2:10)
783+
(2, 10)
784+
785+
julia> extrema([9,pi,4.5])
786+
(3.141592653589793, 9.0)
787+
788+
julia> extrema([]; init = (Inf, -Inf))
789+
(Inf, -Inf)
790+
```
791+
"""
792+
extrema(itr; kw...) = extrema(identity, itr; kw...)
793+
794+
"""
795+
extrema(f, itr; [init]) -> Tuple
796+
797+
Compute both the minimum and maximum of `f` applied to each element in `itr` and return
798+
them as a 2-tuple. Only one pass is made over `itr`.
799+
800+
The value returned for empty `itr` can be specified by `init`. It must be a 2-tuple whose
801+
first/second element is a neutral element for `min`/`max` (i.e. which is greater/less than
802+
or equal to any other element). This is required because it is unspecified whether `init`
803+
is used for non-empty collections. Note: it implies that, for empty `itr`, the first
804+
element is typically _greater_ than the last element. This is a "paradoxical" but yet
805+
expected result.
806+
807+
!!! compat "Julia 1.2"
808+
This method requires Julia 1.2 or later.
809+
810+
!!! compat "Julia 1.6"
811+
Keyword argument `init` requires Julia 1.6 or later.
812+
813+
# Examples
814+
```jldoctest
815+
julia> extrema(sin, 0:π)
816+
(0.0, 0.9092974268256817)
817+
818+
julia> extrema(sin, Real[]; init = (1.0, -1.0)) # good, since -1 ≤ sin(::Real) ≤ 1
819+
(1.0, -1.0)
820+
```
821+
"""
822+
extrema(f, itr; kw...) = mapreduce(_DupY(f), _extrema_rf, itr; kw...)
823+
824+
# Not using closure since `extrema(type, itr)` is a very likely use-case and it's better
825+
# to avoid type-instability (#23618).
826+
struct _DupY{F} <: Function
827+
f::F
828+
end
829+
_DupY(f::Type{T}) where {T} = _DupY{Type{T}}(f)
830+
@inline (f::_DupY)(x) = (y = f.f(x); (y, y))
831+
832+
@inline _extrema_rf((min1, max1), (min2, max2)) = (min(min1, min2), max(max1, max2))
833+
765834
## all & any
766835

767836
"""

test/reduce.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,9 +241,15 @@ prod2(itr) = invoke(prod, Tuple{Any}, itr)
241241

242242
@test_throws ArgumentError maximum(Int[])
243243
@test_throws ArgumentError minimum(Int[])
244+
@test_throws ArgumentError extrema(Int[])
244245

245246
@test maximum(Int[]; init=-1) == -1
246247
@test minimum(Int[]; init=-1) == -1
248+
@test extrema(Int[]; init=(1, -1)) == (1, -1)
249+
250+
@test maximum(sin, []; init=-1) == -1
251+
@test minimum(sin, []; init=1) == 1
252+
@test extrema(sin, []; init=(1, -1)) == (1, -1)
247253

248254
@test maximum(5) == 5
249255
@test minimum(5) == 5

test/reducedim.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ A = Array{Int}(undef, 0, 3)
8080
@test_throws ArgumentError maximum(A; dims=1)
8181
@test maximum(A; dims=1, init=-1) == reshape([-1,-1,-1], 1, 3)
8282

83+
@test maximum(zeros(0, 2); dims=1, init=-1) == fill(-1, 1, 2)
84+
@test minimum(zeros(0, 2); dims=1, init=1) == ones(1, 2)
85+
@test extrema(zeros(0, 2); dims=1, init=(1, -1)) == fill((1, -1), 1, 2)
86+
8387
# Test reduction along first dimension; this is special-cased for
8488
# size(A, 1) >= 16
8589
Breduc = rand(64, 3)

0 commit comments

Comments
 (0)