Skip to content

Commit 6012a85

Browse files
cmcaineJameson Nashtkf
authored andcommitted
Add 2-arg versions of findmax/min, argmax/min (JuliaLang#35316)
Defines a descending total order, `isgreater` (not exported), where unordered values like NaNs and missing are last. This makes defining min, argmin, etc, simpler and more consistent. Also adds 2-arg versions of findmax/min, argmax/min. Defines and exports the `isunordered` predicate for testing whether a value is unordered like NaN and missing. Fixes JuliaLang#27613. Related: JuliaLang#27639, JuliaLang#27612, JuliaLang#34674. Thanks to @tkf, @StefanKarpinski and @drewrobson for their assistance with this PR. Co-authored-by: Jameson Nash <[email protected]> Co-authored-by: Takafumi Arakaki <[email protected]>
1 parent afe9ae9 commit 6012a85

File tree

9 files changed

+392
-153
lines changed

9 files changed

+392
-153
lines changed

NEWS.md

+2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ Build system changes
2828
New library functions
2929
---------------------
3030

31+
* Two argument methods `findmax(f, domain)`, `argmax(f, domain)` and the corresponding `min` versions ([#27613]).
32+
* `isunordered(x)` returns true if `x` is value that is normally unordered, such as `NaN` or `missing`.
3133

3234
New library features
3335
--------------------

base/array.jl

-134
Original file line numberDiff line numberDiff line change
@@ -2205,140 +2205,6 @@ findall(x::Bool) = x ? [1] : Vector{Int}()
22052205
findall(testf::Function, x::Number) = testf(x) ? [1] : Vector{Int}()
22062206
findall(p::Fix2{typeof(in)}, x::Number) = x in p.x ? [1] : Vector{Int}()
22072207

2208-
"""
2209-
findmax(itr) -> (x, index)
2210-
2211-
Return the maximum element of the collection `itr` and its index or key.
2212-
If there are multiple maximal elements, then the first one will be returned.
2213-
If any data element is `NaN`, this element is returned.
2214-
The result is in line with `max`.
2215-
2216-
The collection must not be empty.
2217-
2218-
# Examples
2219-
```jldoctest
2220-
julia> findmax([8,0.1,-9,pi])
2221-
(8.0, 1)
2222-
2223-
julia> findmax([1,7,7,6])
2224-
(7, 2)
2225-
2226-
julia> findmax([1,7,7,NaN])
2227-
(NaN, 4)
2228-
```
2229-
"""
2230-
findmax(a) = _findmax(a, :)
2231-
2232-
function _findmax(a, ::Colon)
2233-
p = pairs(a)
2234-
y = iterate(p)
2235-
if y === nothing
2236-
throw(ArgumentError("collection must be non-empty"))
2237-
end
2238-
(mi, m), s = y
2239-
i = mi
2240-
while true
2241-
y = iterate(p, s)
2242-
y === nothing && break
2243-
m != m && break
2244-
(i, ai), s = y
2245-
if ai != ai || isless(m, ai)
2246-
m = ai
2247-
mi = i
2248-
end
2249-
end
2250-
return (m, mi)
2251-
end
2252-
2253-
"""
2254-
findmin(itr) -> (x, index)
2255-
2256-
Return the minimum element of the collection `itr` and its index or key.
2257-
If there are multiple minimal elements, then the first one will be returned.
2258-
If any data element is `NaN`, this element is returned.
2259-
The result is in line with `min`.
2260-
2261-
The collection must not be empty.
2262-
2263-
# Examples
2264-
```jldoctest
2265-
julia> findmin([8,0.1,-9,pi])
2266-
(-9.0, 3)
2267-
2268-
julia> findmin([7,1,1,6])
2269-
(1, 2)
2270-
2271-
julia> findmin([7,1,1,NaN])
2272-
(NaN, 4)
2273-
```
2274-
"""
2275-
findmin(a) = _findmin(a, :)
2276-
2277-
function _findmin(a, ::Colon)
2278-
p = pairs(a)
2279-
y = iterate(p)
2280-
if y === nothing
2281-
throw(ArgumentError("collection must be non-empty"))
2282-
end
2283-
(mi, m), s = y
2284-
i = mi
2285-
while true
2286-
y = iterate(p, s)
2287-
y === nothing && break
2288-
m != m && break
2289-
(i, ai), s = y
2290-
if ai != ai || isless(ai, m)
2291-
m = ai
2292-
mi = i
2293-
end
2294-
end
2295-
return (m, mi)
2296-
end
2297-
2298-
"""
2299-
argmax(itr)
2300-
2301-
Return the index or key of the maximum element in a collection.
2302-
If there are multiple maximal elements, then the first one will be returned.
2303-
2304-
The collection must not be empty.
2305-
2306-
# Examples
2307-
```jldoctest
2308-
julia> argmax([8,0.1,-9,pi])
2309-
1
2310-
2311-
julia> argmax([1,7,7,6])
2312-
2
2313-
2314-
julia> argmax([1,7,7,NaN])
2315-
4
2316-
```
2317-
"""
2318-
argmax(a) = findmax(a)[2]
2319-
2320-
"""
2321-
argmin(itr)
2322-
2323-
Return the index or key of the minimum element in a collection.
2324-
If there are multiple minimal elements, then the first one will be returned.
2325-
2326-
The collection must not be empty.
2327-
2328-
# Examples
2329-
```jldoctest
2330-
julia> argmin([8,0.1,-9,pi])
2331-
3
2332-
2333-
julia> argmin([7,1,1,6])
2334-
2
2335-
2336-
julia> argmin([7,1,1,NaN])
2337-
4
2338-
```
2339-
"""
2340-
argmin(a) = findmin(a)[2]
2341-
23422208
# similar to Matlab's ismember
23432209
"""
23442210
indexin(a, b)

base/exports.jl

+1
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,7 @@ export
657657
isequal,
658658
ismutable,
659659
isless,
660+
isunordered,
660661
ifelse,
661662
objectid,
662663
sizeof,

base/operators.jl

+58-1
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ is defined, it is expected to satisfy the following:
141141
`isless(x, y) && isless(y, z)` implies `isless(x, z)`.
142142
143143
Values that are normally unordered, such as `NaN`,
144-
are ordered in an arbitrary but consistent fashion.
144+
are ordered after regular values.
145145
[`missing`](@ref) values are ordered last.
146146
147147
This is the default comparison used by [`sort`](@ref).
@@ -168,6 +168,63 @@ isless(x::AbstractFloat, y::AbstractFloat) = (!isnan(x) & (isnan(y) | signless(x
168168
isless(x::Real, y::AbstractFloat) = (!isnan(x) & (isnan(y) | signless(x, y))) | (x < y)
169169
isless(x::AbstractFloat, y::Real ) = (!isnan(x) & (isnan(y) | signless(x, y))) | (x < y)
170170

171+
"""
172+
isgreater(x, y)
173+
174+
Not the inverse of `isless`! Test whether `x` is greater than `y`, according to
175+
a fixed total order compatible with `min`.
176+
177+
Defined with `isless`, this function is usually `isless(y, x)`, but `NaN` and
178+
[`missing`](@ref) are ordered as smaller than any ordinary value with `missing`
179+
smaller than `NaN`.
180+
181+
So `isless` defines an ascending total order with `NaN` and `missing` as the
182+
largest values and `isgreater` defines a descending total order with `NaN` and
183+
`missing` as the smallest values.
184+
185+
!!! note
186+
187+
Like `min`, `isgreater` orders containers (tuples, vectors, etc)
188+
lexigraphically with `isless(y, x)` rather than recursively with itself:
189+
190+
```jldoctest
191+
julia> Base.isgreater(1, NaN) # 1 is greater than NaN
192+
true
193+
194+
julia> Base.isgreater((1,), (NaN,)) # But (1,) is not greater than (NaN,)
195+
false
196+
197+
julia> sort([1, 2, 3, NaN]; lt=Base.isgreater)
198+
4-element Vector{Float64}:
199+
3.0
200+
2.0
201+
1.0
202+
NaN
203+
204+
julia> sort(tuple.([1, 2, 3, NaN]); lt=Base.isgreater)
205+
4-element Vector{Tuple{Float64}}:
206+
(NaN,)
207+
(3.0,)
208+
(2.0,)
209+
(1.0,)
210+
```
211+
212+
# Implementation
213+
This is unexported. Types should not usually implement this function. Instead, implement `isless`.
214+
"""
215+
isgreater(x, y) = isunordered(x) || isunordered(y) ? isless(x, y) : isless(y, x)
216+
217+
"""
218+
isunordered(x)
219+
220+
Return true if `x` is a value that is not normally orderable, such as `NaN` or `missing`.
221+
222+
!!! compat "Julia 1.7"
223+
This function requires Julia 1.7 or later.
224+
"""
225+
isunordered(x) = false
226+
isunordered(x::AbstractFloat) = isnan(x)
227+
isunordered(x::Missing) = true
171228

172229
function ==(T::Type, S::Type)
173230
@_pure_meta

0 commit comments

Comments
 (0)