根据同一Vec的其他元素删除Vec元素的最佳方法

时间:2016-06-19 10:01:26

标签: rust borrow-checker

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

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

注意:上面的代码不正确!有关详细信息,请参阅评论)

我看到一些缺点:

  • 我需要一个额外的分配矢量
  • 也许有更有效的方法,而不是经常调用swap_remove
  • 如果我需要保留订单,我无法使用swap_remove,但必须使用remove,这很慢

有更好的方法吗?我不只是询问我的用例,而是关于标题中描述的一般情况。

3 个答案:

答案 0 :(得分:10)

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

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

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

答案 1 :(得分:1)

您可以使用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<Option<HashSet<u8>>>

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]
}

答案 2 :(得分:1)

  
      
  • 我需要一个额外的分配矢量
  •   

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

  
      
  • 也许比经常调用swap_remove更有效。
  •   
  • 如果我需要保留订单,我无法使用swap_remove,但必须使用remove,这很慢
  •   

我将to_deleteVec<usize>更改为Vec<bool>,并标记是否应删除特定的散列图。然后,您可以使用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为&#34;删除&#34;,然后在retain中检查那个条件(它将需要将外循环从基于迭代器的更改为基于范围的。)

Sidenote(如果HashSet<u8>不仅仅是一个玩具示例):存储和比较小整数集的更有效方法是使用bitset