【问题标题】:Best way to remove elements of Vec depending on other elements of the same Vec根据同一 Vec 的其他元素删除 Vec 元素的最佳方法
【发布时间】:2016-06-19 10:01:25
【问题描述】:

我有一个集合向量,我想删除向量中其他集合的子集的所有集合。示例:

a = {0, 3, 5}
b = {0, 5}
c = {0, 2, 3}

在这种情况下,我想删除 b,因为它是 a 的子集。我可以使用“哑” n² 算法。

遗憾的是,让它与借用检查器一起工作非常棘手。我想出的最好的是(Playground):

let mut v: Vec<HashSet<u8>> = vec![];

let mut to_delete = Vec::new();
for (i, set_a) in v.iter().enumerate().rev() {
    for set_b in &v[..i] {
        if set_a.is_subset(&set_b) {
            to_delete.push(i);
            break;
        }
    }
}

for i in to_delete {
    v.swap_remove(i);
}

注意:上面的代码是不正确的!更多细节见cmets)

我看到了一些缺点:

  • 我需要一个带有额外分配的额外向量
  • 也许有比经常致电swap_remove 更有效的方法
  • 如果我需要保持秩序,我不能使用swap_remove,但必须使用remove,这很慢

有没有更好的方法来做到这一点?我不只是询问我的用例,而是询问标题中描述的一般情况。

【问题讨论】:

  • 这个算法不正确;它只删除向量中 earlier 集合的子集。示例:play.rust-lang.org/…
  • 注意:保持顺序并避免重新分配可以通过构建临时(并按顺序推送)来实现,然后将其与原始交换。但目前尚不清楚临界点是什么。
  • 这里是否需要保留订单?如果不是,我会先按大小对向量进行排序,这样您就可以避免双向检查子集(并删除正确的)。
  • @ChrisEmerson 谢谢!我不会在问题中修复我的代码,而是添加一条不正确的注释。但是修复它的好主意:)

标签: rust borrow-checker


【解决方案1】:

这是一个不进行额外分配并保留顺序的解决方案:

fn product_retain<T, F>(v: &mut Vec<T>, mut pred: F)
    where F: FnMut(&T, &T) -> bool
{
    let mut j = 0;
    for i in 0..v.len() {
        // invariants:
        // items v[0..j] will be kept
        // items v[j..i] will be removed
        if (0..j).chain(i + 1..v.len()).all(|a| pred(&v[i], &v[a])) {
            v.swap(i, j);
            j += 1;
        }
    }
    v.truncate(j);
}

fn main() {
    // test with a simpler example
    // unique elements
    let mut v = vec![1, 2, 3];
    product_retain(&mut v, |a, b| a != b);
    assert_eq!(vec![1, 2, 3], v);

    let mut v = vec![1, 3, 2, 4, 5, 1, 2, 4];
    product_retain(&mut v, |a, b| a != b);
    assert_eq!(vec![3, 5, 1, 2, 4], v);
}

这是一种分区算法。第一个分区中的元素将被保留,第二个分区中的元素将被删除。

【讨论】:

  • 如果谓词是可交换的,内循环可以从j开始,对吗?如果是这样,我们是否可以将其编码到 Rust 的类型系统中,以便它在适当的时候自动选择更有效的 impl?
【解决方案2】:

您可以使用while 循环代替for

use std::collections::HashSet;

fn main() {
    let arr: &[&[u8]] = &[
        &[3],
        &[1,2,3],
        &[1,3],
        &[1,4],
        &[2,3]
    ];

    let mut v:Vec<HashSet<u8>> = arr.iter()
        .map(|x| x.iter().cloned().collect())
        .collect();

    let mut pos = 0;
    while pos < v.len() {
        let is_sub = v[pos+1..].iter().any(|x| v[pos].is_subset(x)) 
            || v[..pos].iter().any(|x| v[pos].is_subset(x));

        if is_sub {
            v.swap_remove(pos);
        } else {
            pos+=1;
        }
    }
    println!("{:?}", v);
}

没有额外的分配。


为避免使用removeswap_remove,可以将向量的类型改为Vec&lt;Option&lt;HashSet&lt;u8&gt;&gt;&gt;

use std::collections::HashSet;

fn main() {
    let arr: &[&[u8]] = &[
        &[3],
        &[1,2,3],
        &[1,3],
        &[1,4],
        &[2,3]
    ];

    let mut v:Vec<Option<HashSet<u8>>> = arr.iter()
        .map(|x| Some(x.iter().cloned().collect()))
        .collect();

    for pos in 0..v.len(){
        let is_sub = match v[pos].as_ref() {
            Some(chk) => 
                v[..pos].iter().flat_map(|x| x).any(|x| chk.is_subset(x)) 
                ||  v[pos+1..].iter().flat_map(|x| x).any(|x| chk.is_subset(x)),
            None => false,
        };

        if is_sub { v[pos]=None };//Replace with None instead remove

    }
    println!("{:?}", v);//[None, Some({3, 2, 1}), None, Some({1, 4}), None]
}

【讨论】:

    【解决方案3】:
    • 我需要一个带有额外分配的额外向量

    我不会担心这种分配,因为与算法的其余部分相比,该分配的内存和运行时占用空间非常小。

    • 也许有比经常致电swap_remove 更有效的方法。
    • 如果我需要保持秩序,我不能使用swap_remove,,但必须使用remove,这很慢

    我将 to_deleteVec&lt;usize&gt; 更改为 Vec&lt;bool&gt; 并标记是否应删除特定的哈希图。然后您可以使用Vec::retain,它会在保留顺序的同时有条件地删除元素。不幸的是,这个函数没有将索引传递给闭包,所以我们必须创建一个解决方法(playground):

    let mut to_delete = vec![false; v.len()];
    for (i, set_a) in v.iter().enumerate().rev() {
        for set_b in &v[..i] {
            if set_a.is_subset(&set_b) {
                to_delete[i] = true;
            }
        }
    }
    
    {
        // This assumes that retain checks the elements in the order.
        let mut i = 0;
        v.retain(|_| {
            let ret = !to_delete[i];
            i += 1;
            ret
        });
    }
    

    如果你的 hashmap 有一个在正常情况下永远不会出现的特殊值,你可以使用它来将 hashmap 标记为“删除”,然后在retain 中检查该条件(这需要将外部循环从虽然基于迭代器到基于范围)。


    旁注(如果HashSet&lt;u8&gt; 不仅仅是一个玩具示例):存储和比较小整数集的更有效方法是使用bitset

    【讨论】:

    • 信任保留按顺序遍历元素并不是一个真正的选择:/ 将向量内的对象设置为特殊值并使用基于范围的循环对其进行迭代的第二个想法很有趣。在旁注中:是的,这不仅仅是一个玩具示例(这实际上对性能非常关键),我已经在使用 BitSet :) 谢谢!
    • @LukasKalbertodt retain says此方法在原地运行并保留保留元素的顺序。我认为不允许更改。
    • @Shepmaster 在我的解释中,这句话是关于保留后的顺序,而不是关于访问元素的顺序。但是我无法想象有效的实现会以不同的顺序检查元素。
    猜你喜欢
    • 2023-02-09
    • 2016-10-14
    • 1970-01-01
    • 1970-01-01
    • 2016-01-17
    • 1970-01-01
    • 2020-12-11
    • 1970-01-01
    • 2022-08-09
    相关资源
    最近更新 更多