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

Expand Down
18 changes: 18 additions & 0 deletions base/compiler/compiler.jl
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,24 @@ include("operators.jl")
include("pointer.jl")
include("refvalue.jl")

# required for bootstrap
extrema(itr) = extrema(identity, itr)
function extrema(f, itr)
y = iterate(itr)
y === nothing && throw(ArgumentError("collection must be non-empty"))
(v, s) = y
vmin = vmax = f(v)
while true
y = iterate(itr, s)
y === nothing && break
(x, s) = y
fx = f(x)
vmax = max(fx, vmax)
vmin = min(fx, vmin)
end
return (vmin, vmax)
end

# checked arithmetic
const checked_add = +
const checked_sub = -
Expand Down
38 changes: 20 additions & 18 deletions base/multidimensional.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1709,9 +1709,13 @@ _unique_dims(A::AbstractArray, dims::Colon) = invoke(unique, Tuple{Any}, A)
end

"""
extrema(A::AbstractArray; dims) -> Array{Tuple}
extrema([f,] A::AbstractArray; dims, [init]) -> Array{Tuple}

Compute the minimum and maximum elements of an array over the given dimensions.
Compute the minimum and maximum elements of `A` over dimensions `dims`.
If `f` is provided, return the minimum and maximum elements after applying `f` to them.

!!! compat "Julia 1.2"
The `extrema(f, A)` method requires Julia 1.2 or later.

# Examples
```jldoctest
Expand All @@ -1734,22 +1738,20 @@ julia> extrema(A, dims = (1,2))
(9, 15)
```
"""
extrema(A::AbstractArray; dims = :) = _extrema_dims(identity, A, dims)

"""
extrema(f, A::AbstractArray; dims) -> Array{Tuple}

Compute the minimum and maximum of `f` applied to each element in the given dimensions
of `A`.

!!! compat "Julia 1.2"
This method requires Julia 1.2 or later.
"""
extrema(f, A::AbstractArray; dims=:) = _extrema_dims(f, A, dims)

_extrema_dims(f, A::AbstractArray, ::Colon) = _extrema_itr(f, A)

function _extrema_dims(f, A::AbstractArray, dims)
extrema(f::F, A::AbstractArray; dims=:, init=_InitialValue()) where {F} =
_extrema_dims(f, A, dims, init)

_extrema_dims(f::F, A::AbstractArray, ::Colon, init) where {F} =
mapreduce(_DupY(f), _extrema_rf, A; init = init)
_extrema_dims(f::F, A::AbstractArray, ::Colon, ::_InitialValue) where {F} =
mapreduce(_DupY(f), _extrema_rf, A)
# Note: not passing `init = _InitialValue()` since user-defined
# `reduce`/`foldl` cannot be aware of `Base._InitialValue` that is an
# internal implementation detail.

_extrema_dims(f::F, A::AbstractArray, dims, init) where {F} =
mapreduce(_DupY(f), _extrema_rf, A; dims = dims, init = init)
function _extrema_dims(f::F, A::AbstractArray, dims, ::_InitialValue) where {F}
sz = size(A)
for d in dims
sz = setindex(sz, 1, d)
Expand Down
52 changes: 0 additions & 52 deletions base/operators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -504,58 +504,6 @@ julia> minmax('c','b')
"""
minmax(x,y) = isless(y, x) ? (y, x) : (x, y)

"""
extrema(itr) -> Tuple

Compute both the minimum and maximum element in a single pass, and return them as a 2-tuple.

# Examples
```jldoctest
julia> extrema(2:10)
(2, 10)

julia> extrema([9,pi,4.5])
(3.141592653589793, 9.0)
```
"""
extrema(itr) = _extrema_itr(identity, itr)

"""
extrema(f, itr) -> Tuple

Compute both the minimum and maximum of `f` applied to each element in `itr` and return
them as a 2-tuple. Only one pass is made over `itr`.

!!! compat "Julia 1.2"
This method requires Julia 1.2 or later.

# Examples
```jldoctest
julia> extrema(sin, 0:π)
(0.0, 0.9092974268256817)
```
"""
extrema(f, itr) = _extrema_itr(f, itr)

function _extrema_itr(f, itr)
y = iterate(itr)
y === nothing && throw(ArgumentError("collection must be non-empty"))
(v, s) = y
vmin = vmax = f(v)
while true
y = iterate(itr, s)
y === nothing && break
(x, s) = y
fx = f(x)
vmax = max(fx, vmax)
vmin = min(fx, vmin)
end
return (vmin, vmax)
end

extrema(x::Real) = (x, x)
extrema(f, x::Real) = (y = f(x); (y, y))
Comment on lines -556 to -557
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't these still useful?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mapreduce has a specialization for Number:

mapreduce(f, op, a::Number) = mapreduce_first(f, op, a)

So, I think the compiler will generate the equivalent machine code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool. Maybe worth checking just in case?


## definitions providing basic traits of arithmetic operators ##

"""
Expand Down
71 changes: 70 additions & 1 deletion base/reduce.jl
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,7 @@ julia> prod(1:5; init = 1.0)
"""
prod(a; kw...) = mapreduce(identity, mul_prod, a; kw...)

## maximum & minimum
## maximum, minimum, & extrema
_fast(::typeof(min),x,y) = min(x,y)
_fast(::typeof(max),x,y) = max(x,y)
function _fast(::typeof(max), x::AbstractFloat, y::AbstractFloat)
Expand Down Expand Up @@ -785,6 +785,75 @@ Inf
"""
minimum(a; kw...) = mapreduce(identity, min, a; kw...)

