同时映射和过滤数组


155

我有一个要迭代的对象数组,以生成一个新的过滤后的数组。但是,我还需要根据参数从新数组中滤除一些对象。我正在尝试:

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

那是一个好方法吗?有没有更好的方法?我愿意使用lodash之类的任何库。




3
.reduce()绝对比.filter(...).map(...)我在其他地方看到的建议要快。我设置了一个JSPerf测试来演示stackoverflow.com/a/47877054/2379922
贾斯汀·L

Answers:


218

您应该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>


另外,减速器可以是一个纯函数,像这样

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

2
对我来说,第一个参数filtered是一个对象。所以filtered.push对我来说是不确定的。
Rahmathullah M

4
filtered作为对象我也有问题。这是因为我没有在[]reduce函数之后传递“初始值”-空数组()。例如,不 var reduced = options.reduce(function(filtered, option) { ... }); 正确的正确 var reduced = options.reduce(function(filtered, option) { ... }, []);
乔恩·希金斯

这里没有理由reduce,您可以很好地使用,forEach因为在每次迭代中都更改了数组filtered,因此它不是纯粹的功能。这将使事件更具可读性和紧凑性。
Marko

1
@Marko我也介绍了纯减速器。PTAL。
thefourtheye

没错,现在是纯净的。为您的努力+1。并不是我纯粹是函数式编程的提倡者,远非如此,我只是看不出要点:)但是实际上,我在看到它之后就使用了您的把戏,因为在给定的情况下它非常方便。但是对于此任务,您可能需要看一看flatMap函数,我认为它是在您做出回答后成为标准的(同样由于某些原因,某些浏览器不支持该功能)。因为这样的连接数组使O(n ^ 2)任务脱离O(n)任务,所以它应该更具性能。
Marko

43

使用减少,卢克!

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

29

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

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

在上面链接的MDN页面上:

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


4
很好的解决方案!但是,读者请记住,这flatMap是最近才推出的,并且其浏览器支持有限。您可能要使用官方的polyfill / shims:flatMapflat
Arel

24

使用ES6,您可以做得很短:

options.filter(opt => !opt.assigned).map(opt => someNewObject)


25
这将执行两个循环,具体取决于返回的过滤器是否仍会耗尽内存。
hogan's

1
@hogan,但这是原始查询的正确答案(可能不是最佳解决方案)
vikramvi

1
@vikramvi感谢您的来信。关键是,我们可以通过多种方式实现相同的目标,我更喜欢最好的方式。
hogan

10

reduceES6花式扩展语法的一行在这里!

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);


1
真的很好@Maxim!我支持这个!但是...在添加的每个元素上,它必须将所有元素散布在result...中,就像filter(assigned).map(name)解决方案一样
0zkr PM

好一个。我花了一点时间才知道发生了什么事情...assigned ? [name] : []-可能更...(assigned ? [name] : [])
容易读懂

5

我会发表评论,但我没有所需的声誉。对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.concat(name) : result, []);

console.log(filtered);

说明

我们不会在每次迭代中一遍又一遍地散布整个结果,而是仅在实际存在要插入的值时才追加到数组。


3

使用Array.prototy.filter本身

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

2

在某个时候,使用它难吗(或同样容易) 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。


你可能要检查你的模因... ;-)
阿雷尔

2

我通过以下几点优化了答案:

  1. 改写if (cond) { stmt; }cond && stmt;
  2. 使用ES6 箭头功能

我将提出两种解决方案,一种使用forEach,另一种使用reduce

解决方案1:使用forEach

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

解决方案2:使用reduce

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

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


1
cond && stmt;到底为什么要使用?这很难阅读,根本没有任何好处。
jlh

1

使用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 现在将包含过滤的对象。


0

直接使用.reduce可能很难看懂,因此我建议创建一个为您生成减速器的函数:

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'];
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.