问题
I have a problem how to correctly filter data when it I pass one or several values from filters on the page. For example I have such object with data:
let data = [
{'name': 'test 1', 'address': 'New York', 'class_id': [1, 2, 3], 'country': 'USA', 'country_id': 20},
{'name': 'test 2', 'address': 'Lisbona', 'class_id': [2, 3], 'country': 'Portugal', 'country_id': 12},
{'name': 'test 3', 'address': 'New York', 'class_id': [2], 'country': 'USA', 'country_id': 20},
{'name': 'test 4', 'address': 'Atlanta', 'class_id': [2], 'country': 'USA', 'country_id': 20},
{'name': 'test 5', 'address': 'New York', 'class_id': [3], 'country': 'USA', 'country_id': 20},
{'name': 'test 6', 'address': 'Rio', 'class_id': [1], 'country': 'Brazil', 'country_id': 11},
]
I would like to filter specific data when I pass eg one value so that it looks in class_id eg: [3]
but I would also like to filter all objects that meet more conditions, i.e., filter out when class_id: [3] and country_id: 20
so that the result returns the following result
data = [
{'name': 'test 1', 'address': 'New York', 'class_id': [1, 2, 3], 'country': 'USA', 'country_id': 20},
{'name': 'test 5', 'address': 'New York', 'class_id': [3], 'country': 'USA', 'country_id': 20},
]
I tried solutions using _.where did not help. I also tried with _.filter and _.contains, it works only if I search after class_id but if I give the second condition in filters, which is country_id, unfortunately then nothing returns
回答1:
This question is a good exercise in composition and abstraction. Let's consider some of the functions that Underscore has to offer and how we can use them. Gradually, we'll compose these into a larger whole until we've met your goal.
_.contains takes a collection (array or object) and a value. If the collection contains the value, it returns true
, otherwise false
.
_.contains([1, 2, 3], 3) // true
_.partial takes a function and any number of arguments that you may want to "pre-fill" as arguments to that function. It then returns a new function with those arguments pre-filled. You can use _
as a placeholder for arguments that you want to leave open.
// a version of _.contains that has the second argument pre-filled
// with the value 3
var contains3 = _.partial(_.contains, _, 3);
// the next line is equivalent to calling _.contains([1, 2, 3], 3)
contains3([1, 2, 3]) // true
_.property takes a name and returns a function. That function takes an object and returns the value of the property with the name you passed.
var getName = _.property('name');
getName({'name': 'test 1'}) // 'test 1'
_.compose takes any number of functions and returns a new function. The arguments that you pass to this new function are fed to the rightmost function that you passed into _.compose
. Then, from right to left, every function is called with the result of the function to its right. Finally, the new function returns the result of the leftmost function.
// a function that takes an object, reads its class_id property,
// and checks whether that contains the value 3
var classIdContains3 = _.compose(contains3, _.property('class_id'));
classIdContains3({'class_id': [1, 2, 3]}) // true
We can turn this into a function that will return such a "property contains value checker" for any combination of property name and value.
function propertyContains(name, value) {
return _.compose(
_.partial(_.contains, _, value),
_.property(name)
);
}
var classIdContains3 = propertyContains('class_id', 3);
classIdContains3({'class_id': [1, 2, 3]}) // true
classIdContains3({'class_id': [1, 2]}) // false
var nameContainsE = propertyContains('name', 'e');
nameContainsE({'name': 'test 1'}) // true
nameContainsE({'name': 'Zorro'}) // false
_.filter takes a collection and a criterion. It returns an array with all elements of the collection that meet the criterion.
_.filter(data, classIdContains3)
//[
// {'name': 'test 1', 'address': 'New York', 'class_id': [1, 2, 3], 'country': 'USA', 'country_id': 20},
// {'name': 'test 2', 'address': 'Lisbona', 'class_id': [2, 3], 'country': 'Portugal', 'country_id': 12},
// {'name': 'test 5', 'address': 'New York', 'class_id': [3], 'country': 'USA', 'country_id': 20},
//]
Besides passing a function as the filter criterion, functions like _.filter
also allow several declarative shorthands. For checking properties against an exact value, this notation works:
_.filter(data, {'country_id': 20})
//[
// {'name': 'test 1', 'address': 'New York', 'class_id': [1, 2, 3], 'country': 'USA', 'country_id': 20},
// {'name': 'test 3', 'address': 'New York', 'class_id': [2], 'country': 'USA', 'country_id': 20},
// {'name': 'test 4', 'address': 'Atlanta', 'class_id': [2], 'country': 'USA', 'country_id': 20},
// {'name': 'test 5', 'address': 'New York', 'class_id': [3], 'country': 'USA', 'country_id': 20},
//]
To narrow down the results by multiple criteria, simply filter multiple times.
// first filter pass
var hasClassId3 = _.filter(data, classIdContains3);
// second filter pass (filtering result of the above)
_.filter(hasClassId3, {'country_id': 20})
//[
// {'name': 'test 1', 'address': 'New York', 'class_id': [1, 2, 3], 'country': 'USA', 'country_id': 20},
// {'name': 'test 5', 'address': 'New York', 'class_id': [3], 'country': 'USA', 'country_id': 20},
//]
_.chain and _.value allow for a shorter notation of the above.
_.chain(data)
.filter(classIdContains3)
.filter({'country_id': 20})
.value()
//[
// {'name': 'test 1', 'address': 'New York', 'class_id': [1, 2, 3], 'country': 'USA', 'country_id': 20},
// {'name': 'test 5', 'address': 'New York', 'class_id': [3], 'country': 'USA', 'country_id': 20},
//]
Using _.each, we can turn this into a function that will filter by arbitrary combinations of criteria.
function multiFilter(data, criteria) {
var result = _.chain(data);
if (criteria.exact) result = result.filter(criteria.exact);
_.each(criteria.contains, function(value, name) {
result = result.filter(propertyContains(name, value));
});
return result.value();
}
multiFilter(data, {
exact: {
country_id: 20,
},
contains: {
class_id: 3,
},
})
//[
// {'name': 'test 1', 'address': 'New York', 'class_id': [1, 2, 3], 'country': 'USA', 'country_id': 20},
// {'name': 'test 5', 'address': 'New York', 'class_id': [3], 'country': 'USA', 'country_id': 20},
//]
multiFilter(data, {
exact: {
country_id: 20,
address: 'New York',
},
contains: {
name: '3',
},
})
//[
// {'name': 'test 3', 'address': 'New York', 'class_id': [2], 'country': 'USA', 'country_id': 20},
//]
multiFilter(data, {
contains: {
address: 'a',
},
})
//[
// {'name': 'test 2', 'address': 'Lisbona', 'class_id': [2, 3], 'country': 'Portugal', 'country_id': 12},
// {'name': 'test 4', 'address': 'Atlanta', 'class_id': [2], 'country': 'USA', 'country_id': 20},
//]
For more Underscore, functional programming, and search, you may also refer to this answer.
来源:https://stackoverflow.com/questions/62409101/underscore-how-to-filter-all-objects-when-there-are-multiple-parameters