"""
extrema(itr; [init]) -> (mn, mx)

Compute both the minimum `mn` and maximum `mx` element in a single pass, and return them
as a 2-tuple.

The value returned for empty `itr` can be specified by `init`. It must be a 2-tuple whose
first and second elements are neutral elements for `min` and `max` respectively
(i.e. which are greater/less than or equal to any other element). As a consequence, when
`itr` is empty the returned `(mn, mx)` tuple will satisfy `mn ≥ mx`. When `init` is
specified it may be used even for non-empty `itr`.

!!! compat "Julia 1.8"
Keyword argument `init` requires Julia 1.8 or later.

# Examples
```jldoctest
julia> extrema(2:10)
(2, 10)

julia> extrema([9,pi,4.5])
(3.141592653589793, 9.0)

julia> extrema([]; init = (Inf, -Inf))
(Inf, -Inf)
```
"""
extrema(itr; kw...) = extrema(identity, itr; kw...)

"""
extrema(f, itr; [init]) -> (mn, mx)

Compute both the minimum `mn` and maximum `mx` of `f` applied to each element in `itr` and
return them as a 2-tuple. Only one pass is made over `itr`.

The value returned for empty `itr` can be specified by `init`. It must be a 2-tuple whose
first and second elements are neutral elements for `min` and `max` respectively
(i.e. which are greater/less than or equal to any other element). It is used for non-empty
collections. Note: it implies that, for empty `itr`, the returned value `(mn, mx)` satisfies
`mn ≥ mx` even though for non-empty `itr` it satisfies `mn ≤ mx`. This is a "paradoxical"
but yet expected result.

!!! compat "Julia 1.2"
This method requires Julia 1.2 or later.

!!! compat "Julia 1.8"
Keyword argument `init` requires Julia 1.8 or later.

# Examples
```jldoctest
julia> extrema(sin, 0:π)
(0.0, 0.9092974268256817)

julia> extrema(sin, Real[]; init = (1.0, -1.0)) # good, since -1 ≤ sin(::Real) ≤ 1
(1.0, -1.0)
```
"""
extrema(f, itr; kw...) = mapreduce(_DupY(f), _extrema_rf, itr; kw...)

# Not using closure since `extrema(type, itr)` is a very likely use-case and it's better
# to avoid type-instability (#23618).
struct _DupY{F} <: Function
f::F
end
_DupY(f::Type{T}) where {T} = _DupY{Type{T}}(f)
@inline (f::_DupY)(x) = (y = f.f(x); (y, y))

@inline _extrema_rf((min1, max1), (min2, max2)) = (min(min1, min2), max(max1, max2))

## findmax, findmin, argmax & argmin

"""
Expand Down
4 changes: 2 additions & 2 deletions stdlib/SparseArrays/test/higherorderfns.jl
Original file line number Diff line number Diff line change
Expand Up @@ -709,8 +709,8 @@ end
@test extrema(f, x) == extrema(f, y)
@test extrema(spzeros(n, n)) == (0.0, 0.0)
@test extrema(spzeros(n)) == (0.0, 0.0)
@test_throws ArgumentError extrema(spzeros(0, 0))
@test_throws ArgumentError extrema(spzeros(0))
@test_throws "reducing over an empty" extrema(spzeros(0, 0))
@test_throws "reducing over an empty" extrema(spzeros(0))
@test extrema(sparse(ones(n, n))) == (1.0, 1.0)
@test extrema(sparse(ones(n))) == (1.0, 1.0)
@test extrema(A; dims=:) == extrema(B; dims=:)
Expand Down
9 changes: 9 additions & 0 deletions test/reduce.jl
Original file line number Diff line number Diff line change
Expand Up @@ -244,23 +244,32 @@ prod2(itr) = invoke(prod, Tuple{Any}, itr)

@test_throws "reducing over an empty" maximum(Int[])
@test_throws "reducing over an empty" minimum(Int[])
@test_throws "reducing over an empty" extrema(Int[])

@test maximum(Int[]; init=-1) == -1
@test minimum(Int[]; init=-1) == -1
@test extrema(Int[]; init=(1, -1)) == (1, -1)

@test maximum(sin, []; init=-1) == -1
@test minimum(sin, []; init=1) == 1
@test extrema(sin, []; init=(1, -1)) == (1, -1)

@test maximum(5) == 5
@test minimum(5) == 5
@test extrema(5) == (5, 5)
@test extrema(abs2, 5) == (25, 25)
@test Core.Compiler.extrema(abs2, 5) == (25, 25)

let x = [4,3,5,2]
@test maximum(x) == 5
@test minimum(x) == 2
@test extrema(x) == (2, 5)
@test Core.Compiler.extrema(x) == (2, 5)

@test maximum(abs2, x) == 25
@test minimum(abs2, x) == 4
@test extrema(abs2, x) == (4, 25)
@test Core.Compiler.extrema(abs2, x) == (4, 25)
end

@test maximum([-0.,0.]) === 0.0
Expand Down
4 changes: 4 additions & 0 deletions test/reducedim.jl
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ A = Array{Int}(undef, 0, 3)
@test_throws "reducing over an empty collection is not allowed" maximum(A; dims=1)
@test maximum(A; dims=1, init=-1) == reshape([-1,-1,-1], 1, 3)

@test maximum(zeros(0, 2); dims=1, init=-1) == fill(-1, 1, 2)
@test minimum(zeros(0, 2); dims=1, init=1) == ones(1, 2)
@test extrema(zeros(0, 2); dims=1, init=(1, -1)) == fill((1, -1), 1, 2)

# Test reduction along first dimension; this is special-cased for
# size(A, 1) >= 16
Breduc = rand(64, 3)
Expand Down