在 javascript 中链接过滤器函数的好方法

问题描述 投票:0回答:6

我有大量的 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...
});

我研究了多个高阶函数链,特别是过滤函数 - 但我没有看到任何关于最佳实践的内容(或不好的实践,我也没有对我建议的两个进行计时和比较)。

在具有大型数据集和许多条件的用例中,这将是首选(我认为它们都相当容易阅读)?

或者也许我缺少一种更高效的方法(但仍然使用高阶函数)。

javascript higher-order-functions
6个回答
40
投票

将过滤器函数存储在数组中,并让

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

两者分别与您的第一个和第二个示例相似。唯一的区别是它没有对过滤器或条件进行硬编码。


39
投票

有趣的问题

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} &nbsp;</span>` +
    `<span style="width:${3+n/2}%">&nbsp;${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 在时间测量中。


2
投票

这两个选项并不完全相同,尽管它们可以产生相同的结果

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

1
投票

如果您将其视为“for 循环”优化问题,您可以看到原始方法会导致多次迭代列表。

第二种方法会将迭代次数减少到一次。

之后,您只需寻找快速确定项目是否通过测试的最佳方法即可。


1
投票

不确定性能,但这些天我很喜欢 javascript 中的 reduce 方法。比如:

arr.reduce((itemMap, item) => {
    if (item.something !== somethingElse) return itemMap;
    return itemMap.push(item);
}, [])

这基本上与

.filter
类似,但你可以用它做更多的事情。就像如果您甚至想映射某些值一样,您可以通过更新
item
对象并返回该项目(如果它匹配所有条件)来实现。 虽然,不确定它的性能如何..


0
投票
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)

© www.soinside.com 2019 - 2024. All rights reserved.