有没有办法在 JavaScript 中实现
map
/reduce
/filter
/等 Set
或者我必须自己编写?
这里有一些明智的
Set.prototype
扩展
Set.prototype.map = function map(f) {
var newSet = new Set();
for (var v of this.values()) newSet.add(f(v));
return newSet;
};
Set.prototype.reduce = function(f,initial) {
var result = initial;
for (var v of this) result = f(result, v);
return result;
};
Set.prototype.filter = function filter(f) {
var newSet = new Set();
for (var v of this) if(f(v)) newSet.add(v);
return newSet;
};
Set.prototype.every = function every(f) {
for (var v of this) if (!f(v)) return false;
return true;
};
Set.prototype.some = function some(f) {
for (var v of this) if (f(v)) return true;
return false;
};
我们来一组吧
let s = new Set([1,2,3,4]);
还有一些愚蠢的小功能
const times10 = x => x * 10;
const add = (x,y) => x + y;
const even = x => x % 2 === 0;
看看它们是如何工作的
s.map(times10); //=> Set {10,20,30,40}
s.reduce(add, 0); //=> 10
s.filter(even); //=> Set {2,4}
s.every(even); //=> false
s.some(even); //=> true
这不是很好吗?是的,我也这么认为。将其与丑陋的迭代器用法进行比较
// puke
let newSet = new Set();
for (let v in s) {
newSet.add(times10(v));
}
还有
// barf
let sum = 0;
for (let v in s) {
sum = sum + v;
}
有没有更好的方法在 JavaScript 中使用
map
来完成 reduce
和 Set
?
一种简便的方法是通过 ES6 扩展运算符将其转换为数组。
然后所有数组功能都可以使用了。
const mySet = new Set([1,2,3,4]);
[...mySet].reduce(...);
总结一下评论中的讨论:虽然没有技术原因设置为not有
reduce
,但目前还没有提供,我们只能希望它在ES7中有所改变。
至于
map
,单独调用它可能会违反Set
约束,因此它在这里的存在可能是有争议的。
考虑使用函数进行映射
(a) => 42
- 它将把集合的大小更改为 1,这可能也可能不是是你想要的。
如果您同意违反该规定,因为例如无论如何你都要折叠,你可以在将每个元素传递给
map
之前将 reduce
部分应用于每个元素,从而接受中间集合(此时不是 Set)被减少可能有重复的元素。这本质上就相当于转成Array来做处理。
map
/reduce
集合上缺少filter
/Map
/Set
的原因似乎主要是概念问题。 Javascript 中的每个集合类型实际上是否应该指定自己的迭代方法只是为了允许这样做
const mySet = new Set([1,2,3]);
const myMap = new Map([[1,1],[2,2],[3,3]]);
mySet.map(x => x + 1);
myMap.map(([k, x]) => [k, x + 1]);
而不是
new Set(Array.from(mySet.values(), x => x + 1));
new Map(Array.from(myMap.entries(), ([k, x]) => [k, x + 1]));
另一种方法是将 map/reduce/filter 指定为可迭代/迭代器协议的一部分,因为
entries
/values
/keys
返回 Iterator
s。但可以想象的是,并非每个可迭代对象都是“可映射的”。另一种选择是为此目的指定一个单独的“收集协议”。
但是,我不知道 ES 目前关于这个话题的讨论。
认为这在速度方面值得一提,如果您正在选择 Set 方法
forEach()
(为每个元素调用回调)或 values()
(返回包含 Set 中所有值的迭代器)。
举个例子,让我们从集合中筛选出偶数:
const generateSet = (n, m) => {
// Generate list of length n with random numbers between 0 and m
let arr = Array.from({ length: n }, () =>
Math.floor(Math.random() * m)
);
// Convert to Set
var set = new Set(arr);
return set;
};
我们的两个过滤功能:
const filterValues = (set) => {
// Using Iterator
const it = set.values();
let result = it.next();
while (!result.done) {
if (result.value % 2 === 0) {
set.delete(result.value);
}
result = it.next();
}
};
const filterForEach = (set) => {
// invokes a callback
set.forEach((item) => {
if (item % 2 === 0) {
set.delete(item);
}
});
};
对于计时,我们使用对随机数组进行计时,包括范围 [0, 10,000,000] 中包含 5,000,000 个项目的数字:
let setValues = generateSet(5000000, 10000000);
console.time("Filter with values()");
filterValues(setValues);
console.timeEnd("Filter with values()");
let setForEach = generateSet(5000000, 10000000);
console.time("Filter with forEach()");
filterForEach(setForEach);
console.timeEnd("Filter with forEach()");
结果是:
Filter with values(): 399.456ms
Filter with forEach(): 374.698ms
或者你可以坚持使用数组方法:
const arrMethod = (set) => {
// Using Array method
const filter = [...set].filter((item) => item % 2 === 0);
return filter;
};
let setArray = generateSet(5000000, 10000000);
console.time("Filter with array");
filterForEach(setArray);
console.timeEnd("Filter with array");
似乎始终更快...
Filter with values(): 356.486ms
Filter with forEach(): 386.825ms
Filter with array: 342.358ms
从 ECMAScript 2025 开始,类似的方法现在作为 迭代器辅助方法 存在。这意味着您可以在函数表达式中使用它们,同时仍然避免创建中间数组。
new Set(s.values().map(times10)); //=> Set {10,20,30,40}
s.values().reduce(add, 0); //=> 10
new Set(s.values().filter(even)); //=> Set {2,4}
s.values().every(even); //=> false
s.values().some(even); //=> true
const set = new Set([1,2,3,4,5]);
function filterSet(index) {
set.delete([...set][index]);
}
filterSet(3); // Set { 1, 2, 3, 5, [size]: 4 }
认为这是“过滤”集合的一个相当不错的解决方案。