多语言中的集合操作:筛选、分组、映射、排序与去重
Table of Contents
多语言中的集合操作:筛选、分组、映射、排序与去重
集合操作是业务开发里最常见的一类代码。查列表、做筛选、做映射、按字段排序、按条件分组,几乎每天都在写。
但不同语言处理集合的风格差异非常明显。有的语言自带很强的链式表达能力,有的语言更偏显式循环,有的语言虽然写起来更长,但性能和控制感更强。
这篇文章用同一个 Prod 列表作为例子,对比 C#、C++、Python、Go、JavaScript、Rust 里几种最常见集合操作的写法。
在线执行环境 https://rextester.com
为什么值得单独看集合操作
很多时候,判断一个人是否真正熟悉某门语言,不是看他会不会定义类,而是看他处理集合时是否顺手。
因为集合操作最能暴露语言习惯:
- C# 擅长 LINQ 风格的声明式表达。
- Python 简洁灵活,列表推导式很强。
- JavaScript 在前端场景里非常自然。
- Go 常常回到显式循环,代码朴素但直接。
- Rust 写法更严格,但可组合性很强。
- C++ 可以非常灵活,但风格差异也最大。
统一示例
假设有数据模型 Prod,字段有 Name、Price、IsActive。
筛选
C# var filtered = lst.Where(t => t.IsActive)
Python filtered = [p for p in lst if p.isActive]
Rust let filtered = lst.into_iter().filter(|x| x.is_active).collect::<Vec<_>>();
C++
// 拷贝到 filtered 的 inserter 迭代器
auto filtered1 = vector<Prod>();
filtered1.reserve(lst.size()); // 可选:预先分配空间提高效率
std::copy_if(lst.begin(), lst.end(),
std::inserter(filtered1, filtered1.begin()), // back_inserter 也常见
[](const Prod& p) { return p.GetIsActive(); });
auto filtered2 = vector<Prod>();
for (const auto& p : lst) { // 基于范围的for循环更易读
if (p.GetIsActive()) {
filtered2.push_back(p);
}
}
Js let lst2 = lst.filter(item => item.isActive)
Go
var newlst []Prod
for _, v := range lst {
if v.IsActive {
newlst = append(newlst, v)
}
}
分组
C# var gp = lst.GroupBy(t => t.IsActive)
Python
gp = {}
for p in lst:
if not p.isActive in gp:
gp[p.isActive] = []
gp[p.isActive].append(p)
Rust
let mut mp: HashMap<bool, Vec<Prod>> = HashMap::new();
for item in lst.iter() {
mp.entry(item.is_active).or_default().push(item.clone());
}
C++
auto mp = map<bool, vector<Prod>>();
for (const auto& p : lst) {
// operator[] 会自动为不存在的键创建空 vector
mp[p.GetIsActive()].push_back(p);
}
也可以在创建之前判断 key 是否存在不存在添加默认的键值对,再修改
auto mp = map<bool, vector<Prod>>();
for (const Prod& p : lst) {
if (mp.find(p.GetIsActive()) == mp.end()) {
mp.insert(make_pair(p.GetIsActive(), vector<Prod>()));
}
mp[p.GetIsActive()].push_back(p);
}
Js
let gp = {}
for (let i = 0; i < lst.length; i++) {
if (!gp[lst[i].isActive]) {
gp[lst[i].isActive] = []
}
gp[lst[i].isActive].push(lst[i])
}
Go
mp := make(map[bool][]Prod)
for _, v := range lst {
mp[v.IsActive] = append(mp[v.IsActive], v)
}
映射
C# var newlst = lst.Select(t => t.Price * 0.8);
Python newlst = [p.Price * 0.8 for p in lst]
Rust let newlst :Vec<f64> = lst.iter().map(|x| (x.price as f64) * 0.8).collect();
C++
auto newlst = vector<double>();
newlst.reserve(lst.size());
std::transform(lst.cbegin(), lst.cend(),
back_inserter(newlst),
[](const Prod& p) { return p.GetPrice() * 0.8;});
Js let newlst = lst.map(t => t.price * 0.8)
Go
var newlst []float64
for _, v := range lst {
newlst = append(newlst, float64(v.Price)*0.8)
}
排序
C# var od = lst.OrderBy(t => t.Price);
Python lst.sort(key=lambda p: p.Price, reverse=True)
Rust
let mut od = lst.clone();
od.sort_by(|a, b| a.price.cmp(&b.price));
C++
sort(lst.begin(), lst.end(),
[](const Prod& left, const Prod& right) {
return left.GetPrice() < right.GetPrice();
});
Js lst.sort((a, b) => a.price - b.price)
Go
sort.Slice(lst, func(i, j int) bool {
return lst[i].Price < lst[j].Price
})
lst.sort! 的宏会自动补全样板代码,注意 i 和 j 是下标
去重
C# lst.Distinct();
Python dist = list(set(lst))
Rust
let dist: Vec<String> = lst
.iter()
.cloned()
.collect::<HashSet<String>>()
.into_iter()
.collect();
C++
std::sort(lst.begin(), lst.end());
auto unique_end = std::unique(lst.begin(), lst.end());
lst.erase(unique_end, lst.end());
C++ 的去重要先排序,后去重。如果是复杂数据结构的类型:
std::sort(lst.begin(), lst.end(), [](const Prod& left, const Prod& right) {
return left.GetName() < right.GetName();
});
auto unique_end = std::unique(lst.begin(), lst.end(),
[](const Prod& left, const Prod& right) {
return left.GetName() == right.GetName();
});
lst.erase(unique_end, lst.end());
Js let dist = [...new Set(lst)] … 是 js 的展开语法,将集合展开为数组
Go
var dist []Prod
mp := make(map[string]Prod)
for _, item := range lst {
s := fmt.Sprintf("%s %d %t", item.Name, item.Price, item.IsActive)
if _, isExist := mp[s]; !isExist {
mp[s] = item
}
}
for _, item := range mp {
dist = append(dist, item)
}
并集
C# var unions = lst.Union(lst2);
Python unions = list(set(lst + lst2))
Rust
let set1: HashSet<_> = lst1.iter().cloned().collect();
let set2: HashSet<_> = lst2.iter().cloned().collect();
let lst: Vec<String> = set1.union(&set2).cloned().collect();
C++
// 自定义比较器
auto compareProd = [](const Prod& left, const Prod& right) {
string ls = left.GetName() + to_string(left.GetPrice()) + to_string(left.GetIsActive());
string rs = right.GetName() + to_string(right.GetPrice()) + to_string(right.GetIsActive());
return ls < rs;
};
sort(lst1.begin(), lst1.end(), compareProd);
sort(lst2.begin(), lst2.end(), compareProd);
// 使用back_inserter方法
auto unions = vector<Prod>();
unions.reserve(lst1.size() + lst2.size());
std::set_union(
lst1.begin(), lst1.end(),
lst2.begin(), lst2.end(),
std::back_inserter(unions),
compareProd
);
C++ 的去重要求有序,求并集也一样
Js let unions = [...new Set([...lt, ...lt2])]
Go
unions := []Prod{}
keys := make(map[string]struct{})
append2unions := func(l []Prod) {
for _, v := range l {
s := fmt.Sprintf("%s %d %t", v.Name, v.Price, v.IsActive)
if _, isExist := keys[s]; !isExist {
keys[s] = struct{}{}
unions = append(unions, v)
}
}
}
append2unions(lst1)
append2unions(lst2)
fmt.Println("并集: ", unions)
keys := make(map[string]struct{}) 类似于字符串的 set,只有键,没有值。也可以存一个 bool 值
这段代码就是遍历元素,构造字符串的键 s,将没有存储到字典的键存到 unions,已有的就跳过
差集
C# var excepts = lst.Except(lst2);
Python excepts = list(set(lst2) - set(lt))
Rust
let set1: HashSet<_> = lst1.iter().cloned().collect();
let set2: HashSet<_> = lst2.iter().cloned().collect();
let lst: Vec<String> = set1.difference(&set2).cloned().collect();
C++
auto compareProd = [](const Prod& left, const Prod& right) {
string ls = left.GetName() + to_string(left.GetPrice()) + to_string(left.GetIsActive());
string rs = right.GetName() + to_string(right.GetPrice()) + to_string(right.GetIsActive());
return ls < rs;
};
sort(lst1.begin(), lst1.end(), compareProd);
sort(lst2.begin(), lst2.end(), compareProd);
auto diff = vector<Prod>();
diff.reserve(lst1.size() + lst2.size());
std::set_difference(
lst1.begin(), lst1.end(),
lst2.begin(), lst2.end(),
std::back_inserter(diff),
compareProd
);
C++ 的交集叫 set_intersection
Js let excepts = [...(new Set([...lt, ...lt2]).difference(new Set(lt)))]
new Set([...lt, ...lt2]) 是并集
new Set([...lt, ...lt2]).difference(new Set(lt)) 通过 difference 制造差集
再将结果展开成数组
Go
differences := []Prod{}
keys := make(map[string]int)
for idx, v := range lst1 {
s := fmt.Sprintf("%s %d %t", v.Name, v.Price, v.IsActive)
if _, isExist := keys[s]; !isExist {
keys[s] = idx
}
}
for _, v := range lst2 {
s := fmt.Sprintf("%s %d %t", v.Name, v.Price, v.IsActive)
delete(keys, s)
}
for _, idx := range keys {
differences = append(differences, lst1[idx])
}
fmt.Println("差集: ", differences)
Go 求差集的方式,就是遍历 lst1 得到 key 的集合,再遍历 lst2 删除对应的 key,剩下来的就是差集
实际开发里怎么选
如果你写的是业务代码,我自己的倾向是优先写“团队最容易一眼读懂”的版本,而不是最花哨的版本。
- C# 优先用清晰的 LINQ。
- Python 优先用推导式和内置容器。
- JavaScript 优先用
map、filter这类常见组合。 - Go 在数据规模不大时,普通循环通常就够了。
- C++ 和 Rust 更适合在你明确需要控制性能、所有权或底层行为时再写得更复杂。
总结
跨语言看集合操作,最重要的不是记住每种语法,而是看清“这一门语言鼓励你怎样处理数据流”。
一旦把筛选、分组、映射、排序、去重这些基础操作都对上号,再去读真实项目代码,会轻松很多。