How do I get the minimum or maximum value of an it

2019-01-20 13:10发布

I understand why the floats don't have an implementation for Ord but that doesn't particularly help me when I want to be lazy and use iterators.

Is there a workaround or an easy way to take the minimum / min / min_by of an iterator containing floating point numbers?

I know one can sort (which is slow) or wrap it in another type and implement the needed trades (which is verbose) but I am hoping for something a little more elegant.

3条回答
一夜七次
2楼-- · 2019-01-20 13:19

If you know your data does not contain NaNs, then assert that fact by unwrapping the comparison:

fn example(x: &[f64]) -> Option<f64> {
    x.iter()
        .cloned()
        .min_by(|a, b| a.partial_cmp(b).expect("Tried to compare a NaN"))
}

If your data may have NaNs, you need to handle that case specifically. One solution is to say that all 16,777,214 NaN values are equal to each other and are always greater than or less than other numbers:

use std::cmp::Ordering;

fn example(x: &[f64]) -> Option<f64> {
    x.iter()
        .cloned()
        .min_by(|a, b| {
            // all NaNs are greater than regular numbers
            match (a.is_nan(), b.is_nan()) {
                (true, true) => Ordering::Equal,
                (true, false) => Ordering::Greater,
                (false, true) => Ordering::Less,
                _ => a.partial_cmp(b).unwrap(),
            }
        })
}

There are numerous crates available that can be used to give you whichever semantics your code needs.


You should not use partial_cmp(b).unwrap_or(Ordering::Equal) because it provides unstable results when NaNs are present, but it leads the reader into thinking that they are handled:

use std::cmp::Ordering;
use std::f64;

fn example(x: &[f64]) -> Option<f64> {
    x.iter()
        .cloned()
        .min_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal))
}

fn main() {
    println!("{:?}", example(&[f64::NAN, 1.0]));
    println!("{:?}", example(&[1.0, f64::NAN]));
}
Some(NaN)
Some(1.0)
查看更多
再贱就再见
3楼-- · 2019-01-20 13:23

Floats have their own min and max methods that handle NaN consistently, so you can fold over the iterator:

use std::f64;

fn main() {
    let x = [2.0, 1.0, -10.0, 5.0, f64::NAN];

    let min = x.iter().fold(f64::INFINITY, |a, &b| a.min(b));
    println!("{}", min);
}

Prints -10.

If you want different NaN handling, you can use PartialOrd::partial_cmp. For example, if you wish to propagate NaNs, fold with:

use std::f64;
use std::cmp::Ordering;

fn main() {
    let x = [2.0, 1.0, -10.0, 5.0, f64::NAN];

    let min = x.iter().fold(f64::INFINITY, |a, &b| {
        match PartialOrd::partial_cmp(&a, &b) {
            None => f64::NAN,
            Some(Ordering::Less) => a,
            Some(_) => b,
        }
    });
    println!("{}", min);
}
查看更多
Juvenile、少年°
4楼-- · 2019-01-20 13:40

Perhaps like this?

fn main() {
    use std::cmp::Ordering;
    let mut x = [2.0, 1.0, -10.0, 5.0];
    x.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
    println!("min in x: {:?}", x);
}

One thing I struggled with is that sort_by mutates the vector in place so you can't use it in a chain directly.

查看更多
登录 后发表回答