如何在 JavaScript 中映射/减少/过滤集合?

Posted

技术标签:

【中文标题】如何在 JavaScript 中映射/减少/过滤集合?【英文标题】:How to map/reduce/filter a Set in JavaScript? 【发布时间】:2016-01-18 23:53:24 【问题描述】:

有没有办法在 javascriptmap/reduce/filter/etc 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 中使用 Set 来完成 mapreduce

【问题讨论】:

map-reduce-ing Set 的问题在于 Set 不是 Functor。 好吧,考虑var s = new Set([1,2,3,4]); s.map((a) => 42);。它改变了元素的数量,map 通常不应该这样做。如果您只比较保留对象的一部分,那就更糟了,因为从技术上讲,您会得到哪个是未指定的。 我曾考虑过,但我不确定我(个人)是否会认为这是无效的。好的,所以至少 forEach 存在这种情况,但为什么没有 reduce 呢? 我会说这是一个疏忽。折叠(减少)一个 Set 非常好。 一些相关阅读:esdiscuss.org/topic/set-some-every-reduce-filter-map-methods 【参考方案1】:

总结来自 cmets 的讨论:虽然设置为 not 没有技术原因 reduce,但目前还没有提供,我们只能希望它在 ES7 中有所改变。

至于map,单独调用它可能违反Set 约束,因此它的存在可能存在争议。

考虑使用函数 (a) => 42 进行映射 - 它会将集合的大小更改为 1,这可能也可能不是您想要的。

如果您同意违反该规则,例如,无论如何你都要折叠,你可以在每个元素上应用 map 部分,然后将它们传递给 reduce,从而接受中间集合(此时不是 Set) 将要减少的可能有重复的元素。这本质上相当于转换为Array进行处理。

【讨论】:

这很好,除了(使用上面的代码),s.map(a => 42) 将导致Set 42 ,因此映射的结果将是不同的长度,但不会有“重复”元素。也许更新措辞,我会接受这个答案。 @naomik 哦,天哪,我刚喝完第一杯咖啡。再看一遍,传递给 reduce 的中间集合 可能 如果您接受它不是集合,则它具有直接元素 - 这就是我的意思。 哦,我明白了 - 地图必须映射到相同的类型,因此可能在目标集中发生冲突。当我发现这个问题时,我在想 map 会映射到一个集合中的一个数组。 (就像你做了 set.toArray().map()` 在 Scala 和 Haskell 中,集合支持映射操作——它可以减少集合中元素的数量。【参考方案2】:

Map/Set 集合上缺少map/reduce/filter 的原因似乎主要是概念问题。 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 返回 Iterators。可以想象,并非每个可迭代对象也是“可映射的”。另一种选择是为此目的指定一个单独的“收集协议”。

但是,我不知道 ES 目前对此主题的讨论。

【讨论】:

【参考方案3】:

一种简便的方法是通过 ES6 扩展运算符将其转换为数组。

那么你就可以使用所有的数组函数了。

const mySet = new Set([1,2,3,4]);
[...mySet].reduce()

【讨论】:

因为Set的功能不可用!这是一个完整的、有指导的和理解的解决方法,目前还没有出现在本主题中。在 Set 实现这些功能之前,“需要更长时间”的事实是为解决方法付出的可悲代价! this和Array.from有什么区别 至少对我来说,这和Array.from 之间的区别在于Array.from 与TypeScript 一起工作。使用[...mySet] 会出现错误:TS2461: Type 'Set<number>' is not an array type. 对于 spread 与 Array.from(),请参阅 ***.com/a/40549565/5516454 基本上,两者都可以在这里使用。 Array.from() 还可以执行不实现@@iterator 方法的类数组对象。 仍然不适用于我的打字稿。我得到ERROR TypeError: this.sausages.slice is not a function

以上是关于如何在 JavaScript 中映射/减少/过滤集合?的主要内容,如果未能解决你的问题,请参考以下文章

如何将一系列日期绑定值映射/减少到 JavaScript / RXJS 中的运行总数?

Javascript通过包含搜索词的标签子数组过滤或减少每个JSON对象

在 Javascript 中有效地逐步过滤大型数据集

javascript Array .filter / .map / .reduce ... etc - 在集合上映射然后减少它

javascript ES6过滤减少

javascript 地图,过滤,减少