对于BTreeMap和其他依赖于Ord的东西,是否有等效于C++比较器对象?
Is there an equivalent to C++ comparator objects for BTreeMap and other things that rely on Ord?
我似乎无法用Ord
特征完成与C++比较器对象相同的事情。以这种对更复杂场景的简化为例:
int epsilon = 0.001;
auto less = [epsilon](const double a, const double b) {
if (fabs(a - b) > epsilon) {
return a < b;
}
else {
return false;
}
};
std::map<float, int, decltype(less)> myMap(less);
myMap.insert(std::make_pair(1.0, 1));
myMap.insert(std::make_pair(2.0, 2));
myMap.insert(std::make_pair(3.0, 3));
auto found = myMap.find(1.0001);
assert(found != myMap.end());
assert(found->second == 1);
我希望比较器具有一些运行时上下文,例如 epsilon 值。我不知道您将如何使用BTreeMap
做到这一点,因为我无法将状态添加到Ord
特征中。有没有一些我还没有想出的技巧来做等效的?
编辑
我的例子中有一个错别字,使我无法意识到C++实际上不起作用。我正在重新评估这个问题。
浮点类型不实现Ord
是有原因的。BTreeMap
的内部对Ord
的实现者做出假设,这使得它更有效率,但如果这些假设被证明是不正确的,那么它可能导致未定义的行为,因为这些假设在unsafe
代码中被依赖。浮点不能满足这些假设,因为存在NaN
,表示无穷大的值和浮点运算的性质,这意味着"相同"的数字可以有不同的二进制表示。
您的C++代码可能会遇到相同的问题,但有时具有未定义行为的代码似乎可以正常工作。直到有一天它没有 - 这就是未定义行为的本质!
浮点数适用于表示度量值或值可以具有大量变化的数量级的位置。如果你计算城市之间的距离,数字相差几纳米,你不在乎。你永远不必找到另一个与伦敦到纽约的距离完全相同的城市。更有可能的是,您会很乐意搜索与最近的 1 公里距离相同的城市 - 您可以将其作为整数进行比较。
这让我想到了一个问题,你为什么要使用浮点数作为键?这对您意味着什么,您想在那里存储什么?f64::NAN
是您希望有效的值吗?45.00000000001
和45.00000000001001
"一样"吗?您是否存储非常大的数字和非常小的数字一样可能?对于这些数字来说,精确相等有意义吗?它们是否是计算的结果,可能会产生舍入误差?
不可能告诉你在这里该做什么,但我可以建议你看看你的真正问题,并以反映你真正约束的方式对它进行建模。如果您只关心特定的精度,则将数字舍入到该精度并将它们存储在定点类型、整数或有理数中,所有这些都实现了Ord
。
根据您的C++代码,看起来您对0.001
的精度感到满意。因此,您可以将密钥存储为整数 - 您只需要记住进行转换并根据需要乘以/除以 1000。你将不得不处理NaN
和无穷大的可能性,但你会在安全的代码中做到这一点,所以你不必担心 UB。
以下是如何使用num-rational
箱来获取与C++代码具有类似行为的东西:
extern crate num_rational; // 0.2.1
use num_rational::Rational64;
use std::collections::BTreeMap;
fn in_thousandths(n: f64) -> Rational64 {
// you may want to include further validation here to deal with `NaN` or infinities
let denom = 1000;
Rational64::new_raw((n * denom as f64) as i64, denom)
}
fn main() {
let mut map = BTreeMap::new();
map.insert(in_thousandths(1.0), 1);
map.insert(in_thousandths(2.0), 2);
map.insert(in_thousandths(3.0), 3);
let value = map.get(&1.into());
assert_eq!(Some(&1), value);
}
我认为你应该使用自己的类型并实现自己的 Ord 特征:
#[derive(PartialOrd, Debug)]
struct MyNumber {
value: f64,
}
impl MyNumber {
const EPSILON: f64 = 0.001;
fn new(value: f64) -> Self {
Self { value }
}
}
impl PartialEq for MyNumber {
fn eq(&self, other: &MyNumber) -> bool {
if f64::abs(self.value - other.value) < MyNumber::EPSILON {
return true;
} else {
return false;
}
}
}
impl Eq for MyNumber {}
use std::cmp::Ordering;
impl Ord for MyNumber {
fn cmp(&self, other: &MyNumber) -> Ordering {
if self == other {
Ordering::Equal
} else if self < other {
Ordering::Less
} else {
Ordering::Greater
}
}
}
fn main() {
use std::collections::BTreeMap;
let mut map = BTreeMap::new();
map.insert(MyNumber::new(1.0), 1);
map.insert(MyNumber::new(2.0), 2);
map.insert(MyNumber::new(3.0), 3);
println!("{:?}", map.get(&MyNumber::new(1.0001)));
}
但我认为它不符合BTreeMap的要求。
在 rust 中做到这一点的方法是将你的密钥包装在 newtype 中,并按照你想要的方式实现Ord
:
use std::cmp::Ordering;
use std::collections::BTreeMap;
const EPSILON: f64 = 0.001;
struct MyFloat (f64);
impl Ord for MyFloat {
fn cmp (&self, other: &MyFloat) -> Ordering {
if (self.0 - other.0).abs() < EPSILON {
// I assume that this is what you wanted even though the C++ example
// does the opposite
Ordering::Equal
} else if self.0 < other.0 {
Ordering::Less
} else {
Ordering::Greater
}
}
}
impl PartialOrd for MyFloat {
fn partial_cmp (&self, other: &MyFloat) -> Option<Ordering> {
Some (self.cmp (other))
}
}
impl PartialEq for MyFloat {
fn eq (&self, other: &MyFloat) -> bool {
(self.0 - other.0).abs() < EPSILON
}
}
impl Eq for MyFloat {}
fn main() {
let mut map = BTreeMap::new();
map.insert (MyFloat (1.0), 1);
map.insert (MyFloat (2.0), 2);
map.insert (MyFloat (3.0), 3);
let found = map.get (&MyFloat (1.00001));
println!("Found: {:?}", found);
}
操场
请注意,仅当键有限且间隔超过EPSILON
时,这才有效。
- 什么时候调用组成单元对象的析构函数
- 对RValue对象调用的LValue ref限定成员函数
- CMake-按正确顺序将项目与C运行时对象文件链接
- 空基优化子对象的地址
- 将对象数组的引用传递给函数
- 你能重载对象变量名本身返回的内容吗
- C++使用整数的压缩数组初始化对象
- 找不到成员对象:没有名为get_event()的成员,也处理多态性和向量
- 将对象移动到std::shared_ptr
- 代理对象的常量正确性
- 提升 ASIO 无法识别计时器对象
- 将Ref对象作为类成员
- 将包含C样式数组的对象初始化为成员变量(C++)
- 如何返回一个类的两个对象相加的结果
- 使用std::函数映射对象方法
- 是否需要删除包含对象的"pair"?
- 如何在自删除后将对象设置为nullptr
- 迭代时从向量和内存中删除对象
- 构造对象的歧义
- 使用"std::unordereded_map"映射到"std::list"对象