Janne
Janne

Reputation: 66

Filtering an array with multiple filter arrays

I'm trying to filter an array of items based on multiple filter arrays. Each of the items has a selection of filters, and I need to show only the ones that match all of the selected filters.

const selectedFilters = {
    color: ["Red", "Blue"],
    type: ["Shirt"],
    size: ["M"]
};

const items = [
    {
        name: "Item 1",
        filters: {
            color: ["Red", "Blue", "Black"],
            type: ["Shirt"],
            size: ["M"]
        }
    },
    {
        name: "Item 2",
        filters: {
            color: ["Red"],
            type: ["Pants"],
            size: ["M"]
        }
    }
];

This is how I've been trying to solve it. Filter through all of the items - for each filter category that is not empty, go through all the filter words and check that the item matches all of them.

const filterItems = (items, selectedFilters) => {
    const filterKeys = Object.keys(selectedFilters);

    return items.filter(item => {
        return filterKeys.every(key => {

            // Ignore empty filters
            if (!selectedFilters[key].length) {
                return true;
            }

            return selectedFilters[key].every(filterWord => {
                item.filters[key].includes(filterWord);
            });
        });
    });
};

filterItems(items, selectedFilters);

Returns an empty array, should return an array with the "Item 1" object.

Upvotes: 0

Views: 2690

Answers (5)

deepak_pal
deepak_pal

Reputation: 159

getFilteredResult = (data)=>{
    let res = true;
    for(d in data) {
            if(d == 'filters') {
                let obj = data[d];  
                for(key in obj) {
                    for(filterkey in selectedFilters) {         
                        if(filterkey == key) {
                            selectedFilters[filterkey].forEach(data=>{
                                 if(!obj[key].includes(data)){
                                    res=false;
                                }
                            })
                        }
                    }
                }
            }
        }
    return res;
}
items.filter((data,index,arr)=> getFilteredResult(data))

Upvotes: 0

Teocci
Teocci

Reputation: 8885

There are many ways to solve this. For example, your solution is right but we need remove the curly brakets {} inside the every callback like this:

filterWord => item.filters[key].includes(filterWord)

Another option could be converting the selectedFilters as an filterValues array, using the flat() method. This method creates a new array with all sub-array elements concatenated into it recursively. Now, we extract the filters of each items and convert it as itemFilters array using the flat() method again. Then we try to match them with the filterValues combining every() and includes() methods. Both will test whether all elements in the array include the elements of each filter.

The third option is very similar to your solution. In this case, we extracts the selectedFilters keys into a types array. Then for every type we call the some() method combined with includes() method to test whether at least one element in the array is included in the items.filters[type].

Here you have the implementation:

const selectedFilters = {
    color: ['Red', 'Blue'],
    type: ['Shirt'],
    size: ['M']
};

const items = [
    {
        name: 'Item 1',
        filters: {
            color: ['Red', 'Blue', 'Black'],
            type: ['Shirt'],
            size: ['M']
        }
    },
    {
        name: 'Item 2',
        filters: {
            color: ['Red'],
            type: ['Pants'],
            size: ['M']
        }
    },
    {
        name: 'Item 3',
        filters: {
            color: ['Green', 'Blue', 'Red'],
            type: ['Shirt'],
            size: ['S']
        }
    },
    {
        name: 'Item 4',
        filters: {
            color: ['Red', 'Blue'],
            type: ['Shirt'],
            size: ['M']
        }
    },
];

const filterItems = (items, selectedFilters) => {
    const filterKeys = Object.keys(selectedFilters);
    
    return items.filter(item => {
        return filterKeys.every(key => {
            // Ignore empty filters
            if (!selectedFilters[key].length) {
                return true;
            }
            return selectedFilters[key].every(filterWord => item.filters[key].includes(filterWord));
        });
    });
};

const results = filterItems(items, selectedFilters);

console.log('Your option: ');
results.forEach(function (item, key) {
	console.log('Name: ', item.name);
});

