我有大量的 json 对象数组,需要根据多个用户选择输入进行过滤。目前我正在将过滤器函数链接在一起,但我有一种感觉,这很可能不是执行此操作的最高效的方法。
目前我正在做这个:
var filtered = data.filter(function(data) {
return Conditional1
})
.filter(function(data) {
return Conditional2
})
.filter(function(data) {
return Conditional3
}) etc...;
虽然(我认为)每次迭代的“数据”可能会更少,但我想知道是否更好的做法是这样做:
var condition1 = Conditional1
var condition2 = Conditional2
var condition3 = Conditional3
etc...
var filtered = data.filter(function(data) {
return condition1 && condition2 && condition3 && etc...
});
我研究了多个高阶函数链,特别是过滤函数 - 但我没有看到任何关于最佳实践的内容(或不好的实践,我也没有对我建议的两个进行计时和比较)。
在具有大型数据集和许多条件的用例中,这将是首选(我认为它们都相当容易阅读)?
或者也许我缺少一种更高效的方法(但仍然使用高阶函数)。
将过滤器函数存储在数组中,并让
array.reduce()
运行每个过滤器,将其应用于数据。这是以运行所有这些数据为代价的,即使没有更多数据可供过滤。
const data = [...]
const filters = [f1, f2, f3, ...]
const filteredData = filters.reduce((d, f) => d.filter(f) , data)
另一种方法是使用
array.every()
。这采用相反的方法,遍历数据并检查是否所有过滤器都适用。一旦有一项返回 array.every()
,false
就会返回 false。
const data = [...]
const filters = [f1, f2, f3, ...]
const filteredData = data.filter(v => filters.every(f => f(v)))
两者分别与您的第一个和第二个示例相似。唯一的区别是它没有对过滤器或条件进行硬编码。
有趣的问题
data = new Array(111111).fill().map((a,n) => n);
const f1 = (a) => a % 2;
const f2 = (a) => a % 5;
const f3 = (a) => a > 347;
const filters = [f1, f2, f3];
var benches = [
[ "filter().filter.. - 1", () => {
var res = data.filter(a=>a%2).filter(a=>a%5).filter(a=>a>347);
}],
[ "filter(&& &&) - 2", () => {
var res = data.filter(a=>a%2 && a%5 && a>347);
}],
[ "reduce - 3", () => {
var res = filters.reduce((d, f) => d.filter(f) , data);
}],
[ "filter(every) - 4", () => {
var res = data.filter(v => filters.every(f => f(v)))
}],
];
function bench(f) {
var t0 = performance.now();
var res = f();
return performance.now() - t0;
}
var times = benches.map( a => [a[0], bench(a[1])] )
.sort( (a,b) => a[1]-b[1] );
var max = times[times.length-1][1];
times = times.map( a => {a[2] = (a[1]/max)*100; return a; } );
var template = (title, time, n) =>
`<div>` +
`<span>${title} </span>` +
`<span style="width:${3+n/2}%"> ${Number(time.toFixed(3))}msec</span>` +
`</div>`;
var strRes = times.map( t => template(...t) ).join("\n");
var $container = document.getElementById("container");
$container.innerHTML = strRes;
body { color:#fff; background:#333; font-family:helvetica; }
body > div > div { clear:both }
body > div > div > span {
float:left;
width:43%;
margin:3px 0;
text-align:right;
}
body > div > div > span:nth-child(2) {
text-align:left;
background:darkorange;
animation:showup .37s .111s;
-webkit-animation:showup .37s .111s;
}
@keyframes showup { from { width:0; } }
@-webkit-keyframes showup { from { width:0; } }
<div id="container"> </div>
还记得在 for 循环中,例如两个循环的情况,一个 3000 一个 7 那么:3000x7 > 7x3000 在时间测量中。
这两个选项并不完全相同,尽管它们可以产生相同的结果
var filtered = data.filter(function(data) {
return Conditional1
})
.filter(function(data) {
return Conditional2
})
.filter(function(data) {
return Conditional3
}) etc...;
如果您想相互独立地检查条件,则该选项更好。如果您需要在按条件 2 过滤之前先按条件 1 过滤数据,则应该使用它。如果您想要过滤符合 3 个条件或它们的组合的项目,请使用第二个:
var condition1 = Conditional1
var condition2 = Conditional2
var condition3 = Conditional3
etc...
var filtered = data.filter(function(data) {
return condition1 && condition2 && condition3 && etc...
});
如果您将其视为“for 循环”优化问题,您可以看到原始方法会导致多次迭代列表。
第二种方法会将迭代次数减少到一次。
之后,您只需寻找快速确定项目是否通过测试的最佳方法即可。
不确定性能,但这些天我很喜欢 javascript 中的 reduce 方法。比如:
arr.reduce((itemMap, item) => {
if (item.something !== somethingElse) return itemMap;
return itemMap.push(item);
}, [])
这基本上与
.filter
类似,但你可以用它做更多的事情。就像如果您甚至想映射某些值一样,您可以通过更新 item
对象并返回该项目(如果它匹配所有条件)来实现。
虽然,不确定它的性能如何..
type PredicateFn = (input: any) => boolean
const isBig: PredicateFn = (n: number): boolean => {
return n > 100
}
const isEven: PredicateFn = (n: number): boolean => {
return n % 2 === 0
}
const isInt: PredicateFn = (n: number): boolean => {
return !n.toString().includes('.')
}
function composeFilters<T>(array: T[], predicates: PredicateFn[]): T[] {
function filter(input: T) {
return predicates.every(predicate => predicate(input))
}
return array.filter(filter)
}
const input = [1, 2.5, 101, 100.5, 110, 220.24, 333, 400, 500, 223, 111]
const result = composeFilters(input, [isBig, isInt, isEven])
console.log(result)