我正在尝试在Rust中编写一个naif实现的kmeans用于学习目的。其中一个步骤如下:我有一组积分xs
和另一组积分centroids
。我想根据质心中最近的邻居对xs
进行分组。也就是说,如果两个点具有共同的最近邻居,则它们属于同一组。
例如在Scala中,这看起来像
xs groupBy { x => closest(x, centroids) } values
在标准库中没有找到groupBy
方法,我尝试将其写成如下(假设Point
和closest
已定义):
fn clusters(xs: & Vec<Point>, centroids: & Vec<Point>) -> Vec<Vec<Point>> {
let mut groups: TreeMap<Point, Vec<Point>> = TreeMap::new();
// for x in xs.iter() {
// let y = closest(*x, centroids);
// match groups.find(&y) {
// Some(mut val) => val.push(*x),
// None => {
// groups.insert(y, vec![*x]);
// },
// }
// }
let result: Vec<Vec<Point>> = groups.values().map(|x| *x).collect();
result
}
我评论了中心部分,因为我在创建TreeMap<Point, Vec<Point>>
并将其值返回为Vec<Vec<Point>>
时遇到了问题。 TreeMap上有一个方法values
,它返回一个类型为Map<...>
的迭代器。我试过了:
Vec
。问题是迭代器的元素实际上是Vec<Point>
的指针,所以我必须做let result: Vec<& Vec<Point>> = groups.values().collect();
之类的事情。再一次,Rust不会让我回复那些指针,因为它们太短了error: cannot move out of dereference of &-pointer
返回该地图值的正确方法是什么?
此外,如果我取消中心部分,Rust会阻止我执行groups.insert(y, vec![*x]);
,因为groups
在模式匹配中被本地借用为不可变引用。我该如何解决这个问题?
答案 0 :(得分:5)
你的第一个问题是values()返回一个对象,该对象提供不可变的投影到TreeMap中,但是你试图在地图调用中将数据移出它。
两种可能的解决方案是: 1)您创建向量的副本。然而,这是昂贵的操作。
let result: Vec<Vec<Point>> = groups.values().map(|x| x.clone()).collect();
2)你使用了使用treemap的to_iter()方法,你可以自由地移出数据。
let result: Vec<Vec<Point>> = groups.into_iter().map(|(p, v)| v).collect();
然后,评论代码中存在两个问题。
首先,您必须获得对已找到项目的可变引用,因此您必须调用find_mut()而不是find()。
其次,在None分支中,您尝试插入已经借用的树形图(通过find()/ find_mut()调用的结果)。 Rust不会让你。目前,唯一的选择是在匹配块之后推迟插入:
let should_insert = match groups.find_mut(&y) {
Some(mut val) => {
val.push(*x);
false
}
None => {
true
},
};
if should_insert {
groups.insert(y, vec![*x]);
}
编辑:在较新版本的Rust中有更好的方法:
use std::collections::btree_map::Entry;
match groups.entry(&y) {
Entry::Occupied(mut view) => { val.get_mut().push(*x); }
Entry::Vaccant(view) => { view.insert(vec![*x]); }
};
答案 1 :(得分:4)
直接返回迭代器,但Rust抱怨说我必须添加一个生命周期说明符,我不确定使用哪个
借用检查程序在这种情况下使您免于使用后使用错误。由于您在函数中本地创建树图并且不将其移动到其他任何位置,因此一旦函数完成,其元素将自动被销毁。因此,Rust编译器不允许您将迭代器返回到在函数返回后将立即停止存在的数据结构,这实际上是一件好事。
取消引用所有这些指针,如上所述。我认为这是正确的方法,但Rust告诉我错误:无法移出&amp; -pointer的取消引用
右。情况与此类似:
let mut mystrings = vec!["hello".to_string(), "world".to_string()];
let x = *mystrings.get(0);
在这里,您会得到相同的错误,因为您不允许将第一个字符串移出矢量,就像这样。请记住,移动是破坏性的。这意味着源将无效。但是你真的不想要一个第一个对象处于某种无效状态的向量。这就是为什么Rust不允许你移出引用的原因。你可以拨打clone
let x = mystrings.get(0).clone();
但这可能也不是你想要的。克隆载体和字符串是昂贵的。但您可以像这样使用replace
:
let mut mystrings = vec!["hello".to_string(), "world".to_string()];
let x = ::std::mem::replace(mystrings.get_mut(0), String::new());
这将字符串从向量移动到x,同时将空字符串移动到向量中作为替换。这样,向量中的String对象保持有效。这样的东西也适用于矢量。
另一种将事物移出集合的方法是像PEPP建议的“移动迭代器”。他打败了我。