const filterValues = Object.values(selectedFilters).flat();
const results2 = items.filter(({filters}) => {
    const itemFilters = Object.values(filters).flat();
    return filterValues.every(filter => itemFilters.includes(filter));
})

console.log('Second option: ');
results2.forEach(function (item, key) {
	console.log('Name: ', item.name);
});


const types = Object.keys(selectedFilters);
const results3 = items.filter(
    item => types.every(
        type => selectedFilters[type].some(
            filter => item.filters[type].includes(filter)
        )
    )
);

console.log('Third option: ');
results3.forEach(function (item, key) {
	console.log('Name: ', item.name);
});

Upvotes: 0

Vikash Pathak
Vikash Pathak

Reputation: 3562

You can use the Array.filter() function for this. Here is the very simple and effective solution fo this.

Array.prototype.diff = function(arr2) {
    var ret = [];
    for(var i in this) {   
        if(arr2.indexOf(this[i]) > -1){
            ret.push(this[i]);
        }
    }
    return ret;
};

var filteredArr = items.filter(function (el) {
  return el.filters.color.diff(selectedFilters.color).length > 0 &&
         el.filters.type.diff(selectedFilters.type).length > 0 &&
         el.filters.size.diff(selectedFilters.size).length > 0
});

Here is the working example..

https://playcode.io/325371?tabs=console&script.js&output

Note: you can change AND / OR condition as per your requirements.

Upvotes: 0

Eddie
Eddie

Reputation: 26844

You can use some to check at least one element is included in the array and the function expects a bool return value.

The some() method tests whether at least one element in the array passes the test implemented by the provided function. It returns a Boolean value.

const selectedFilters = {"color":["Red","Blue"],"type":["Shirt"],"size":["M"]}
const items = [{"name":"Item 1","filters":{"color":["Red","Blue","Black"],"type":["Shirt"],"size":["M"]}},{"name":"Item 2","filters":{"color":["Red"],"type":["Pants"],"size":["M"]}}]

const filterItems = (items, selectedFilters) => {
  const filterKeys = Object.keys(selectedFilters);
  return items.filter(item => {
    return filterKeys.every(key => {
      return selectedFilters[key].some(filterWord => {   //Use some
        return item.filters[key].includes(filterWord);   //Return the a bool
      });
    })
  });
};

var result = filterItems(items, selectedFilters);
console.log(result);

Shorter version:

const selectedFilters = {"color":["Red","Blue"],"type":["Shirt"],"size":["M"]};

const items = [{"name":"Item 1","filters":{"color":["Red","Blue","Black"],"type":["Shirt"],"size":["M"]}},{"name":"Item 2","filters":{"color":["Red"],"type":["Pants"],"size":["M"]}}];

const filterItems = (items, selectedFilters) => {
  const fkeys = Object.keys(selectedFilters);
  return items.filter(i => fkeys.every(k => selectedFilters[k].some(o => i.filters[k].includes(o))));
};

const result = filterItems(items, selectedFilters);
console.log(result);

Upvotes: 0

random
random

Reputation: 7891

You can create the array of values of selectedFilters as well array of filters property values. Then use every on selectedFilters to check if all values in it are present within the filters.

const selectedFilters = {
    color: ["Red", "Blue"],
    type: ["Shirt"],
    size: ["M"]
};

const items = [
    {
        name: "Item 1",
        filters: {
            color: ["Red", "Blue", "Black"],
            type: ["Shirt"],
            size: ["M"]
        }
    },
    {
        name: "Item 2",
        filters: {
            color: ["Red"],
            type: ["Pants"],
            size: ["M"]
        }
    }
];

const filterArr = Object.values(selectedFilters).flat();

const output = items.filter(({filters}) => {
    const objFilters = Object.values(filters).flat();
    return filterArr.every(val => objFilters.includes(val));
})
console.log(output);

Upvotes: 1

Related Questions