Reputation: 395
I have an array, let's say:
let arr: Vec<f64> = vec![1.5, 1.1, -1.6, 0.9, -0.5];
I want the absolute maximum of this array:
let abs_max = arr."some functions";
println!("{}", abs_max); // Gives 1.6 (or -1.6, but not 1.5).
Is there a smart, maybe almost inline, way of doing this? Or do i have to create my own function which iterates through all the values by for loop and compares the values?
I've tried the following suggestion made in this post:
let abs_max = arr.iter().max_by(|a, b| a.abs().total_cmp(b.abs()))
And this code gives the following error:
error[E0599]: no method named `abs` found for reference `&&{float}` in the current scope
--> src\main.rs:51:44
|
51 | let abs_max = arr.iter().max_by(|a, b| a.abs().total_cmp(b.abs()));
| ^^^ method not found in `&&{float}`
error[E0599]: no method named `abs` found for reference `&&{float}` in the current scope
--> src\main.rs:51:62
|
51 | let abs_max = arr.iter().max_by(|a, b| a.abs().total_cmp(b.abs()));
| ^^^ method not found in `&&{float}`
Upvotes: 2
Views: 1238
Reputation: 70337
In Rust, we usually operate on iterators rather than collections like vectors directly, so all of the juicy "operate on a collection" functions are going to be in std::iter::Iterator
.
Now, Iterator
has a max
function, which would work for integers, but it won't work for floats, because floats are not totally ordered (NaN
, as usual, is a problem).
If you had a collection of integers or anything else that implemented the Ord
trait, you could write
arr.iter().max()
which would return an Option<T>
containing the maximum value, or None
if the collection was empty.
However, f64
and the other floating-point types don't implement Ord
. Fortunately, the wonderful writers of the Rust documentation for Iterator::max
thought of this.
Note that
f32
/f64
doesn’t implementOrd
due to NaN being incomparable. You can work around this by usingIterator::reduce
:assert_eq!( [2.4, f32::NAN, 1.3] .into_iter() .reduce(f32::max) .unwrap(), 2.4 );
So we can apply reduce
to f64::max
(which is like Ord::max
except that it works for the non-total ordering of f64
). Adapting a bit to your use case, we get
arr.iter().copied().reduce(f64::max)
Again, this returns an Option<f64>
which is empty if the initial collection is empty. You can unwrap
if you know the initial collection to be nonempty. Also, if you're never using the array again after this point (i.e. you can pass ownership of it to the iterator), you can use arr.into_iter()
rather than arr.iter().copied()
to save yourself a copy of each element.
This compares using f64::max
, the "usual" ordering of f64
. But it sounds like you want a custom ordering, namely the maximum by absolute value. We can use max_by
to get this custom ordering.
arr.iter().max_by(|a, b| a.abs().total_cmp(&b.abs()))
Finally, if this is a large project and you don't mind pulling in external dependencies, I recommend OrderedFloat
for dealing with all of the awkwardness around NaN and floating point types. It provides an Ord
instance for a float-like type which sorts NaN as part of the total ordering, rather than following the (frankly bizarre) IEEE rules for ordering floats. With that library, your maximum becomes
arr.iter().max_by_key(|x| OrderedFloat(x.abs())
Upvotes: 4