Reputation: 35194
var list = [
{ mode: 1, type: 'foo', blah: 1 },
{ mode: 3, type: 'foo', blah: 1 },
{ mode: 3, type: 'foo', blah: 1 },
{ mode: 1, type: 'bar', blah: 1 },
{ mode: 2, type: 'bar', blah: 0 },
{ mode: 3, type: 'bar', blah: 1 }
];
var filter = [
{ propertyName: 'mode', value: 1 },
{ propertyName: 'type', value: 'foo' },
{ propertyName: 'blah', value: 1 }
];
var i = 0;
var result1 = $.grep(list, function(x){
i++;
return x.type === 'foo' && x.mode === 1 && x.blah === 1;
});
console.log(result1, 'iterations:', i); // 6 iterations
var j = 0;
var result2 = list;
$.each(filter, function(k, filter){
result2 = $.grep(result2, function(listItem) {
j++;
return listItem[filter.propertyName] === filter.value;
});
});
console.log(result2, 'iterations:', j); // 9 iterations
I would like to optimize my filter method that gives result2
above.
As you can see in result1
, the same result can be acheived with less iterations. It might not look like much in my example, but have large lists where performance is an issue.
My question: Is there any way to optimize the filtering for result2
so that it works as the result1
filtering?
Upvotes: 4
Views: 3304
Reputation: 3243
You may combine both previous answers by @dandavis and @Bergi:
var filtered = list.filter(function(item) {
return filter.every(function(f) {
return f.value === item[f.propertyName];
});
});
Upvotes: 0
Reputation: 664375
My question: Is there any way to optimize the filtering for result2 so that it works as the result1 filtering?
Yes. grep
is building a new array for each filter, and it would be better to do that only once. With the native filter
and every
methods:
var result3 = list.filter(function(listItem) {
return filter.every(function(test) {
return listItem[test.propertyName] === test.value;
});
});
Plain loops would be faster probably. With jQuery's iteration, which doesn't have an every
equivalent, you'd use one anyway:
var result3 = $.grep(list, function(listItem) {
for (var i=0, l=filter.length; i<l; i++)
if (listItem[filter[i].propertyName] !== filter[i].value)
return false;
return true;
});
Of course that would still need to iterate over the filter
s every time, you don't really get around that (unless you compile a new Function
). But you can furhter optimize the filter
array by putting those properties that will filter out the most items in the front.
EDIT: Here's an example with a compiled function:
var result4 = list.filter(new Function("listItem",
"return "+filter.map(function(test) {
return "listItem."+test.propertyName+"==="+JSON.stringify(test.value);
// make sure that ^ these are valid ^ these are primitive
// identifiers value only
}).join(" && ")+";"
));
Yet its performance needs to be thoroughly tested, as evaling the function body can introduce a huge overhead especially in older JS engines. It might be faster for thousands of filtered rows though.
Upvotes: 1
Reputation: 16726
you can build a matching object first, and re-use it to avoid the loop-de-loop:
var ob={};
filter.map(function(a,b){
ob[a.propertyName]=a.value;
})
result2 = $.grep(list, function(x){
j++;
return x.type === ob.tpye && x.mode === ob.mode && x.blah === ob.blah;
});
/* which has the console showing (and yes, they are the same two objects in both results):
[Object] "iterations:" 6
[Object] "iterations:" 6 */
full: http://jsfiddle.net/SqJ2y/2/
Upvotes: 3