Skip to content

Commit 33aef13

Browse files
committed
Add 2-arg versions of findmax/min, argmax/min
Fixes JuliaLang#27613. Related: JuliaLang#27639, JuliaLang#27612, JuliaLang#34674. Thanks to @tkf, @StefanKarpinski and @drewrobson for their assistance with this PR.
1 parent 56785e8 commit 33aef13

File tree

4 files changed

+229
-134
lines changed

4 files changed

+229
-134
lines changed

NEWS.md

+1
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ New library functions
9595
* New function `Base.rest` for taking the rest of a collection, starting from a specific
9696
iteration state, in a generic way ([#37410]).
9797
* New function `isgreater(a, b)` defines a descending total order where unorderable values and missing are ordered smaller than any regular value.
98+
* Two argument methods `findmax(f, domain)`, `argmax(f, domain)` and the corresponding `min` versions ([#27613]).
9899

99100
New library features
100101
--------------------

base/array.jl

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

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

base/reduce.jl

+196
Original file line numberDiff line numberDiff line change
@@ -762,6 +762,202 @@ Inf
762762
"""
763763
minimum(a; kw...) = mapreduce(identity, min, a; kw...)
764764

765+
## findmax, findmin, argmax & argmin
766+
767+
"""
768+
findmax(f, domain) -> (f(x), x)
769+
findmax(f)
770+
771+
Returns a pair of a value in the codomain (outputs of `f`) and the corresponding
772+
value in the `domain` (inputs to `f`) such that `f(x)` is maximised. If there
773+
are multiple maximal points, then the first one will be returned.
774+
775+
When `domain` is provided it may be any iterable and must not be empty.
776+
777+
When `domain` is omitted, `f` must have an implicit domain. In particular, if
778+
`f` is an indexable collection, it is interpreted as a function mapping keys
779+
(domain) to values (codomain), i.e. `findmax(itr)` returns the maximal element
780+
of the collection `itr` and its index.
781+
782+
Values are compared with `isless`.
783+
784+
# Examples
785+
786+
```jldoctest
787+
julia> findmax(identity, 5:9)
788+
(9, 9)
789+
790+
julia> findmax(-, 1:10)
791+
(-1, 1)
792+
793+
julia> findmax(cos, 0:π/2:2π)
794+
(1.0, 0.0)
795+
796+
julia> findmax([8,0.1,-9,pi])
797+
(8.0, 1)
798+
799+
julia> findmax([1,7,7,6])
800+
(7, 2)
801+
802+
julia> findmax([1,7,7,NaN])
803+
(NaN, 4)
804+
```
805+
806+
"""
807+
findmax(f, domain) = mapfoldl(x -> (f(x), x), _rf_findmax, domain)
808+
_rf_findmax((fm, m), (fx, x)) = isless(fm, fx) ? (fx, x) : (fm, m)
809+
810+
"""
811+
findmin(f, domain) -> (f(x), x)
812+
findmin(f)
813+
814+
Returns a pair of a value in the codomain (outputs of `f`) and the corresponding
815+
value in the `domain` (inputs to `f`) such that `f(x)` is minimised. If there
816+
are multiple minimal points, then the first one will be returned.
817+
818+
When `domain` is provided it may be any iterable and must not be empty.
819+
820+
When `domain` is omitted, `f` must have an implicit domain. In particular, if
821+
`f` is an indexable collection, it is interpreted as a function mapping keys
822+
(domain) to values (codomain), i.e. `findmin(itr)` returns the minimal element
823+
of the collection `itr` and its index.
824+
825+
Values are compared with `isgreater`.
826+
827+
# Examples
828+
829+
```jldoctest
830+
julia> findmin(identity, 5:9)
831+
(5, 5)
832+
833+
julia> findmin(-, 1:10)
834+
(-10, 10)
835+
836+
julia> findmin(cos, 0:π/2:2π)
837+
(-1.0, 3.141592653589793)
838+
839+
julia> findmin([8,0.1,-9,pi])
840+
(-9, 3)
841+
842+
julia> findmin([1,7,7,6])
843+
(1, 1)
844+
845+
julia> findmin([1,7,7,NaN])
846+
(NaN, 4)
847+
```
848+
849+
"""
850+
findmin(f, domain) = mapfoldl(x -> (f(x), x), _rf_findmin, domain)
851+
_rf_findmin((fm, m), (fx, x)) = isgreater(fm, fx) ? (fx, x) : (fm, m)
852+
853+
findmax(a) = _findmax(a, :)
854+
855+
function _findmax(a, ::Colon)
856+
p = pairs(a)
857+
y = iterate(p)
858+
if y === nothing
859+
throw(ArgumentError("collection must be non-empty"))
860+
end
861+
(mi, m), s = y
862+
i = mi
863+
while true
864+
y = iterate(p, s)
865+
y === nothing && break
866+
m != m && break
867+
(i, ai), s = y
868+
if ai != ai || isless(m, ai)
869+
m = ai
870+
mi = i
871+
end
872+
end
873+
return (m, mi)
874+
end
875+
876+
findmin(a) = _findmin(a, :)
877+
878+
function _findmin(a, ::Colon)
879+
p = pairs(a)
880+
y = iterate(p)
881+
if y === nothing
882+
throw(ArgumentError("collection must be non-empty"))
883+
end
884+
(mi, m), s = y
885+
i = mi
886+
while true
887+
y = iterate(p, s)
888+
y === nothing && break
889+
m != m && break
890+
(i, ai), s = y
891+
if ai != ai || isless(ai, m)
892+
m = ai
893+
mi = i
894+
end
895+
end
896+
return (m, mi)
897+
end
898+
899+
"""
900+
argmax(f, domain)
901+
argmax(f)
902+
903+
Return a value `x` in the domain of `f` for which `f(x)` is maximised.
904+
If there are multiple maximal values for `f(x)` then the first one will be found.
905+
906+
When `domain` is provided it may be any iterable and must not be empty.
907+
908+
When `domain` is omitted, `f` must have an implicit domain. In particular, if
909+
`f` is an indexable collection, it is interpreted as a function mapping keys
910+
(domain) to values (codomain), i.e. `argmax(itr)` returns the index of the
911+
maximal element in `itr`.
912+
913+
Values are compared with `isless`.
914+
915+
# Examples
916+
```jldoctest
917+
julia> argmax([8,0.1,-9,pi])
918+
1
919+
920+
julia> argmax([1,7,7,6])
921+
2
922+
923+
julia> argmax([1,7,7,NaN])
924+
4
925+
```
926+
"""
927+
argmax(f, domain) = findmax(f, domain)[2]
928+
argmax(f) = findmax(f)[2]
929+
930+
"""
931+
argmin(f, domain)
932+
argmin(f)
933+
934+
Return a value `x` in the domain of `f` for which `f(x)` is minimised.
935+
If there are multiple minimal values for `f(x)` then the first one will be found.
936+
937+
When `domain` is provided it may be any iterable and must not be empty.
938+
939+
When `domain` is omitted, `f` must have an implicit domain. In particular, if
940+
`f` is an indexable collection, it is interpreted as a function mapping keys
941+
(domain) to values (codomain), i.e. `argmin(itr)` returns the index of the
942+
minimal element in `itr`.
943+
944+
Values are compared with `isgreater`.
945+
946+
# Examples
947+
```jldoctest
948+
julia> argmin([8,0.1,-9,pi])
949+
3
950+
951+
julia> argmin([7,1,1,6])
952+
2
953+
954+
julia> argmin([7,1,1,NaN])
955+
4
956+
```
957+
"""
958+
argmin(f, domain) = findmin(f, domain)[2]
959+
argmin(f) = findmin(f)[2]
960+
765961
## all & any
766962

767963
"""

test/reduce.jl

+32
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,38 @@ A = circshift(reshape(1:24,2,3,4), (0,1,1))
387387
end
388388
end
389389

390+
# findmin, findmax, argmin, argmax
391+
392+
@testset "findmin(f, domain)" begin
393+
@test findmin(-, 1:10) == (-10, 10)
394+
@test findmin(identity, [1, 2, 3, missing]) === (missing, missing)
395+
@test findmin(identity, [1, NaN, 3, missing]) === (missing, missing)
396+
@test findmin(identity, [1, missing, NaN, 3]) === (missing, missing)
397+
@test findmin(identity, [1, NaN, 3]) === (NaN, NaN)
398+
@test findmin(identity, [1, 3, NaN]) === (NaN, NaN)
399+
@test all(findmin(cos, 0:π/2:2π) .≈ (-1.0, π))
400+
end
401+
402+
@testset "findmax(f, domain)" begin
403+
@test findmax(-, 1:10) == (-1, 1)
404+
@test findmax(identity, [1, 2, 3, missing]) === (missing, missing)
405+
@test findmax(identity, [1, NaN, 3, missing]) === (missing, missing)
406+
@test findmax(identity, [1, missing, NaN, 3]) === (missing, missing)
407+
@test findmax(identity, [1, NaN, 3]) === (NaN, NaN)
408+
@test findmax(identity, [1, 3, NaN]) === (NaN, NaN)
409+
@test findmax(cos, 0:π/2:2π) == (1.0, 0.0)
410+
end
411+
412+
@testset "argmin(f, domain)" begin
413+
@test argmin(-, 1:10) == 10
414+
@test argmin(sum, Iterators.product(1:5, 1:5)) == (1, 1)
415+
end
416+
417+
@testset "argmax(f, domain)" begin
418+
@test argmax(-, 1:10) == 1
419+
@test argmax(sum, Iterators.product(1:5, 1:5)) == (5, 5)
420+
end
421+
390422
# any & all
391423

392424
@test @inferred any([]) == false

0 commit comments

Comments
 (0)