Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the idiomatic way to get the index of a maximum or minimum floating point value in a slice or Vec in Rust?

Assumption -- The Vec<f32> does not have any NaN values or exhibit any NaN behavior.

Take the following sample set:

0.28  
0.3102
0.9856
0.3679
0.3697
0.46  
0.4311
0.9781
0.9891
0.5052
0.9173
0.932 
0.8365
0.5822
0.9981
0.9977

What is the neatest and most stable way to get the index of the highest value in the above list (values can be negative)?

My initial attempts were along the following lines:

let _tmp = *nets.iter().max_by(|i, j| i.partial_cmp(j).unwrap()).unwrap();    
let _i = nets.iter().position(|&element| element == _tmp).unwrap();

Where nets is a &Vec<f32>. Which to me seems blatantly incorrect.

The Python equivalent of this that works (taking into consideration the above assumption):

_i = nets.index(max(nets))
like image 632
Juxhin Avatar asked Dec 23 '18 11:12

Juxhin


People also ask

How do I get the index of the minimum and maximum values?

Sign in to answer this question. The "min" and "max" functions in MATLAB return the index of the minimum and maximum values, respectively, as an optional second output argument.

How to find the maximum element present in an object?

Then we have used the syntax below to find the row and column number of the maximum element and stored it in the variable “max”. We have made use of the max () function which is used to find the maximum element present in an object. This object can be a Vector, a list, a matrix, a data frame, etc.

What is the index of a radical?

The index is the number to the upper left of a radical symbol, and it tells us which root of the radicand to take. For example, an index of 2 means a square root, an index of 3 means a cube root, and so forth. Depending on the radicand and index, we may get real, pure imaginary, or complex roots.

What is the design philosophy of the rust library?

@Juxhin Please bear in mind that one of the design philosophies of the Rust standard library is to adopt new APIs slowly and to instead encourage functionality to emerge in the community.


4 Answers

Is there a reason why this wouldn't work?

>= Rust 1.62.0 (2022-06-30)

use std::cmp::Ordering;
   
fn example(nets: &Vec<f32>) {
    let index_of_max: Option<usize> = nets
        .iter()
        .enumerate()
        .max_by(|(_, a), (_, b)| a.total_cmp(b))
        .map(|(index, _)| index);
}

< Rust 1.62.0 (2022-06-30)

use std::cmp::Ordering;
   
fn example(nets: &Vec<f32>) {
    let index_of_max: Option<usize> = nets
        .iter()
        .enumerate()
        .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(Ordering::Equal))
        .map(|(index, _)| index);
}
like image 109
Emily Avatar answered Nov 02 '22 23:11

Emily


I took the answer from @Akiner Alkan and tweaked it a bit, here's a simple one-liner without any unwrap, doing the job:

let maxi = my_vec.iter().enumerate().fold((0, 0.0), |max, (ind, &val)| if val > max.1 {(ind, val)} else {max});

(PS: new to rust and first post in StackOverflow, don't judge me if I do it wrong :D)

like image 40
litchipi Avatar answered Nov 03 '22 01:11

litchipi


The reason why this is tricky is because f32 does not implement Ord. That is because NaN values prevent floating point numbers from forming a total order, which violates the contract of Ord.

There are 3rd party crates that work around this by defining a numeric type wrapper which is not allowed to contain a NaN. One example is ordered-float. If you use this crate to first prepare the collection to contain NotNan values, then you can write code very close to your original idea:

use ordered_float::NotNan;

let non_nan_floats: Vec<_> = nets.iter()
    .cloned()
    .map(NotNan::new)       // Attempt to convert each f32 to a NotNan
    .filter_map(Result::ok) // Unwrap the `NotNan`s and filter out the `NaN` values 
    .collect();

let max = non_nan_floats.iter().max().unwrap();
let index = non_nan_floats.iter().position(|element| element == max).unwrap();

Add this to Cargo.toml:

[dependencies]
ordered-float = "1.0.1"

Bonus material: The type conversion can be made truly zero-cost (assuming you are really sure that there are no NaN values!), by taking advantage of the fact that NotNan has a transparent representation:

let non_nan_floats: Vec<NotNan<f32>> = unsafe { mem::transmute(nets) };
like image 20
Peter Hall Avatar answered Nov 03 '22 00:11

Peter Hall


I will probably do something like this:

fn main() -> Result<(), Box<std::error::Error>> {
    let samples = vec![
        0.28, 0.3102, 0.9856, 0.3679, 0.3697, 0.46, 0.4311, 0.9781, 0.9891, 0.5052, 0.9173, 0.932,
        0.8365, 0.5822, 0.9981, 0.9977,
    ];

    // Use enumerate to get the index
    let mut iter = samples.iter().enumerate();
    // we get the first entry
    let init = iter.next().ok_or("Need at least one input")?;
    // we process the rest
    let result = iter.try_fold(init, |acc, x| {
        // return None if x is NaN
        let cmp = x.1.partial_cmp(acc.1)?;
        // if x is greater the acc
        let max = if let std::cmp::Ordering::Greater = cmp {
            x
        } else {
            acc
        };
        Some(max)
    });
    println!("{:?}", result);

    Ok(())
}

This could be implemented by adding a trait on Iterator with for example the function try_max_by.

like image 29
Stargateur Avatar answered Nov 03 '22 01:11

Stargateur