通过迭代从BTreeMap或BTreeSet中删除项目

tof*_*der 6 rust

我想删除BTreeMap通过迭代找到的项目.

由于在迭代时无法删除项目,因此我将要删除的项目放入向量中.主要问题是不可能使用引用向量,而只能使用值向量.必须克隆必须删除条目的所有密钥(假设密钥实现了Clone特征).

例如,这个简短的示例不编译:

use std::collections::BTreeMap;

pub fn clean() {
    let mut map = BTreeMap::<String, i32>::new();

    let mut to_delete = Vec::new();

    {
        for (k, v) in map.iter() {
            if *v > 10 {
                to_delete.push(k);
            }
        }
    }

    for k in to_delete.drain(..) {
        map.remove(k);
    }
}

fn main() {}
Run Code Online (Sandbox Code Playgroud)

它在编译时会产生以下错误:

error[E0502]: cannot borrow `map` as mutable because it is also borrowed as immutable
  --> src/main.rs:17:9
   |
9  |         for (k, v) in map.iter() {
   |                       --- immutable borrow occurs here
...
17 |         map.remove(k);
   |         ^^^ mutable borrow occurs here
18 |     }
19 | }
   | - immutable borrow ends here
Run Code Online (Sandbox Code Playgroud)

改变to_delete.push(k)to_delete.push(k.clone())使这个片断编译正确,但它是相当昂贵的,如果每个键删除必须被克隆.

有更好的解决方案吗?

She*_*ter 5

TL; DR:你不能。

就编译器而言, 的实现BTreeMap::remove可能会这样做:

pub fn remove<Q>(&mut self, key: &Q) -> Option<V>
where
    K: Borrow<Q>,
    Q: Ord + ?Sized,
{
    // actual deleting code, which destroys the value in the set
    // now what `value` pointed to is gone and `value` points to invalid memory

    // And now we access that memory, causing undefined behavior
    key.borrow();
}
Run Code Online (Sandbox Code Playgroud)

因此,当集合发生变异时,编译器必须防止使用对值的引用。

为此,您需要类似假设的“游标”集合 API 之类的东西。这将允许您遍历集合,返回一个特殊类型,该类型保存集合的可变内部。这种类型可以为您提供一个参考以进行检查,然后允许您删除该项目。


我可能会从一个有点不同的方向看这个问题。我不会尝试保留地图,而是创建一个全新的地图:

use std::collections::BTreeMap;

pub fn main() {
    let mut map = BTreeMap::new();

    map.insert("thief", 5);
    map.insert("troll", 52);
    map.insert("gnome", 7);

    let map: BTreeMap<_, _> =
        map.into_iter()
        .filter(|&(_, v)| v <= 10)
        .collect();

    println!("{:?}", map); // troll is gone
}
Run Code Online (Sandbox Code Playgroud)

如果您的条件在使结构唯一的字段上相等(也就是PartialEqand 中使用的唯一字段Hash),您可以Borrow为您的类型实现并直接获取/删除它:

use std::collections::BTreeMap;

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
struct Monster(String);

use std::borrow::Borrow;

impl Borrow<str> for Monster {
    fn borrow(&self) -> &str { &self.0 }
}

pub fn main() {
    let mut map = BTreeMap::new();

    map.insert(Monster("thief".into()), 5);
    map.insert(Monster("troll".into()), 52);
    map.insert(Monster("gnome".into()), 7);

    map.remove("troll");

    println!("{:?}", map); // troll is gone
}
Run Code Online (Sandbox Code Playgroud)

也可以看看:

  • 请注意,根据地图的大小与要删除的元素数量相比,这在性能方面并不是一个明显的收益。 (4认同)