我有这种简单的情况,我希望过滤并映射到相同的值,如下所示:
const files = results.filter(function(r){
return r.file;
})
.map(function(r){
return r.file;
});
为了保存代码行,以及提高性能,我正在寻找:
const files = results.filterAndMap(function(r){
return r.file;
});
这是否存在,或者我应该自己写点什么?我想在几个地方使用这样的功能,从来没有费心去研究它。
答案 0 :(得分:8)
如果您确实需要在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
[]
)
如果您需要提高性能,可以将concat
更改为push
并返回原始累加器数组,以避免创建额外的数组。
但是,最快的解决方案可能是一个很好的旧for
循环,它可以避免所有函数调用和堆栈帧
files = []
for (var i = 0; i < results.length; i++) {
var file = results[i].file
if (file) files.push(file)
}
但我认为filter/map
方法更具表现力和可读性
答案 1 :(得分:7)
<强>传感器强>
以最通用的形式,您的问题的答案在于transducers。但在我们过于抽象之前,让我们先看一些基础 - 下面,我们实现了几个传感器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 ]
&#13;
可连接API
也许您对代码的简单性感到满意,但您对某些非传统的API感到不满意。如果您希望保留链接.map
,.filter
,.whatever
调用而无需添加过度迭代的功能,我们可以创建一个通用接口来进行转换,并在此基础上创建可链接的API - 这个答案改编自我上面分享的链接和other answers I have about transducers
// 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 ]
&#13;
可连接的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 ]
&#13;
<强>&GT;谈论绩效
在速度方面,没有任何功能变体能够击败静态for
循环,它将所有程序语句组合在一个循环体中。但是,上面的传感器确实 potential 要比一系列.map
/ .filter
/ .whatever
调用快,其中通过大数据集进行多次迭代会很贵。
编码风格&amp;实施强>
传感器的本质在于mapReduce
,这就是我选择首先引入它的原因。如果您能够理解如何进行多个mapReduce
调用并将它们排列在一起,那么您将了解传感器。
当然你可以通过多种方式实现传感器,但我发现Brian's approach最有用,因为它将传感器编码为monoid - 有一个monoid允许我们做出各种方便的假设它。一旦我们转换了一个阵列(一种类型的幺半群),你可能想知道如何转换任何其他幺半群...在这种情况下,阅读那篇文章!
答案 2 :(得分:3)
要提高性能,您必须更快地衡量哪种解决方案。让我们玩一会儿https://jsperf.com/filter-than-map-or-reduce/1
欢迎任何其他测试用例。
如果你想使用针对NodeJS的基准测试(请记住npm i benchmark
)
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()
答案 3 :(得分:2)
为什么不只是forEach
?
const files = [];
results.forEach(function(r){
if(r.file) {
files.push(r.file);
}
});
&#13;
如果速度不够快,您可以使用fast.js
并进行其他微观优化
const files = [];
const length = results.length;
for(var i = 0; i < length; i++) {
if (results[i].file) {
files[files.length] = results[i].file;
}
}
&#13;
答案 4 :(得分:1)
您可以使用o.file
的值或者使用空数组连接结果。
results.reduce((r, o) => r.concat(o.file || []), []);
答案 5 :(得分:0)
您可以使用Array.prototype.reduce()
const results = [{file:{file:1}}, {notfile:{file:1}}];
const files = results.reduce(function(arr, r){
return r.file ? arr = [...arr, r.file.file] : arr;
}, []);
console.log(files); // 1
&#13;
答案 6 :(得分:-2)
const file = (array) => {
return array.reduce((acc,curr) => curr.file ? acc.concat(curr) : acc,
[])
}
流程:
acc作为[](空数组)启动。 reduce docs