【问题标题】:Removing items from a BTreeMap or BTreeSet found through iteration从通过迭代找到的 BTreeMap 或 BTreeSet 中删除项目
【发布时间】:2015-12-31 00:43:05
【问题描述】:

我想从BTreeMap 中删除通过迭代找到的项目。

由于迭代时无法删除项目,我将要删除的项目放入向量中。主要问题是不能使用 references 的向量,而只能使用值的向量。然后必须克隆必须删除条目的所有键(假设键实现了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() {}

编译时出现如下错误:

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

to_delete.push(k) 更改为to_delete.push(k.clone()) 可使此sn-p 正确编译,但如果必须克隆每个要删除的键,则代价非常高。

有没有更好的解决方案?

【问题讨论】:

  • 我希望最终,BTreeMap 能够获得像fn drain(&amp;mut self, Range&lt;K&gt;) 这样的方法。相关的是this proto RFC,但也请查看this onethis one
  • @Shepmaster -- 当然,这里的问题不是 Range,而是 Range
  • @Jsor 啊,我错过了。我觉得这是一个不太可能的情况,所以它确实是一个棘手的情况!
  • 这里需要的是drain的一个版本,它带有谓词,它确实是一个值得添加的东西。

标签: rust


【解决方案1】:

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

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

为此,您需要用于集合的假设“光标”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
}

如果您的条件是使结构唯一的字段的相等性(也就是PartialEqHash 中使用的唯一字段),您可以为您的类型实现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
}

另见:

【讨论】:

  • 这是一个有趣的想法。
  • 请注意,根据地图的大小与要删除的元素数量相比,这在性能方面并没有明显的提升。
猜你喜欢
  • 2018-09-10
  • 1970-01-01
  • 1970-01-01
  • 2021-11-08
  • 2014-10-26
  • 1970-01-01
相关资源
最近更新 更多