1

I have a bunch of filter criteria stored in an object. The criteria changes from time to time, so I can't have a static filter (ie: price > 5 && price < 19 && ...).

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

I then have a loop setup to filter through an array based on the criteria and return the filtered array:

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

Is there a more efficient way to setup the filter conditionals ahead of time (ie: item.price > 5 && item.price < 19 && item.age < 35) and then filter the array? As opposed to what I'm currently doing and referencing the object during each array loop - which is inefficient with all the conditionals and sub-loops.

See my jsbin - http://jsbin.com/celin/2/edit .

1
  • have you considered underscore's filters and chaining? Commented Nov 23, 2014 at 0:20

2 Answers 2

4

i would use 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;
});

return falseif it should not be included in your filtered list. inside the for-loop, the function just checks if the criteria has a min, and if if this is bigger than the same property in the array item. if so, it just returns false for this element (the same of course for the max-property).

if both fit, the function returns true, and i will be included in your filtered list!

edit: now with fixed bin

Sign up to request clarification or add additional context in comments.

5 Comments

This gets a different answer than the OP.
you did see that i changed one of the criteria's for testing? and you did see, that OP stated the the criteria object is dynamic? the question was about making the filtering more elegant, and thats excactly what i did...
Your way was definitely more elegant than mine. I'll end up going with yours and just change filter to $.grep which seems to be faster (jsperf.com/grepvsfiltervsloop).
No, I missed the change in the criteria. Sorry.
np, my comment was probably a bit harsh, sorry for that!
1

I've been working on the Ramda library, and using it to do this is fairly straightforward:

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

(also available in this JSBin.)

To do this without a library, I converted the code above to into a library-less version, and it's a bit more complicated, but still readable:

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)

In either version, the criteria is parsed once to create a set of predicate functions. Those functions are combined into a single predicate which is passed as a filter.

2 Comments

I'm somewhat hesitant of adding a 20kb library solely for the sake of using the filter function, but Ramda seems interesting, so I'll look into it. As for the filter + reduce method you proposed, it seems grep would be faster: jsperf.com/grep-vs-reduce-loop .
I definitely wouldn't recommend adding Ramda just for this one feature. That was just to demonstrate the function(ish) approach. But note that this approach was designed to separate out the processing of the criteria from the analysis of the data, which is what I thought you wanted. And your tests don't check that. (Also note that if you're using jQuery only for grep it's a lot bigger than Ramda. :-) )

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.