同时映射和过滤数组

Posted

技术标签:

【中文标题】同时映射和过滤数组【英文标题】:Map and filter an array at the same time 【发布时间】:2016-03-27 16:20:31 【问题描述】:

我有一个对象数组,我想对其进行迭代以生成一个新的过滤数组。而且,我需要根据参数从新数组中过滤掉一些对象。我正在尝试这个:

function renderOptions(options) 
    return options.map(function (option) 
        if (!option.assigned) 
            return (someNewObject);
        
    );   

这是一个好方法吗?有没有更好的方法?我愿意使用任何库,例如 lodash。

【问题讨论】:

“object.keys”方法怎么样? developer.mozilla.org/fr/docs/Web/javascript/Reference/… 使用减少:developer.mozilla.org/fr/docs/Web/JavaScript/Reference/… .reduce() 绝对比我在其他地方看到的.filter(...).map(...) 更快。我设置了一个 JSPerf 测试来演示 ***.com/a/47877054/2379922 【参考方案1】:

您应该为此使用Array.reduce

var options = [
   name: 'One', assigned: true , 
   name: 'Two', assigned: false , 
   name: 'Three', assigned: true , 
];

var reduced = options.reduce(function(filtered, option) 
  if (option.assigned) 
     var someNewValue =  name: option.name, newProperty: 'Foo' 
     filtered.push(someNewValue);
  
  return filtered;
, []);

document.getElementById('output').innerhtml = JSON.stringify(reduced);
<h1>Only assigned options</h1>
<pre id="output"> </pre>

或者,reducer 可以是一个纯函数,像这样

var reduced = options.reduce(function(result, option) 
  if (option.assigned) 
    return result.concat(
      name: option.name,
      newProperty: 'Foo'
    );
  
  return result;
, []);

【讨论】:

对我来说,第一个参数filtered 是一个对象。所以 filtered.push 对我来说是未定义的。 我也有一个问题,filtered 是一个对象。这是因为我没有传入“初始值”——reduce 函数之后的空数组 ([])。例如不正确var reduced = options.reduce(function(filtered, option) ... ); 正确var reduced = options.reduce(function(filtered, option) ... , []); @Marko 我还介绍了一个纯减速器。 PTAL。 Yap 现在很纯净。 +1 为您的努力。并不是说我是纯粹的函数式编程倡导者,远非如此,我只是看不出重点:) 但实际上我在看到它之后就使用了你的技巧,因为它在给定的情况下非常方便。但是对于这个任务,您可能想看看 flatMap 功能,我认为它在您做出回答后成为标准(也因为这个原因,某些浏览器可能不支持)。它应该更高效,因为像这样连接数组会使 O(n^2) 任务脱离 O(n) 任务。 @Marko flatMap 不能在这里使用,因为它不允许我们过滤。除非,在这种情况下我们返回一个空数组。我相信这对读者来说并不容易。【参考方案2】:

自 2019 年以来,Array.prototype.flatMap 是不错的选择。

options.flatMap(o => o.assigned ? [o.name] : []);

来自上面链接的 MDN 页面:

flatMap 可以用作添加和删除项目的一种方式(修改 地图期间的项目数)。换句话说,它允许您映射 许多项目对许多项目(通过分别处理每个输入项目), 而不是总是一对一的。从这个意义上说,它的工作原理类似于 过滤器的对面。只需返回一个 1 元素数组来保留该项目, 用于添加项目的多元素数组,或用于删除的 0 元素数组 项目。

【讨论】:

这在性能方面与使用 2 个循环(分别调用 array.filter 和 array.map)相比如何? 我使用 jsbench.me 和 jsben.ch 进行了比较,得到了非常不同的结果。第一次 flatMap 慢了 5 倍,第二次快了 2 倍。所以我不知道如何对其进行基准测试。【参考方案3】:

使用 reduce,卢克!

function renderOptions(options) 
    return options.reduce(function (res, option) 
        if (!option.assigned) 
            res.push(someNewObject);
        
        return res;
    , []);   

