Redsandro
Redsandro

Reputation: 11356

How can I find if one or more array elements match in lodash find

Using lodash _.find(), we can see if there's a matching object in a collection.

var users = [
  { 'user': 'barney',  'age': 36, 'active': true,  arr: [2, 3, 6, 9] },
  { 'user': 'fred',    'age': 40, 'active': false, arr: [2, 3, 6, 9] },
  { 'user': 'pebbles', 'age': 1,  'active': true,  arr: [2, 3, 6, 9] }
];

You can provide an array, and an object matches if all values in that array are present in the collection. Notice the arr.

_.result(_.find(users, { 'age': 1, 'active': true, 'arr': [2, 9] }), 'user');
// Result: "pebbles"

How can I match objects that have one (or more) matching array items for any array in the match object? (In this case arr.) Something similar to the $in query from MongoDB.

_.result(_.find(users, { 'age': 1, 'active': true, 'arr': [2, 4] }), 'user');
// Result: undefined
// Expected: "pebbles"

Upvotes: 2

Views: 11874

Answers (2)

mu is too short
mu is too short

Reputation: 434645

From the fine manual:

If an object is provided for predicate the created _.matches style callback returns true for elements that have the properties of the given object, else false.

and _.matches does element-by-element equality tests with arrays so you can't use an object predicate for what you want to do. However, you don't have to pass an object to _.find, you can pass a function:

Iterates over elements of collection, returning the first element predicate returns truthy for. The predicate is bound to thisArg and invoked with three arguments: (value, index|key, collection).

The object form of _.find that you're using just builds a predicate function behind the curtains. You can always supply your own predicate function if you need to:

_.find(users, function(value) {
    if(value.age !== 1)
        return false;
    if(!value.active)
        return false;
    if(_.intersection(value.arr, [2, 4]).length <= 0)
        return false;
    return true;
})

Of course, if your arrays aren't really sets (i.e. there could be duplicates or order matters) then you'd have to replace with _.intersection test with something that matches your specific requirements.

Demo: http://jsfiddle.net/ambiguous/vo72Lyce/

If you want this to work for arbitrary objects then you'd write your own version of _.matches (probably by starting with the current implementation) that does what you want; then you could use your version of _.matches to build the predicate function for _.find.

Upvotes: 2

kmc059000
kmc059000

Reputation: 3067

As far as I know, you cannot do that in lodash.

What fuzzyMatcher does is create an array of functions where each function corresponds to a single property in the object you are trying to match on. Those functions will return true if that property matches the property in the user object. Then fuzzyMatcher runs each of those functions and gets the count of the functions which returned true. Then it returns true if the number of matching functions is greater than or equal the number of desired matches (1 in your case).

This seems to work pretty well:

function fuzzyMatcher(collection, matchObj, minSimilarProps) {
    var matchFns = [];

    _.forOwn(matchObj, function(value, key) {
        matchFns.push(_.matchesProperty(key, value));
    });

    return _.find(collection, function(u) {
        return _.filter(matchFns, function(fn) {
            return fn(u);
        }).length >= minSimilarProps;
    });
}

//matches 1 properties
fuzzyMatcher(users, { 'age': 1, 'active': true, 'arr': [2, 4] }, 1);

//or matches 2 properties
fuzzyMatcher(users, { 'age': 1, 'active': true, 'arr': [2, 4] }, 2);

Or in your case:

_.result(fuzzyMatcher(users, { 'age': 1, 'active': true, 'arr': [2, 4] }, 2), 'user');

Also I think you need to reword what you want to say "two or more" since once or more would match barney since active=1 in both cases.

Upvotes: 0

Related Questions