rust 如何在浮点数的Vec上进行二分查找?

4dbbbstv  于 2023-06-30  发布在  其他
关注(0)|答案(6)|浏览(120)

如果你有一个Vec<u32>,你可以使用slice::binary_search方法。
由于我不明白的原因,f32f64没有实现Ord。由于基本类型来自标准库,您无法自己在它们上实现Ord,因此似乎无法使用此方法。
如何有效地做到这一点?
我真的必须将f64 Package 在 Package 器结构中并在其上实现Ord吗?必须这样做似乎非常痛苦,并且涉及大量的transmute,以不安全地来回转换数据块,没有任何理由。

i7uq4tfw

i7uq4tfw1#

由于我不明白的原因,f32和f64没有实现Order。
因为floating point is hard!简短的版本是浮点数有一个特殊的值NaN -不是一个数字。IEEE浮点数规范规定1 < NaN1 > NaNNaN == NaN都是false
Ord说:
形成total order的类型的Trait。
这意味着比较需要具有 * 总体性 *:
a ≤ B或b ≤ a
但是我们刚刚看到浮点数没有这个属性。
所以,是的,您需要创建一个 Package 器类型,以某种方式处理large number of NaN values的比较。也许在你的例子中,你可以Assertfloat值永远不是NaN,然后调用常规的PartialOrd trait。下面是一个例子:

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);
}
hgb9j2n6

hgb9j2n62#

其中一个slice方法是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),
    }
}
x8goxv8g

x8goxv8g3#

自Rust 1.62.0起,名为.total_cmp()的浮点数的内置总排序比较方法现在已经稳定。这实现了IEEE 754中定义的总排序,每个可能的f64位值都被不同地排序,包括正零和负零,以及所有可能的NaN。
浮点数仍然不能实现Ord,所以它们不能直接排序,但是样板文件已经被减少到一行,没有任何外部导入或恐慌的机会:

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}).");
        }
    }
}
6rqinv9w

6rqinv9w4#

如果您确定浮点值永远不会是NaN,那么可以使用decorum中的 Package 器来表达这种语义。具体来说,类型Ordered实现了Ord,每当程序试图做一些无效的事情时,它就会死机:

use decorum::Ordered;

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

7rtdyuoh5#

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));
watbbzwu

watbbzwu6#

您可以在newtype上使用nutypefinite验证来派生Ord,而不需要太多的样板文件。finite验证排除了NaNInfinity,这使得nutype能够安全地导出Ord
下面是一个例子:

use nutype::nutype;

#[nutype(validate(finite))]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Float(f64);

fn main() {
    let raw_data = vec![1.1, 2.5, 0.1, 4.5];
    let mut data: Vec<Float> = raw_data
        .into_iter()
        .map(|val| Float::new(val).unwrap())
        .collect();

    data.sort();
    println!("data = {data:?}");

    let idx25 = data.binary_search(&Float::new(2.5).unwrap());
    println!("2.5 index = {idx25:?}");

    let idx36 = data.binary_search(&Float::new(3.6).unwrap());
    println!("3.6 index = {idx36:?}");
}

输出:

data = [Float(0.1), Float(1.1), Float(2.5), Float(4.5)]
2.5 index = Ok(2)
3.6 index = Err(3)

请注意,Float::new(val)返回Result。传递类似Float::new(0.0/0.0)的内容将返回错误。

相关问题