【讨论】:

【参考方案4】:

使用 ES6,你可以做的很短:

options.filter(opt =&gt; !opt.assigned).map(opt =&gt; someNewObject)

【讨论】:

@hogan 但这是原始查询的正确答案(可能不是最佳解决方案) @vikramvi 谢谢你的来信。问题是,我们可以通过多种方式实现相同的目标,我更喜欢最好的方式。 这并不完全正确,因为在这里您丢失了初始定位值的索引,并且该信息在运行地图 fn 时非常有用。 这种方式很好,但这个问题是同时映射和过滤数组最好的方式是options.reduce(res,options.... answer @Zuker【参考方案5】:

一行reduce 与 ES6 花哨spread syntax 在这里!

var options = [
   name: 'One', assigned: true , 
   name: 'Two', assigned: false , 
   name: 'Three', assigned: true , 
];

const filtered = options
  .reduce((result, name, assigned) => [...result, ...assigned ? [name] : []], []);

console.log(filtered);

【讨论】:

真的很好@Maxim!我赞成这个!但是...在添加的每个元素上,它必须传播result...中的所有元素...就像filter(assigned).map(name)解决方案 不错的一个。我花了一秒钟才意识到...assigned ? [name] : [] 发生了什么——它可能更易读为...(assigned ? [name] : [])【参考方案6】:

我会发表评论,但我没有所需的声誉。对 Maxim Kuzmin 原本非常好的答案的一个小改进,以提高效率:

const options = [
   name: 'One', assigned: true , 
   name: 'Two', assigned: false , 
   name: 'Three', assigned: true , 
];

const filtered = options
  .reduce((result,  name, assigned ) => assigned ? result.push(name) && result : result, []);

console.log(filtered);

说明

我们不是在每次迭代中一遍又一遍地传播整个结果,而是只追加到数组中,并且只有在实际有一个值要插入时。

【讨论】:

.concat() 返回一个新数组,它实际上与将旧数组和新项分散到一个新数组中相同,MDN here。您想按照您的描述使用.push()“追加”。 @MarceDev 啊,你是对的,谢谢。将更新我的答案来做到这一点。然后语法变得有点奇怪,因为 push 不返回数组。【参考方案7】:

在某些时候,使用forEach 是不是更容易(或同样容易)

var options = [
   name: 'One', assigned: true , 
   name: 'Two', assigned: false , 
   name: 'Three', assigned: true , 
];

var reduced = []
options.forEach(function(option) 
  if (option.assigned) 
     var someNewValue =  name: option.name, newProperty: 'Foo' 
     reduced.push(someNewValue);
  
);

document.getElementById('output').innerHTML = JSON.stringify(reduced);
<h1>Only assigned options</h1>
<pre id="output"> </pre>

但是,如果有一个 malter()fap() 函数结合了 mapfilter 函数,那就太好了。它会像过滤器一样工作,除了返回 true 或 false 之外,它会返回任何对象或 null/undefined。

【讨论】:

你可能想检查你的模因... ;-) 但是你不会炫耀你的 1 行 133t JS,除了你之外没有人理解。 这些名字是金,你应该向 TC39 提交提案【参考方案8】:

使用Array.prototy.filter 本身

function renderOptions(options) 
    return options.filter(function(option)
        return !option.assigned;
    ).map(function (option) 
        return (someNewObject);
    );   

【讨论】:

如果你想要的值是在做过滤器的过程中计算出来的,这样就不太好用了;你必须复制那部分代码。【参考方案9】:

我用以下几点优化了答案:

    if (cond) stmt; 重写为cond &amp;&amp; stmt; 使用 ES6 Arrow Functions

我将介绍两种解决方案,一种使用forEach,另一种使用reduce:

解决方案 1:使用 forEach

该解决方案通过使用forEach 遍历每个元素来工作。然后,在forEach 循环的主体中,我们有条件充当过滤器,它决定我们是否要向结果数组附加一些东西。

