在Rust中进行`HashSet`的复合操作,或者如何在Rust中获取`HashSet`的显式差异/并集。

3
我想用伪代码执行以下操作:
(a, b, c) = (HashSet(...), HashSet(...), HashSet(...))

(a, b, c) = (a - b - c, b - a - c, c - a - b)

在 Rust 中,我尝试了类似这样的东西:
fn get_random_set(...) -> HashSet<String> {
    ...
}

// Sets of randomly generated "words" that define behavior of the whole program.
let action_plus: HashSet<String> = get_random_set();
let action_minus: HashSet<String> = get_random_set();
let action_new_line: HashSet<String> = get_random_set();

现在我们要从这些HashSet中排除所有常见的"words"。

我了解到differenceunion方法返回DifferenceUnion迭代器。如果我这样做:

let action_plus = HashSet::from(action_minus.union(&action_new_line).collect::<Vec<String>>());

我收到这个:

the trait `From<Vec<String>>` is not implemented for `HashSet<_, _>`

如何解决这个问题?

我觉得这个答案里有一段代码片段可能会对你有帮助。https://dev59.com/SlkS5IYBdhLWcg3wemv6 - etchesketch
有时候from并不是答案,而是在into_iter()上使用collect() - tadman
3个回答

5

直接使用 HashSet 而不是 Vec,使用 impl FromIterator for HashSet 进行收集。

即:

let action_plus: HashSet<_> = action_minus.union(&action_new_line).collect();

正如 Chayim 在评论中所说,您也可以使用 | (BitOr) 运算符,因为 HashSet 实现了它 ,并且文档上也这样说:

将 self 和 rhs 的并集作为一个新的 HashSet<T, S> 返回

如果你查看它的实现,你会发现它基本上与上面的代码相同(尽管它从这些集合中克隆项目):

impl<T, S> BitOr<&HashSet<T, S>> for &HashSet<T, S>
where
    T: Eq + Hash + Clone,
    S: BuildHasher + Default,
{
    type Output = HashSet<T, S>;

    fn bitor(self, rhs: &HashSet<T, S>) -> HashSet<T, S> {
        self.union(rhs).cloned().collect()
    }
}

HasSet还实现了BitAnd&)运算符用于创建交集,Sub-)用于创建差集,以及BitXor^)用于创建对称差集。
你可以使用这些便捷的运算符,但是当人们不期望它们时,它们可能会让人感到困惑,所以请谨慎使用。

3
或者,更简短地说,&action_minus | &action_new_line - Chayim Friedman
1
谢谢大家,这绝对是我想要实施的!下次我会更仔细地阅读文档。 - maestroviktorin

2
您可以直接将其收集到一个HashSet中。
let a = get_random_set();
let b = get_random_set();
let c = get_random_set();
let a_minus_b_minus_c = a
    .difference(&b)
    .cloned()
    .collect::<HashSet<_>>()
    .difference(&c)
    .cloned()
    .collect::<HashSet<_>>();

克隆是将从HashSet<&String>转换为HashSet<String>的输出必要的,因为difference要求相同类型的集合。如果您可以接受a_minus_b_minus_c作为HashSet<&String>,则不需要最后一个cloned

然而,使用减法运算符更容易。HashSet通过引用接受两个运算数的Sub实现,所以每次都需要添加一个引用。

let a_minus_b_minus_c = &(&a - &b) - &c;

还有其他的:
  • BitAnd 用于求交集 &a & &b
  • BitOr 用于求并集 &a | &b
  • BitXor 用于求对称差集 &a ^ &b
请注意,所有这些克隆操作都会将集合中的值复制并返回一个新的集合,因此它们的效率不是最优的。如果您需要更高的性能,您可以通过使用 retain(差集、交集)或 extend(并集)手动修改第一个集合,或者手动创建一个保存引用的新集合来执行操作。

谢谢!你的解释帮助我更深入地理解,并解决了我遇到的问题。 - maestroviktorin

2

你的目标是从所有的集合中移除在多个集合中出现的任何元素。由于你只是移除元素,不需要克隆任何字符串,而且可以原地执行所有操作。如果你的集合分别称为abc,你可以使用以下代码:

a.retain(|x| b.remove(x) | c.remove(x));
b.retain(|x| c.remove(x));

这将首先从两个集合中删除ab以及ac之间的任何交集中的元素,然后删除bc之间的所有交集中的元素。总体而言,这应该比其他答案中讨论的方法要快得多(尽管我没有做任何基准测试)。
请注意,第一行使用非短路运算符|而不是||很重要。最后一个操作数c.remove(x)显然具有副作用,因此我们不能跳过它。否则,在所有三个集合中都存在的元素将无法从c中删除。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接