7

I want to use max_by_key to get the maximum value from a vector of f64s based on a certain key. This is a simple example, with a small vector and abs as the key:

let a: Vec<f64> = vec![-3.0, 0.2, 1.4];
*a.iter().max_by_key(|n| n.abs()).unwrap()

However, since f64 does not implement Ord, I get

    error[E0277]: the trait bound `f64: std::cmp::Ord` is not satisfied
 --> src/main.rs:3:15
  |
3 |     *a.iter().max_by_key(|n| n.abs()).unwrap();
  |               ^^^^^^^^^^ the trait `std::cmp::Ord` is not implemented for `f64`

Similarly, sort_by_key fails with the same error:

a.sort_by_key(|n| n.abs())

I know I can get around the partial ordering restriction to sort a vector of floats with sort_by

b.sort_by(|m, n| m.partial_cmp(n).unwrap_or(Less))

but that would have to be called on a vector b for which I've computed the key (in this case abs) for each element of a, and then I would have to go back and find the corresponding element of a, which seems complicated and slow. As the number of items in the list grows, I'd like to minimize passes through the data.

Are there any workarounds?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
jayelm
  • 7,236
  • 5
  • 43
  • 61
  • 2
    [Implement `Ord`](http://stackoverflow.com/questions/29884402/how-do-i-implement-ord-for-a-struct) for a wrapper type around the `f64`. This forces you to define how `NaN` is handled. [Examples exist](http://stackoverflow.com/q/28247990/155423) for similar things. – Shepmaster May 10 '16 at 01:35
  • Do **not** use `.unwrap_or(Less)` — this [gives unstable results in the presence of NaN](https://stackoverflow.com/a/50308360/155423). – Shepmaster May 12 '18 at 16:27
  • 1
    Duplicate of [How do I get the minimum or maximum value of an iterator containing floating point numbers?](https://stackoverflow.com/questions/28446632/how-do-i-get-the-minimum-or-maximum-value-of-an-iterator-containing-floating-poi) – E_net4 Feb 03 '22 at 11:14

1 Answers1

6

If you do not want to create a wrapper type you can use the ordered_float or ord_subset crate. For example

extern crate ordered_float;
extern crate ord_subset;

#[test]
fn test_example_() {
    use ordered_float::OrderedFloat;
    // OrderedFloat -> NaN is greater than all other values and equal to itself.
    // NotNaN -> NotNaN::new panics if called with NaN.

    let mut a: Vec<f64> = vec![-3.0, 0.2, 1.4];

    let max = *a.iter().max_by_key(|n| OrderedFloat(n.abs())).unwrap();
    assert_eq!(-3.0, max);

    a.sort_by_key(|n| OrderedFloat(n.abs()));
    assert_eq!(vec![0.2, 1.4, -3.0], a);
}

#[test]
fn test_example_ord_subset() {
    use ord_subset::OrdSubsetIterExt;

    let a: Vec<f64> = vec![-3.0, 0.2, 1.4];

    // For f64, NaN is ignored.
    let max = *a.iter().ord_subset_max_by_key(|n| n.abs()).unwrap();
    assert_eq!(-3.0, max);

    // ord_subset does not help with the sorting problem in the question
}
malbarbo
  • 10,717
  • 1
  • 42
  • 57
  • Seems like no intrinsic way to do this without implementing/including a wrapper type, so I'll accept this. Thanks! – jayelm May 10 '16 at 21:54