Skip to content

Clarify the possible uses of the init keyword in minimum, maximum and extrema #44819

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

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 46 additions & 11 deletions base/reduce.jl
Original file line number Diff line number Diff line change
Expand Up @@ -739,19 +739,29 @@ Return the largest element in a collection.
The value returned for empty `itr` can be specified by `init`. It must be
a neutral element for `max` (i.e. which is less than or equal to any
other element) as it is unspecified whether `init` is used
for non-empty collections.
for non-empty collections. If `init > maximum(itr)`, return `init`.
Comment on lines 741 to +742
Copy link
Member

Choose a reason for hiding this comment

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

I am okay with making the behavior specified here (c.f. #49042), but the docstring needs to be consistent and not both say it is unspecified behavior and to also specify the behavior here

Copy link
Contributor

Choose a reason for hiding this comment

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

My understanding is that the current maximum (and minimum and maybe elsewhere?) documentation is merely repeating what is said about the init parameter of mapreduce. But this advice is only true for mapreduce in general, and not for the specific cases of maximum and minimum. Here we should be able to say init will indeed be treated as just another element in the input, and will be returned as the output in case of an empty list. It would just be nice to have 1. a confirmation that this is the desired behavior for maximum, minimum and mapreduce and 2. perhaps the ability to prove this is the case looking at the implementation, and what methods are called in the specific case of maximum and minimum. I'm unfortunately not too familiar with the implementation, I can't easily make sense of it myself, and I'm not sure where the implementation for these methods diverges compared to other reducing operations (if it diverges at all. is it still unspecified in general for mapreduce?).

In other words, I suggest actually removing the text mentioning anything unspecified for these methods, and only mention the term will be output for an empty list, and will generally be treated as an extra item in the input.

Copy link
Member

Choose a reason for hiding this comment

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

That behavior is already specified for mapfoldr, mapfoldl, and mapreduce(identity), so it seems reasonable to assume the same for mapreduce(max) and thus maximum as well. But that is the question intended to be answered by #49042 (it looks only like a doc change to me now).

Aside, to be pedantic about this question:

reduce(min, [1,2,3],init=3) sort of follows the monoid laws as well... There's probably a special name for this group

I think this is exactly the same monoid law as the first example. In particular, the init is supposed to be ranging over the domain of the inputs. So if the input was UInt8 instead, then the init is 0xff instead. But if the input function generating that array was 2pi*sin%Int, then the init is arguably 6, since the domain of that input function is [-6, 6]. Using typemax is just a rough approximation of the expected domain in any case.


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

# Examples
```jldoctest
julia> maximum(-20.5:10)
julia> x = -20.5:10
-20.5:1.0:9.5

julia> maximum(x)
9.5

julia> maximum([1,2,3])
julia> y = [1 2 3]
1×3 Matrix{Int64}:
1 2 3

julia> maximum(y)
3

julia> maximum(y, init=maximum(x)) == maximum(x ∪ y) == 9.5
true

julia> maximum(())
ERROR: MethodError: reducing over an empty collection is not allowed; consider supplying `init` to the reducer
Stacktrace:
Expand All @@ -771,19 +781,29 @@ Return the smallest element in a collection.
The value returned for empty `itr` can be specified by `init`. It must be
a neutral element for `min` (i.e. which is greater than or equal to any
other element) as it is unspecified whether `init` is used
for non-empty collections.
for non-empty collections. If `init < minimum(itr)`, return `init`.

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

# Examples
```jldoctest
julia> minimum(-20.5:10)
julia> x = -20.5:10
-20.5:1.0:9.5

julia> minimum(x)
-20.5

julia> minimum([1,2,3])
julia> y = [1 2 3]
1×3 Matrix{Int64}:
1 2 3

julia> minimum(y)
1

julia> minimum(y, init=minimum(x)) == minimum(x ∪ y) == -20.5
true

julia> minimum([])
ERROR: MethodError: reducing over an empty collection is not allowed; consider supplying `init` to the reducer
Stacktrace:
Expand All @@ -804,20 +824,35 @@ 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`.
`itr` is empty the returned `(mn, mx)` tuple will satisfy `mn ≥ mx`. If the `init` contains
a non-neutral element, this element is returned as appropriate. (If `init[1] < minimum(itr)`,
`mn == init[1]`. If `init[2] > maximum(itr)`, `mx == init[2]`.) 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> x = 1:5
1:5

julia> extrema(x)
(1, 5)

julia> y = [9 pi 4.5]
1×3 Matrix{Float64}:
9.0 3.14159 4.5

julia> extrema([9,pi,4.5])
julia> extrema(x)
(1, 5)

julia> extrema(y)
(3.141592653589793, 9.0)

julia> extrema(y, init=extrema(x)) == extrema(x ∪ y) == (1.0, 9.0)
true

julia> extrema([]; init = (Inf, -Inf))
(Inf, -Inf)
```
Expand Down
9 changes: 9 additions & 0 deletions test/reduce.jl
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,15 @@ A = circshift(reshape(1:24,2,3,4), (0,1,1))
maximum([NaN;zeros(255);missing]) === missing
end

@testset "maximum & minimum & extrema with init" begin
@test maximum(1:10, init=11) == 11
@test maximum(1:10, init=0) == 10
@test minimum(1:10, init=11) == 1
@test minimum(1:10, init=0) == 0
@test extrema(1:10, init=(0, 5)) == (0, 10)
@test extrema(1:10, init=(5, 11)) == (1, 11)
end

# findmin, findmax, argmin, argmax

@testset "findmin(f, domain)" begin
Expand Down