如何实现比较含引用元素的两个Rust迭代器的diff函数?
实现迭代器差异比较的diff函数
需求
实现一个diff函数,比较两个迭代器后返回元组:
- 第一个元素:将
existing迭代器转换为new迭代器时需添加的元素集合(通过克隆引用获得拥有所有权的值) - 第二个元素:需删除的元素集合(同样是拥有所有权的值)
迭代器元素为引用类型,最终返回克隆后的所有权值。
目标函数签名
fn diff<T: PartialEq<T> + Clone>( new: impl IntoIterator<Item = &T>, existing: impl IntoIterator<Item = &T> ) -> (HashSet<T>, HashSet<T>) { // 实现代码 }
注:原签名缺少
Clone约束,由于需要克隆元素获取所有权,必须补充该约束。
示例
let x = vec![&1, &2, &3]; let y = vec![&1, &4]; assert_eq!( diff(&y[..], &x[..]), ( HashSet::from_iter(vec![4].iter().cloned()), HashSet::from_iter(vec![2, 3].iter().cloned()) ) );
尝试实现与编译错误
使用itertools的partition_map尝试实现时,出现编译错误:
let (add, common): (Vec<_>, Vec<_>) = new.into_iter().partition_map(|t| match existing.into_iter().contains(t) { false => Either::Left(t), true => Either::Right(t), });
错误信息(翻译后):
无法从闭包捕获的变量
existing中移出
发生移动是因为existing的类型是impl IntoIterator<Item = &'a T>,该类型未实现Copytrait
问题根源
existing.into_iter()会消耗existing本身(IntoIterator的into_iter方法会获取所有权),闭包中每次迭代都尝试调用该方法,第一次调用就会把existing移走,后续迭代无法再使用。- 即使能重复调用,每次重新遍历
existing的效率极低,时间复杂度为O(n*m)。
正确实现方案
代码实现
use std::collections::HashSet; use itertools::Either; fn diff<T: PartialEq<T> + Clone + Eq + std::hash::Hash>( new: impl IntoIterator<Item = &T>, existing: impl IntoIterator<Item = &T> ) -> (HashSet<T>, HashSet<T>) { // 将existing的元素克隆为所有权值,存入HashSet以实现O(1)查找 let existing_set: HashSet<T> = existing.into_iter().cloned().collect(); // 分离new中需新增的元素,以及与existing共有的元素 let (add, common): (HashSet<_>, HashSet<_>) = new.into_iter() .partition_map(|item| { if existing_set.contains(item) { Either::Right(item.clone()) } else { Either::Left(item.clone()) } }); // 从原existing集合中过滤掉共有元素,得到需删除的部分 let remove: HashSet<_> = existing_set.into_iter() .filter(|item| !common.contains(item)) .collect(); (add, remove) }
关键说明
- 补充Trait约束:
Clone:用于克隆引用获取所有权值Eq+Hash:HashSet的元素必须满足这两个约束以支持哈希存储和查找
- 效率优化:
- 先将
existing转为HashSet,将查找时间从O(m)降至O(1),整体时间复杂度优化为O(n + m)
- 先将
- 逻辑拆分:
- 先分离新增/共有元素,再从原集合中过滤出需删除的元素,逻辑清晰且无重复遍历
测试验证
运行示例代码可正常通过断言:
fn main() { let x = vec![&1, &2, &3]; let y = vec![&1, &4]; assert_eq!( diff(&y[..], &x[..]), ( HashSet::from_iter(vec![4].iter().cloned()), HashSet::from_iter(vec![2, 3].iter().cloned()) ) ); }
内容的提问来源于stack exchange,提问作者jsstuball
相关产品推荐
相关产品推荐

