【问题标题】:Efficient array filtering based on dynamic criteria基于动态标准的高效数组过滤
【发布时间】:2015-01-20 22:28:20
【问题描述】:

我在一个对象中存储了一堆过滤条件。条件不时变化,所以我不能有静态过滤器(即:price > 5 && price < 19 && ...)。

var criteria = {
    price: {
        min: 5,
        max: 19
    },
    age: {
        max: 35
    }
};

然后我有一个循环设置来根据条件过滤数组并返回过滤后的数组:

var filtered = [];
var add = true;

for (var i=0; i < data.length; i++ ){
    add = true;
    var item = data[i];

    for (var main in criteria){
        for (var type in criteria[main] ){
            if ( type === 'min') {
                if ( !(item[main] > criteria[main][type]) ) {
                    add = false;
                    break;
                }
            } else if ( type === 'max') {
                if ( !(item[main] < criteria[main][type]) ) {
                    add = false;
                    break;
                }
            }
        }
    }
    if (add) {
        filtered.push(item);
    }
}

有没有更有效的方法来提前设置过滤条件(即:item.price &gt; 5 &amp;&amp; item.price &lt; 19 &amp;&amp; item.age &lt; 35)然后过滤数组?与我目前正在做的事情和在每个数组循环期间引用对象相反 - 这对于所有条件和子循环来说都是低效的。

查看我的 jsbin - http://jsbin.com/celin/2/edit

【问题讨论】:

  • 你考虑过下划线的过滤器和链接吗?

标签: javascript arrays loops filter


【解决方案1】:

我会使用Array.prototype.filter:

var filtered = data.filter(function (item) {
  var main, critObj;
  for (main in criteria) {
    critObj = criteria[main];
    if (critObj.min && critObj.min >= item[main]) {
      return false;
    }
    if (critObj.max && critObj.max <= item[main]) {
      return false;
    }
  }
  return true;
});

返回false如果它不应该包含在您的过滤列表中。在 for 循环中,该函数只检查条件是否有最小值,以及它是否大于数组项中的相同属性。如果是这样,它只会为这个元素返回 false(当然对于 max-property 也是如此)。

如果两者都合适,则函数返回 true,并且 i 将包含在您的过滤列表中!

编辑:现在使用fixed bin

【讨论】:

  • 这得到的答案与 OP 不同。
  • 您确实看到我更改了测试标准之一?您确实看到了,OP 声明标准对象是动态的?问题是关于使过滤更优雅,而这正是我所做的......
  • 你的方式绝对比我的优雅。我最终会选择你的,只需将 filter 更改为 $.grep,这似乎更快(jsperf.com/grepvsfiltervsloop)。
  • 不,我错过了标准的变化。对不起。
  • np,我的评论可能有点苛刻,抱歉!
【解决方案2】:

我一直在开发 Ramda 库,用它来做这件事相当简单:

var test = R.allPredicates(R.reduce(function(tests, key) {
    var field = criteria[key];
    if ('min' in field) {tests.push(R.pipe(R.prop(key), R.gt(R.__, field.min)));}
    if ('max' in field) {tests.push(R.pipe(R.prop(key), R.lt(R.__, field.max)));}
    return tests;
}, [], R.keys(criteria)));

console.log( 'filtered array is: ', data.filter(test) );

(也可在此 JSBin 中找到。)

为了在没有库的情况下执行此操作,我将上面的代码转换为无库版本,它有点复杂,但仍然可读:

var test = (function(criteria) {
    var tests = Object.keys(criteria).reduce(function(tests, key) {
        var field = criteria[key];
        if ('min' in field) {tests.push(function(item) {
            return item[key] > field.min;
        });}
        if ('max' in field) {tests.push(function(item) {
            return item[key] < field.max;
        });}
        return tests;
    }, []);
    return function(item) {
        return tests.every(function(test) {return test(item);});
    };
}(criteria));

console.log( 'filtered array is: ', data.filter(test) );

(JSBin)

在任一版本中,都会对条件进行一次解析以创建一组谓词函数。这些函数组合成一个谓词,作为过滤器传递。

【讨论】:

  • 我有点犹豫是否仅仅为了使用过滤器功能而添加一个 20kb 的库,但 Ramda 似乎很有趣,所以我会研究一下。至于您提出的 filter + reduce 方法,似乎 grep 会更快:jsperf.com/grep-vs-reduce-loop
  • 我绝对不建议只为这一功能添加 Ramda。那只是为了演示功能(ish)方法。但请注意,这种方法旨在将标准处理与数据分析分开,这正是我认为您想要的。而且您的测试不会检查这一点。 (另请注意,如果您仅将 jQuery 用于grep,它比 Ramda 大得多。:-))
猜你喜欢
  • 1970-01-01
  • 2017-05-04
  • 2023-03-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多