【问题标题】:How to do a binary search on a Vec of floats?如何对浮点数的 Vec 进行二进制搜索?
【发布时间】:2015-03-30 15:25:57
【问题描述】:

如果您有Vec<u32>,您将使用slice::binary_search 方法。

由于我不明白的原因,f32f64 没有实现 Ord。由于原始类型来自标准库,您不能自己对它们实现Ord,因此看来您不能使用此方法。

您如何有效地做到这一点?

我真的必须将f64 包装在一个包装结构中并在其上实现Ord 吗?必须这样做似乎非常痛苦,并且涉及大量transmute 来不安全地无缘无故地来回转换数据块。

【问题讨论】:

    标签: rust


    【解决方案1】:

    由于我不明白的原因,f32 和 f64 没有实现 Ord。

    因为floating point is hard!简短的版本是浮点数有一个特殊的值 NaN - 不是数字。浮点数的 IEEE 规范指出 1 < NaN1 > NaNNaN == NaN 都是 false

    Ord 说:

    构成total order的类型的特征。

    这意味着比较需要有totality

    a ≤ b 或 b ≤ a

    但是我们刚刚看到浮点没有这个属性。

    所以是的,您需要创建一个包装器类型,以某种方式处理比较large number of NaN values。也许您的情况可以断言浮点值永远不是 NaN,然后​​调用常规的 PartialOrd 特征。这是一个例子:

    use std::cmp::Ordering;
    
    #[derive(PartialEq,PartialOrd)]
    struct NonNan(f64);
    
    impl NonNan {
        fn new(val: f64) -> Option<NonNan> {
            if val.is_nan() {
                None
            } else {
                Some(NonNan(val))
            }
        }
    }
    
    impl Eq for NonNan {}
    
    impl Ord for NonNan {
        fn cmp(&self, other: &NonNan) -> Ordering {
            self.partial_cmp(other).unwrap()
        }
    }
    
    fn main() {
        let mut v: Vec<_> = [2.0, 1.0, 3.0].iter().map(|v| NonNan::new(*v).unwrap()).collect();
        v.sort();
        let r = v.binary_search(&NonNan::new(2.0).unwrap());
        println!("{:?}", r);
    }
    

    【讨论】:

    【解决方案2】:

    其中一个切片方法是binary_search_by,您可以使用它。 f32/f64 实现PartialOrd,所以如果你知道他们永远不可能是NaN,你可以解开partial_cmp的结果:

    fn main() {
        let values = [1.0, 2.0, 3.0, 4.0, 5.0];
        let location = values.binary_search_by(|v| {
            v.partial_cmp(&3.14).expect("Couldn't compare values")
        });
    
        match location {
            Ok(i) => println!("Found at {}", i),
            Err(i) => println!("Not found, could be inserted at {}", i),
        }
    }
    

    【讨论】:

      【解决方案3】:

      https://github.com/emerentius/ord_subset 实现了一个 ord_subset_binary_search() 方法,您可以使用它。

      来自他们的自述文件:

      let mut s = [5.0, std::f64::NAN, 3.0, 2.0];
      s.ord_subset_sort();
      assert_eq!(&s[0..3], &[2.0, 3.0, 5.0]);
      assert_eq!(s.ord_subset_binary_search(&5.0), Ok(2));
      
      assert_eq!(s.iter().ord_subset_max(), Some(&5.0));
      assert_eq!(s.iter().ord_subset_min(), Some(&2.0));
      

      【讨论】:

        【解决方案4】:

        如果您确定您的浮点值永远不会是 NaN,则可以使用 decorum 中的包装器来表达此语义。具体来说,Ordered 类型实现了 Ord 并在程序尝试执行无效操作时发生恐慌:

        use decorum::Ordered;
        
        fn foo() {
            let ordered = Ordered<f32>::from_inner(10.);
            let normal = ordered.into()
        }
        

        【讨论】:

          【解决方案5】:

          一种名为 .total_cmp() is available on nighty 的浮点数的内置总排序比较方法,应该会在几个月内稳定,排除任何令人惊讶的问题。 (vote to stablize the feature recently passed。)这实现了 IEEE 754 中定义的总排序,每个可能的 f64 位值都被明确排序,包括正零和负零,以及所有可能的 NaN。

          浮点数仍然不会实现Ord,因此它们不能直接排序,但样板已被缩减为一行,没有任何外部导入或恐慌的机会:

          #![feature(total_cmp)]
          
          fn main() {
              let mut v: Vec<f64> = vec![2.0, 2.5, -0.5, 1.0, 1.5];
              v.sort_by(f64::total_cmp);
          
              let target = 1.25;
              let result = v.binary_search_by(|probe| probe.total_cmp(&target));
          
              match result {
                  Ok(index) => {
                      println!("Found target {target} at index {index}.");
                  }
                  Err(index) => {
                      println!("Did not find target {target} (expected index was {index}).");
                  }
              }
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2013-12-12
            • 1970-01-01
            • 2013-09-29
            • 1970-01-01
            • 2017-02-23
            • 2010-11-01
            • 1970-01-01
            相关资源
            最近更新 更多