Javascript 在同一迭代中过滤和映射

Javascript 在同一迭代中过滤和映射,javascript,node.js,functional-programming,Javascript,Node.js,Functional Programming,我有一个简单的情况,我想过滤并映射到相同的值,如下所示: const files = results.filter(function(r){ return r.file; }) .map(function(r){ return r.file; }); 为了节省代码行并提高性能,我正在寻找: const files = results.filterAndMap(function(r){ return r.file; }); 这是存在的,还

我有一个简单的情况,我想过滤并映射到相同的值,如下所示:

 const files = results.filter(function(r){
      return r.file;
    })
    .map(function(r){
       return r.file;
    });
为了节省代码行并提高性能,我正在寻找:

const files = results.filterAndMap(function(r){
  return r.file;
});

这是存在的,还是我应该自己写点什么?我曾经在一些地方想要这样的功能,只是以前从来没有考虑过。

如果你真的需要在一个函数中实现它,你需要像这样使用
reduce

results.reduce(
  // add the file name to accumulator if it exists
  (acc, result) => result.file ? acc.concat([result.file]) : acc,
  // pass empty array for initial accumulator value
  []
)
如果需要压缩更多性能,可以将
concat
更改为
push
并返回原始累加器数组,以避免创建额外的数组

然而,最快的解决方案可能是一个好的
for
循环,它避免了所有函数调用和堆栈帧

