问题 当比较依赖于数据而不是比较项目的一部分时,如何实现Ord?


我有一个只包含一个小结构 i32

struct MyStruct {
   value: i32,
}

我想实施 Ord 为了存储 MyStruct 在一个 BTreeMap 或任何其他要求您拥有的数据结构 Ord 在它的元素上。

就我而言,比较两个实例 MyStruct 不依赖于 value在其中,但询问另一个数据结构(字典),并且该数据结构对于每个实例都是唯一的 BTreeMap 我会创造。理想情况下,它看起来像这样:

impl Ord for MyStruct {
    fn cmp(&self, other: &Self, dict: &Dictionary) -> Ordering {
        dict.lookup(self.value).cmp(dict.lookup(other.value))
    }
}

然而,这是不可能的,因为一个 Ord 实现只能访问两个实例 MyStruct,仅此而已。

一种解决方案是存储指向字典的指针 MyStruct 但这太过分了。 MyStruct 应该是一个简单的包装器,指针会增加一倍。另一个解决方案是使用静态全局,但这也不是一个好的解决方案。

在C ++中,解决方案很简单:大多数STL算法/数据结构允许您传递比较器,它可以是具有某种状态的函数对象。所以我相信Rust会有一个成语来以某种方式匹配它,有什么方法可以实现这一点吗?


5114
2018-03-04 02:24


起源

实际上,在64位环境中,指针会翻两番 MyStruct的大小从32位到128位(64位指针,32位值,32位填充)。 - Matthieu M.
你是否关心你最终的实际订购,或者你只是想把它放在 MyStruct 地图中的实例?如果是后者,那么使用a会更有意义 HashMap - TheHansinator
哦,好吧,为了这个问题,让我们假设我们需要订单。我想类似的问题会发生在 BinaryHeap如果你需要不时弹出最小/最大值。 - loudandclear


答案:


我记得关于是否允许自定义比较器是否值得讨论的争论,并且当大多数时候通过使用新的(包装)类型并重新定义时可以实现相同的效果,这使得复杂的API变得很复杂。 PartialOrd 为了它。

这最终是一种权衡:权衡API简单性与异常需求(可能总结为访问外部资源)。


在您的具体情况下,有两种解决方案:

  • 按照预期的方式使用API​​:创建一个包含两个实例的包装器结构 MyStruct 和字典的引用,然后定义 Ord 在那个包装器上并将其用作关键字 BTreeMap
  • 以某种方式规避API ......

我个人建议在开始试图绕过它之前,先按照预期使用API​​并进行测量。


@ker 非常友好地提供了以下关于在评论中实现包装的说明(游乐场版):

#[derive(Eq, PartialEq, Debug)]
struct MyStruct {
   value: i32,
}

#[derive(Debug)]
struct MyStructAsKey<'a> {
    inner: MyStruct,
    dict: &'a Dictionary,
}

impl<'a> Eq for MyStructAsKey<'a> {}

impl<'a> PartialEq for MyStructAsKey<'a> {
    fn eq(&self, other: &Self) -> bool {
        self.inner == other.inner && self.dict as *const _ as usize == other.dict as *const _ as usize
    }
}

impl<'a> Ord for MyStructAsKey<'a> {
    fn cmp(&self, other: &Self) -> ::std::cmp::Ordering {
        self.dict.lookup(&self.inner).cmp(&other.dict.lookup(&other.inner))
    }
}

impl<'a> PartialOrd for MyStructAsKey<'a> {
    fn partial_cmp(&self, other: &Self) -> Option<::std::cmp::Ordering> {
        Some(self.dict.lookup(&self.inner).cmp(&other.dict.lookup(&other.inner)))
    }
}

#[derive(Default, Debug)]
struct Dictionary(::std::cell::RefCell<::std::collections::HashMap<i32, u64>>);

impl Dictionary {
    fn ord_key<'a>(&'a self, ms: MyStruct) -> MyStructAsKey<'a> {
        MyStructAsKey {
            inner: ms,
            dict: self,
        }
    }
    fn lookup(&self, key: &MyStruct) -> u64 {
        self.0.borrow()[&key.value]
    }
    fn create(&self, value: u64) -> MyStruct {
        let mut map = self.0.borrow_mut();
        let n = map.len();
        assert!(n as i32 as usize == n);
        let n = n as i32;
        map.insert(n, value);
        MyStruct {
            value: n,
        }
    }
}

fn main() {
    let dict = Dictionary::default();
    let a = dict.create(99);
    let b = dict.create(42);
    let mut set = ::std::collections::BTreeSet::new();
    set.insert(dict.ord_key(a));
    set.insert(dict.ord_key(b));
    println!("{:#?}", set);
    let c = dict.create(1000);
    let d = dict.create(0);
    set.insert(dict.ord_key(c));
    set.insert(dict.ord_key(d));
    println!("{:#?}", set);
}

8
2018-03-04 08:32



@ker:我一直在尝试创建第二个允许按地图字典的玩具......说实话,我并没有完全成功! - Matthieu M.
@ker:我在考虑重新打包 BTreeMap 然后每次调用一个方法切换一个thread-local-variable(并在调用结束时恢复它以前的值)但是...我有点需要一些 AnyMap 因为thread-local-variable能够以通用的方式表达它(也许我应该只是转储泛型)并且生命周期将迫使我使用不安全的代码,并且有很多接口要包装,...这很痛苦 - Matthieu M.
@ker:我已在答案中添加了您的示例,以避免在清除注释时丢失它。另一个解决方案是你提供它作为答案,在这种情况下,我会从我的中删除它并upvote你:) - Matthieu M.
谢谢!我已经看到讨论提到创建一个新类型并实现Ord就足以模拟不同的比较方法,因此不需要自定义比较器。但是,它不会模拟闭包。您所看到的讨论是否提到了这个问题,如果他们确实提供了链接? - loudandclear
@loudandclear:说实话它是一年多前(1.0之前)所以我不知道如何再次找到讨论。至于闭包,我不记得是否(或如何)讨论它们。闭包的唯一好处就是关闭一个环境,所以如果你将环境放在你的结构中(例如通过一个共享指针),那么就不再需要闭包......但是这确实会增加结构的大小。 - Matthieu M.


Rust(更具体地说是Rust的libcollections)目前没有类似比较器的构造,所以使用可变静态可能是你最好的选择。这也在内部使用 rustc,例如字符串interner是静态的。话虽如此,用例并不完全不常见,所以如果我们请求它,Rust有一天会得到外部比较器。


5
2018-03-04 05:18



语言规范中是否有任何内容阻止使用它们?如果数据结构在其构造函数中将闭包作为参数并存储它,那就足够了。所以我猜问题不是“它不可能”,只是标准库不是这样设计的? - loudandclear
究竟。您 能够 做到这一点,只是没有 std::collections 原样。 - llogiq
libstd集合可以通过添加默认类型参数在以后的版本中添加对此的支持。 - bluss
@bluss:的确,就像1.7现在提供了在哈希映射中自定义哈希算法的能力! - Matthieu M.