With Underscore (or Lodash), you can write much shorter code.
// The function that will do the trick.
function subsetFilter(data, keys, predicate) {
return _.chain(data)
.map(_.partial(_.pick, _, keys))
.filter(_.partial(_.every, _, predicate))
.value();
}
// Usage.
subsetFilter(data, ['region', 'nuts'], x => x >= 25);
Let's break this into pieces.
function subsetFilter(data, keys, predicate) {
We create a function with three parameters: the data to be subsetted and filtered, the array of keys that may be variable, and the predicate
, which can be any function that returns a boolean. For example, I used x => x >= 25
above to state that the properties should be greater than or equal to 25.
_.chain(data)
We wrap the data in a special wrapper object which will allow us to chain multiple Underscore functions, each processing the result of the previous function in the chain (doc).
_.pick
This is a function that takes two arguments: an object and an array of keys. It returns a copy of the object with only the properties of which the names were listed in the array of keys (doc). For example:
_.pick({a: 1, b: 2}, ['a', 'c']) // returns {a: 1}
Next,
_.partial(_.pick, _, keys)
This transforms _.pick
into a new function, where the second argument has already been pre-filled with keys
. The _
signifies that this modified version of _.pick
is still looking for its first argument (doc). It is equivalent to the following function:
function(obj) {
return _.pick(obj, keys);
}
Completing this line,
.map(_.partial(_.pick, _, keys))
We call _.map
on the chained data
wrapper, producing a new array in which each element has been transformed through the function we created using _.partial
and _.pick
. In our example, the chain wrapper contains the following data at this point:
[
{
"region": 28,
"nuts": 33
},
{
"region": 18,
"nuts": 22
}
]
(so basically still the same data, except that the score
properties are no longer there because we didn't _.pick
them.)
_.partial(_.every, _, predicate)
Again, we use _.partial
to create a modified version of a function where the second argument has already been pre-filled. This time, the base function is _.every
, which takes an array or object and a predicate. It returns true
if the predicate returns true
for each element of the array or each property of the object, otherwise false
. The pre-filled second argument is the predicate
parameter of our subsetFilter
function. Basically we are stating the following here: give this function an object, and it will tell you whether all of its properties meet predicate
.
.filter(_.partial(_.every, _, predicate))
We _.filter
the intermediate result in our chain with the function we just created using _.partial
and _.every
. We keep only the objects in the array of which each property meets predicate
. At this point, our chain wrapper contains the following value:
[
{
"region": 28,
"nuts": 33
}
]
which is the result you want, but it is still wrapped. So as a final step, we remove the wrapper (doc):
.value();
This is the end of our expression and the result that we return from our function.