files = []
for (var i = 0; i < results.length; i++) {
  var file = results[i].file
  if (file) files.push(file)
}
文件=[]
对于(var i=0;i

但我认为
filter/map
方法更具表现力和可读性

您可以使用
Array.prototype.reduce()

constresults=[{file:{file:1}},{notfile:{file:1}}];
const files=results.reduce(函数(arr,r){
返回r.file?arr=[…arr,r.file.file]:arr;
}, []);

console.log(文件);//1
您可以使用带有空数组的
o.file
或concat的值作为结果

results.reduce((r, o) => r.concat(o.file || []), []);

为什么不干脆用forEach

const文件=[];
结果:forEach(函数(r){
if(r.file){
文件推送(r.file);
}

});要提高性能,您必须衡量哪种解决方案更快。让我们玩一会儿

欢迎使用任何其他测试用例

如果您想对NodeJS使用基准测试(请记住
npm i benchmark

过程:


acc初始化为[](空数组)

传感器

以最一般的形式,你的问题的答案在于。但在我们过于抽象之前,让我们先看一些基础知识–下面,我们实现了几个传感器
mapReduce
filtereduce
,和
tapReduce
;您可以添加所需的任何其他内容

const-mapReduce=map=>reduce=>
(acc,x)=>减少(acc,map(x))
常量filterReduce=filter=>reduce=>
(acc,x)=>过滤器(x)?减少(acc,x):acc
const tapReduce=tap=>reduce=>
(acc,x)=>(抽头(x),减少(acc,x))
常数tcomp=(f,g)=>
k=>f(g(k))
const concat=(xs,ys)=>
xs.concat(ys)
常数转换=(…ts)=>xs=>
xs.reduce(ts.reduce(tcomp,k=>k)(concat),[])
恒干管=
转换(
tapReduce(x=>console.log('with:',x)),
filterReduce(x=>x.file),
tapReduce(x=>console.log('has file:',x.file)),
mapReduce(x=>x.file),
tapReduce(x=>console.log('final:',x)))
常量数据=
[{file:1},{file:undefined},{},{file:2}]
console.log(主(数据))
//使用:{file:1}
//有文件:1
//决赛:1
//使用:{file:undefined}
//与:{}
//使用:{file:2}
//档案编号:2
//决赛:2

//=>[1,2]
什么是
结果
?多维数组
[{file:{file:1}},{notfile:{file:1}}]
?结果只是一个对象数组:
[{},{file:x},{},{},{file:y}]
,等等。“结果只是一个对象数组:
[{},{file:x},{},{},{file:y}]
”这个数组与JavaScript的上下文不匹配。在这种情况下,不需要使用
.map()
。您可以单独使用
.filter()
来返回预期结果。或者,我没有按照您的注释进行操作,或者可能没有正确解释问题。最初将
结果解释为嵌套数组。为什么需要
.map()
?要返回
[x,y]
的数组?预期的结果是什么?你能将forEach添加到比较中吗?@ponury kostek给你-看起来forEach是挑战者中最快的。我知道,这就是为什么我很惊讶他们试图用一些奇怪的方式来做这件事。我猜
reduce
选项的性能问题并不是因为它使用
reduce
,而是因为它使用
concat
。。。我经常使用
(acc.push(x),acc)
而不是
acc.concat(x)
。它变异了
acc
数组,但由于数组是在函数内部创建的,所以问题应该不会太大。
reduce
是比最便宜的操作成本最高的操作-正如所证明的那样,比较过滤器然后映射到reduce,至少在您的答案中提供的屏幕截图中,@KrzysztofSafjanowski,显示了两者之间的细微差别。@KrzysztofSafjanowski哇,我的道歉更贵,谢谢你提到我。谢谢你提到我,我一直认为reduce更便宜。@ajilantang再等几次v8迭代,可能就不会了。从逻辑上讲,
reduce
应该更便宜,因为您只需要迭代数组一次。我猜这两件事中的一件或两件都发生了:JIT优化了
map/filter
案例中的一个过程和/或优化了中间数据结构的创建并生成了更少的垃圾。看起来很有趣,将进行调查:)Brain讨论了逆变函子,并将它们与布尔幺半群结合在一起创建可组合谓词。实际上,你完成了他的博客文章,实现了一个单向换能器——我不确定你的构图是否是反向的。无论如何,这是一项出色的工作(如果有人喜欢方法链接——我不喜欢:D如果有可能的话,我会在接下来的几天里尝试简化它。这个答案将我的Javascript IQ提高了30分或更多。哇!这是很多连接
var suite = new (require('benchmark')).Suite

function getSampleInput() {
  return [{file: 'foo'}, {other: 'bar'}, {file: 'baz'}, {file: 'quux'}, {other: 'quuxdoo'}, {file: 'foobar'}, {file: 'foo'}, {other: 'bar'}, {file: 'baz'}, {file: 'quux'}, {other: 'quuxdoo'}, {file: 'foobar'}, {file: 'foo'}, {other: 'bar'}, {file: 'baz'}, {file: 'quux'}, {other: 'quuxdoo'}, {file: 'foobar'}, {file: 'foo'}, {other: 'bar'}, {file: 'baz'}, {file: 'quux'}, {other: 'quuxdoo'}, {file: 'foobar'}, {file: 'foo'}, {other: 'bar'}, {file: 'baz'}, {file: 'quux'}, {other: 'quuxdoo'}, {file: 'foobar'}, {file: 'foo'}, {other: 'bar'}, {file: 'baz'}, {file: 'quux'}, {other: 'quuxdoo'}, {file: 'foobar'}, {file: 'foo'}, {other: 'bar'}, {file: 'baz'}, {file: 'quux'}, {other: 'quuxdoo'}, {file: 'foobar'}, {file: 'foo'}, {other: 'bar'}, {file: 'baz'}, {file: 'quux'}, {other: 'quuxdoo'}, {file: 'foobar'}]
}

// author https://stackoverflow.com/users/3716153/gaafar 
function reduce(results) {
  return results.reduce(
    (acc, result) => result.file ? acc.concat([result.file]) : acc ,
    []
  )  
}

// author https://stackoverflow.com/users/1223975/alexander-mills
function filterThanMap(results) {
  return results.filter(function(r){
    return r.file;
  })
  .map(function(r){
     return r.file;
  });
}

// author https://stackoverflow.com/users/5361130/ponury-kostek
function forEach(results) {
  const files = [];

  results.forEach(function(r){
    if(r.file) files.push(r.file); 
  });

  return files
}

suite
  .add('filterThanMap', function() {filterThanMap(getSampleInput())})
  .add('reduce', function() {reduce(getSampleInput())})
  .add('forEach', function() {forEach(getSampleInput())})
  .on('complete', function() {
    console.log('results:')
    this.forEach(function(result) {
      console.log(result.name, result.count, result.times.elapsed)
    })
    console.log('the fastest is', this.filter('fastest').map('name')[0])
  })
  .run()
   const file = (array) => {
     return array.reduce((acc,curr) => curr.file ? acc.concat(curr) : acc, 
     [])
    }