const options = [
   name: 'One', assigned: true , 
   name: 'Two', assigned: false , 
   name: 'Three', assigned: true , 
];
const reduced = [ ];
options.forEach(o => 
  o.assigned && reduced.push(  name: o.name, newProperty: 'Foo'  );
 );
console.log(reduced);

解决方案 2:使用 reduce

此解决方案使用Array.prototype.reduce 而不是forEach 来遍历数组。它认识到reduce 具有内置的初始化程序和循环机制这一事实。除此之外,此解决方案与forEach 解决方案或多或少相同,因此差异归结为修饰语法糖。

const options = [
   name: 'One', assigned: true , 
   name: 'Two', assigned: false , 
   name: 'Three', assigned: true , 
];
const reduced = options.reduce((a, o) => 
  o.assigned && a.push(  name: o.name, newProperty: 'Foo'  );
  return a;
, [ ] );
console.log(reduced);

由您决定采用哪种解决方案。

【讨论】:

你到底为什么要使用cond &amp;&amp; stmt;?这更难阅读,而且根本没有任何好处。【参考方案10】:

使用 reduce,您可以在一个 Array.prototype 函数中执行此操作。这将从数组中获取所有偶数。

var arr = [1,2,3,4,5,6,7,8];

var brr = arr.reduce((c, n) => 
  if (n % 2 !== 0) 
    return c;
  
  c.push(n);
  return c;
, []);

document.getElementById('mypre').innerHTML = brr.toString();
<h1>Get all even numbers</h1>
<pre id="mypre"> </pre>

您可以使用相同的方法并将其推广到您的对象,就像这样。

var arr = options.reduce(function(c,n)
  if(somecondition) return c;
  c.push(n);
  return c;
, []);

arr 现在将包含过滤后的对象。

【讨论】:

【参考方案11】:

直接使用.reduce 可能难以阅读,因此我建议创建一个为您生成reducer 的函数:

function mapfilter(mapper) 
  return (acc, val) => 
    const mapped = mapper(val);
    if (mapped !== false)
      acc.push(mapped);
    return acc;
  ;

像这样使用它:

const words = "Map and filter an array #javascript #arrays";
const tags = words.split(' ')
  .reduce(mapfilter(word => word.startsWith('#') && word.slice(1)), []);
console.log(tags);  // ['javascript', 'arrays'];

【讨论】:

【参考方案12】:

您可以使用Array.reduce 与箭头函数是单行代码

const options = [
   name: 'One', assigned: true , 
   name: 'Two', assigned: false , 
   name: 'Three', assigned: true , 
];

const reduced = options.reduce((result, option) => option.assigned ? result.concat( name: option.name, newProperty: 'Foo' ) : result, []);

document.getElementById('output').innerHTML = JSON.stringify(reduced);
<h1>Only assigned options</h1>
<pre id="output"> </pre>

【讨论】:

【参考方案13】:

我已将这些出色的答案转换为实用函数,我想分享它们:

示例:仅过滤奇数并将其递增

例如[1, 2, 3, 4, 5] -filter-> [1, 3, 5] -map-> [2, 4, 6]

通常你会用 filtermap 这样做

const inputArray = [1, 2, 3, 4, 5];
const filterOddPlusOne = inputArray.filter((item) => item % 2).map((item) => item + 1); // [ 2, 4, 6 ]

使用reduce

const filterMap = <TSource, TTarget>(
  items: TSource[],
  filterFn: (item: TSource) => boolean,
  mapFn: (item: TSource) => TTarget
) =>
  items.reduce((acc, cur): TTarget[] => 
    if (filterFn(cur)) return [...acc, mapFn(cur)];
    return acc;
  , [] as TTarget[]);

使用flatMap

const filterMap = <TSource, TTarget>(
  items: TSource[],
  filterFn: (item: TSource) => boolean,
  mapFn: (item: TSource) => TTarget
) => items.flatMap((item) => (filterFn(item) ? [mapFn(item)] : []));

