简体   繁体   中英

Filter/Reject Array of strings against multiple values using underscore

I'd like to _.filter or _.reject the cities array using the filters array using underscore.

var cities = ['USA/Aberdeen', 'USA/Abilene', 'USA/Akron', 'USA/Albany', 'USA/Albuquerque', 'China/Guangzhou', 'China/Fuzhou', 'China/Beijing', 'China/Baotou', 'China/Hohhot' ... ]
var filters = ['Akron', 'Albuquerque', 'Fuzhou', 'Baotou'];

My progress so far:

var filterList;

if (reject) {
    filterList = angular.copy(cities);
    _.each(filters, (filter) => {
        filterList = _.reject(filterList, (city) => city.indexOf(filter) !== -1);
    });
} else {
    filterList = [];
    _.each(filters, (filter) => {
        filterList.push(_.filter(cities, (city) => city.indexOf(filter) !== -1));
    });
}

filterList = _.flatten(filterList);

return filterList;

I'd like to DRY this up and use a more functional approach to achieve this if possible?

A somewhat more functional version using Underscore might look like this:

const cities = ['USA/Aberdeen', 'USA/Abilene', 'USA/Akron', 'USA/Albany', 
                'USA/Albuquerque', 'China/Guangzhou', 'China/Fuzhou',
                'China/Beijing', 'China/Baotou', 'China/Hohhot']
const filters = ['Akron', 'Albuquerque', 'Fuzhou', 'Baotou'];

var inList = names => value => _.any(names, name => value.indexOf(name) > -1);

_.filter(cities, inList(filters));
//=> ["USA/Akron", "USA/Albuquerque", "China/Fuzhou", "China/Baotou"]

_.reject(cities, inList(filters));
//=> ["USA/Aberdeen", "USA/Abilene", "USA/Albany", 
//    "China/Guangzhou", "China/Beijing", "China/Hohhot"]

I'm using vanilla JavaScript here (some() and filter()) but I hope you get the idea:

const isValidCity = city => filters.some(filter => city.indexOf(filter) > -1)

const filteredCities = cities.filter(isValidCity)

Please note that this is a loop over a loop. So the time complexity is O(n * m) here.

In your example all city keys share the same pattern: country + / + city . Your filters are all an exact match to the city part of these names.

If this is a certainty in your data (which it probably isn't...) , you could reduce the number of loops your code makes by creating a Map or object that stores each city per filter entry:

  • Create an object with an entry for each city name
  • Make the key the part that you want the filter to match
  • Make the value the original name
  • Loop through the filters and return the name at each key.

This approach always requires one loop through the data and one loop through the filters. For small array sizes, you won't notice a performance difference. When one of the arrays has length 1, you'll also not notice any differences.

Again, note that this only works if there's a constant relation between your filters and cities.

 var cities = ['USA/Aberdeen', 'USA/Abilene', 'USA/Akron', 'USA/Albany', 'USA/Albuquerque', 'China/Guangzhou', 'China/Fuzhou', 'China/Beijing', 'China/Baotou', 'China/Hohhot' ] var filters = ['Akron', 'Albuquerque', 'Fuzhou', 'Baotou']; const makeMap = (arr, getKey) => arr.reduce( (map, x) => Object.assign(map, { [getKey(x)]: x }), {} ); const getProp = obj => k => obj[k]; const getKeys = (obj, keys) => keys.map(getProp(obj)); // Takes the part after the "/" const cityKey = c => c.match(/\\/(.*)/)[1]; const cityMap = makeMap(cities, cityKey); const results = getKeys(cityMap, filters); console.log(results); 

Since you seem to be using AngularJS, you could utilize the built-in filter functionality. Assuming both the cities and filters array exist on your controller and you're displaying the cities array using ng-repeat , you could have something like this on your controller:

function cityFilter(city) {
    var cityName = city.split('/')[1];
    if (reject) {
        return filters.indexOf(cityName) === -1;
    } else {
        return filters.indexOf(cityName) > -1;
    }
}

And then in your template, you'd do something like this:

<div ng-repeat="city in cities | filter : cityFilter"></div>

Of course you'd have to modify your syntax a bit depending on your code style (for example, whether you use $scope or controllerAs ).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM