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

Ale*_*lls 4 javascript functional-programming node.js

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

 const files = results.filter(function(r){
      return r.file;
    })
    .map(function(r){
       return r.file;
    });
Run Code Online (Sandbox Code Playgroud)

为了节省代码行,以及提高性能,我正在寻找:

const files = results.filterAndMap(function(r){
  return r.file;
});
Run Code Online (Sandbox Code Playgroud)

这是否存在,或者我应该自己写点什么?我想在几个地方使用这样的功能,从来没有费心去研究它.

Tha*_*you 8

传感器

在最通用的形式中,您的问题的答案在于传感器.但是,我们走得太抽象之前,让我们先看看一些基础知识-下面,我们实行一对夫妇换能器mapReduce,filterReduce以及tapReduce; 你可以添加你需要的任何其他.

const mapReduce = map => reduce =>
  (acc, x) => reduce (acc, map (x))
  
const filterReduce = filter => reduce =>
  (acc, x) => filter (x) ? reduce (acc, x) : acc
  
const tapReduce = tap => reduce =>
  (acc, x) => (tap (x), reduce (acc, x))

const tcomp = (f,g) =>
  k => f (g (k))

const concat = (xs,ys) =>
  xs.concat(ys)
  
const transduce = (...ts) => xs =>
  xs.reduce (ts.reduce (tcomp, k => k) (concat), [])

const main =
  transduce (
    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)))
      
const data =
  [{file: 1}, {file: undefined}, {}, {file: 2}]
  
console.log (main (data))
// with: { file: 1 }
// has file: 1
// final: 1
// with: { file: undefined }
// with: {}
// with: { file: 2 }
// has file: 2
// final: 2
// => [ 1, 2 ]
Run Code Online (Sandbox Code Playgroud)

可连接的API

也许您对代码的简单性感到满意,但您对某些非传统的API不满意.如果你想保留的能力,以连锁.map,.filter,.whatever电话不增加不必要的迭代,我们可以做一个通用接口,用以转换,使我们可链接的API最重要的是-这个答案是从我上面分享的链接和适应其他答案我有关于传感器

// Trans Monoid
const Trans = f => ({
  runTrans: f,
  concat: ({runTrans: g}) =>
    Trans (k => f (g (k)))
})

Trans.empty = () =>
  Trans(k => k)

// transducer "primitives"
const mapper = f =>
  Trans (k => (acc, x) => k (acc, f (x)))
  
const filterer = f =>
  Trans (k => (acc, x) => f (x) ? k (acc, x) : acc)
  
const tapper = f =>
  Trans (k => (acc, x) => (f (x), k (acc, x)))
  
// chainable API
const Transduce = (t = Trans.empty()) => ({
  map: f =>
    Transduce (t.concat (mapper (f))),
  filter: f =>
    Transduce (t.concat (filterer (f))),
  tap: f =>
    Transduce (t.concat (tapper (f))),
  run: xs =>
    xs.reduce (t.runTrans ((xs,ys) => xs.concat(ys)), [])
})

// demo
const main = data =>
  Transduce()
    .tap (x => console.log('with:', x))
    .filter (x => x.file)
    .tap (x => console.log('has file:', x.file))
    .map (x => x.file)
    .tap (x => console.log('final:', x))
    .run (data)
    
const data =
  [{file: 1}, {file: undefined}, {}, {file: 2}]

console.log (main (data))
// with: { file: 1 }
// has file: 1
// final: 1
// with: { file: undefined }
// with: {}
// with: { file: 2 }
// has file: 2
// final: 2
// => [ 1, 2 ]
Run Code Online (Sandbox Code Playgroud)

可连接的API,需要2

作为一个练习来实现用尽可能少的依赖仪式尽可能链接API,我重写了代码片段,而不依赖于Trans幺实施或原始传感器mapper,filterer等等-感谢您的评论@ftor.

就整体可读性而言,这是一个明确的降级.我们失去了只看它并理解发生了什么的能力.我们也失去了monoid接口,这使我们很容易在其他表达式中推理我们的传感器.这里的一大收获Transduce是包含在10行源代码中的定义; 与之前的28个相比 - 所以虽然表达式更复杂,但在大脑开始挣扎之前,你可能已经完成了整个定义的阅读

// chainable API only (no external dependencies)
const Transduce = (t = k => k) => ({
  map: f =>
    Transduce (k => t ((acc, x) => k (acc, f (x)))),
  filter: f =>
    Transduce (k => t ((acc, x) => f (x) ? k (acc, x) : acc)),
  tap: f =>
    Transduce (k => t ((acc, x) => (f (x), k (acc, x)))),
  run: xs =>
    xs.reduce (t ((xs,ys) => xs.concat(ys)), [])
})

// demo (this stays the same)
const main = data =>
  Transduce()
    .tap (x => console.log('with:', x))
    .filter (x => x.file)
    .tap (x => console.log('has file:', x.file))
    .map (x => x.file)
    .tap (x => console.log('final:', x))
    .run (data)
    
const data =
  [{file: 1}, {file: undefined}, {}, {file: 2}]

console.log (main (data))
// with: { file: 1 }
// has file: 1
// final: 1
// with: { file: undefined }
// with: {}
// with: { file: 2 }
// has file: 2
// final: 2
// => [ 1, 2 ]
Run Code Online (Sandbox Code Playgroud)

>谈谈表现

在速度方面,没有任何功能变体能够击败静态for循环,它将所有程序语句组合在一个循环体中.然而,上述传感器确实有可能比一系列.map/ .filter/ .whatever调用更快,其中通过大数据集的多次迭代将是昂贵的.

编码风格和实施

传感器的本质在于mapReduce,这就是我选择首先引入它的原因.如果你能理解如何进行多次mapReduce调用并将它们排列在一起,你就会理解传感器.

当然,您可以通过多种方式实现传感器,但我发现Brian的方法最有用,因为它将传感器编码为幺半群 - 有一个monoid允许我们对它做出各种方便的假设.一旦我们转换了一个阵列(一种类型的幺半群),您可能想知道如何转换任何其他幺半群......在这种情况下,请阅读该文章!


Gaa*_*far 7

如果你真的需要在1个函数中执行它,你需要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
  []
)
Run Code Online (Sandbox Code Playgroud)

如果你需要挤出更多的性能,您可以更改concat到push,回到原来的累加器阵列,以避免产生额外的阵列.

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

files = []
for (var i = 0; i < results.length; i++) {
  var file = results[i].file
  if (file) files.push(file)
}
Run Code Online (Sandbox Code Playgroud)

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