用法(reduceflatMap 解决方案相同):

const inputArray = [1, 2, 3, 4, 5];
const filterOddPlusOne = filterMap(
  inputArray,
  (item) => item % 2, // Filter only odd numbers
  (item) => item + 1 // Increment each number
); // [ 2, 4, 6 ]

JavaScript 版本

上面的代码在 TypeScript 中,但问题是关于 JavaScript 的。所以,我已经为你删除了所有的泛型和类型:

const filterMap = (items, filterFn, mapFn) =>
  items.reduce((acc, cur) => 
    if (filterFn(cur)) return [...acc, mapFn(cur)];
    return acc;
  , []);
const filterMap = (items, filterFn, mapFn) =>
  items.flatMap((item) => (filterFn(item) ? [mapFn(item)] : []));

【讨论】:

【参考方案14】:

同时执行filter + map 的最有效方法是将数据作为通用可迭代对象处理,并同时执行这两项操作。在这种情况下,您最多只能浏览一次数据。

下面的示例使用iter-ops 库,并且正是这样做的:

import pipe, filter, map from 'iter-ops';

const i = pipe(
    inputArray,
    filter(value => value === 123), // filter on whatever key you want
    map(value => /* any mapping here*/) // remap data as you like
);

// i = iterable that can be processed further;

console.log([...i]); //=> list of new objects

上面,我说的是at most,因为如果你对可迭代的结果应用进一步的逻辑,比如限制映射项的数量,你最终会迭代你的对象列表甚至少于一次:

const i = pipe(
    inputArray,
    filter(value => value === 123), // filter on whatever key you want
    map(value => /* any mapping here*/), // remap as you like
    take(10) // take up to 10 items only
);

在上面,我们进一步限制了迭代,一旦生成了 10 个结果项就停止,因此我们对数据的迭代次数少于一次。这已经是最有效的了。

更新

我被要求补充为什么这个解决方案比reduce 更有效的答案,所以这里是......

Array 的reduce 是一个有限运算,它遍历完整的数据集,以产生结果。因此,当您需要对该输出数据进行进一步处理时,您最终会生成一个新的迭代序列,依此类推。

当您有一个复杂的业务逻辑要应用于序列/可迭代对象时,链接该逻辑总是更有效,同时只对序列进行一次迭代。在许多情况下,您最终会对一个序列进行复杂的处理,甚至一次都没有检查完整的数据集。这就是可迭代数据处理的效率。

附:我是上述库的作者。

【讨论】:

【参考方案15】:

嘿,我刚刚从事这个项目,想在 MDM 文档上分享我基于 Array.prototype.flatMap() 的解决方案:

 const places = [
 latitude: 40,longitude: 1, latitude:41, longitude:2, latitude:44, longitude:2, latitude:NaN, longitude:NaN , latitude:45, longitude:4,latitude:48, longitude:3, latitude:44, longitude:5, latitude:39, longitude:13, latitude:40, longitude:8, latitude:38, longitude:4
 ]
 
 let items = places?.map((place) => [
    
      latitude: (place.latitude),

      longitude:(place.longitude),
    ,
  ]);
   console.log("Items: ", items);
   
   //Remove elements with NaN latitude and longitude
   
    let newItems = places?.flatMap((o) =>
    Number(o.longitude, o.latitude)
      ?  lng: Number(o.longitude), lat: Number(o.latitude) 
      : []
  ); 
  
 console.log("Coordinates after NaN values removed: ", newItems);
一种

【讨论】:

以上是关于同时映射和过滤数组的主要内容,如果未能解决你的问题,请参考以下文章

通过特定映射过滤 2 个结构/数组

过滤和映射数组时的SwiftUI EXC_BAD_INSTRUCTION

如何在 Mongodb 中对多个数组使用聚合映射和过滤器

映射输入数组并获取过滤后的对象数组作为结果

javascript 映射,获取过滤的数组

从数组映射React JS中过滤多个项目[重复]