Peter Boomsma
Peter Boomsma

Reputation: 9808

Filter arrays on multiple values

I have a Inventory class that has several methods. 2 Methods return all the lions and wolves they have. One method merges the array from lions and wolves into one array. And finally I have a method that I want to use to filter out certain objects depending on the input.

class Inventory {

    getAllLions(): ILion[] {
        const lions = [
            { id: 1, name: 'Joffrey', gender: Gender.male, vertrabrates: true, warmBlood: true, hair: 'Golden', runningSpeed: 30, makeSound() { } },
            { id: 2, name: 'Tommen', gender: Gender.male, vertrabrates: true, warmBlood: true, hair: 'Golden', runningSpeed: 30, makeSound() { } },
            { id: 3, name: 'Marcella', gender: Gender.female, vertrabrates: true, warmBlood: true, hair: 'Golden', runningSpeed: 30, makeSound() { } },
        ];
        return lions;
    }

    getAllWolves(): IWolf[] {
        const wolves: IWolf[] = [
            { id: 1, name: 'Jon', gender: Gender.male, vertrabrates: true, warmBlood: true, hair: 'Grey', runningSpeed: 30, makeSound() { } },
            { id: 2, name: 'Robb', gender: Gender.male, vertrabrates: true, warmBlood: true, hair: 'Black', runningSpeed: 30, makeSound() { } },
            { id: 3, name: 'Sansa', gender: Gender.female, vertrabrates: true, warmBlood: true, hair: 'Grey', runningSpeed: 30, makeSound() { } },
            { id: 4, name: 'Arya', gender: Gender.female, vertrabrates: true, warmBlood: true, hair: 'White', runningSpeed: 30, makeSound() { } },
        ];
        return wolves;
    }

    getAllAnimals(allLions: ILion[], allWolves: IWolf[]): IAnimal[] {
        const allAnimals = allLions.concat(allWolves);  
        return allAnimals
    };

    static getAnimalBy(name: string, gender: Gender, hair: string, runningSpeed: number, allAnimals): any[] {
        let found = false;
        let results = [];

        for (let animal of allAnimals) {
            if (name === animal.name || gender === animal.gender || hair === animal.hair || runningSpeed === animal.runningSpeed) {
                found = true;
                results.push(animal);
            }
        }

        if (found) { return results } else { alert('No results'); return [] }    
    }

}

On the getAnimalBy I have several parameters. These are the parameters the user could filter the animals by. At the moment it only works with one value though. If I do:

const filterdAnimal = Inventory.getAnimalBy(null, Gender.female, null, null, allAnimals);

I get all the results that equal the female gender. But if I do:

const filterdAnimal = Inventory.getAnimalBy(null, Gender.female, 'Golden', null, allAnimals);

It adds the objects that equal Golden to the results.

What would be a good method to only show the results that would equal the female gender and the golden hair value?

Upvotes: 1

Views: 11014

Answers (1)

TSV
TSV

Reputation: 7641

You can use JS "filter" array function with predicate:

var allAnimals: Array<Animal>;
var found = allAnimals.filter(animal => {
    return name === animal.name || gender === animal.gender || hair === animal.hair || runningSpeed === animal.runningSpeed;
});

More over, we can use template object for filtering:

function findByTemplate(allAnimals: Array<Animal>, template: any) {
    return allAnimals.filter(animal => {
        return Object.keys(template).every(propertyName => animal[propertyName] === template[propertyName]);
    });
}

Usage:

var found = findByTemplate(allAnimals, {name: "Aw", gender: "Male"});
var found = findByTemplate(allAnimals, {name: "Aw"});
var found = findByTemplate(allAnimals, {name: "Aw", gender: "Male", hair: "Red", runningSpeed: 50});

Update 1

Generalized solution:

function findByTemplate(objects: Array<any>, template: any) {
    return objects.filter(obj => {
        return Object.keys(template).every(propertyName => obj[propertyName] === template[propertyName]);
    });
}

We can find by template object in arrays of objects of any types.

Lets we passed array of some objects and the {name: "Aw", gender: "Male"} as a template object.

objects.filter iterates through the array and includes item in filter result if predicate function returns true.

Predicate function:

obj => {
    return Object.keys(template).every(propertyName => obj[propertyName] === template[propertyName]);
}

gets keys from passed template

Object.keys(template) for template '{name: "Aw", gender: "Male"}' returns array

["name", "gender"]

So we will check only by passed properties. And we will check whether our condition (another, inner predicate) is true for all items in the '["name", "gender"]' array:

propertyName => obj[propertyName] === template[propertyName]

where propertyName will take "name" and "gender" values.

then propertyName = "name" it tests whether

obj.name === template.name

after that propertyName = "gender" and it tests whether

obj.gender === template.gender

If all tests (name and gender) are passed, we will return true in first predicate and get corresponding object in filter results.

Explanation is larger then code, hope it is clear enough...

Upvotes: 7

Related Questions