Home > Software engineering >  Filter through an array of objects and return the ones that meet all of the given conditions
Filter through an array of objects and return the ones that meet all of the given conditions

Time:11-02

So, I have this array of objects called "characters" (currently only 3 objects, but it's supposed to be much more) and I want to filter through this array with a function (or an array method) and for example: return every character's name, who is character.bodyType: "tall" or has character.element: "fire" AND is also character.weapon: "sword" user, based on how many parameter values I pass to the function.

I wrote a function "isThere" that takes 4 params max but I want to be able to call it with any number of values less than four and get the names based on only those parameter values. Let's say I want to display only the names of characters who are both tall and male. The result should be ['Igneus'] but instead I get [ 'Renna', 'Igneus', 'Igneus', 'Dagon' ] and I couldn't figure out the logic for it to only filter the ones that met both requirements and skipped duplicates and those that met only one of them. If I want to see the names of "sword" characters only, the result should be ["Renna" , "Igneus]... and so on and so forth.

My code is clearly unfinished and wrong, I tried several different approaches with if statements, this is just where I stopped. If it is possible to do with an array method, that's short and clean, I'll take it. I tried Array.filter() method but I just didn't know how to implement a 4 parameter function in it, or if it'spossible at all.

const characters = [
  {
    bodyType: "tall",
    element: "air",
    gender: "female",
    name: "Renna",
    weapon: "sword",
  },
  {
    bodyType: "tall",
    element: "fire",
    gender: "male",
    name: "Igneus",
    weapon: "sword",
  },
  {
    bodyType: "medium",
    element: "water",
    gender: "male",
    name: "Dagon",
    weapon: "spear",
  },
];


  function isThere(body, element, gender, weapon) {
    const arr = [];
    for (const char of characters) {
        if (char.bodyType === body) {
          arr.push(char.name)
        }
        if(char.element === element){
          arr.push(char.name)
        }
        if(char.gender === gender){
          arr.push(char.name)
        }
        if(char.weapon === weapon){
          arr.push(char.name)
        }
    }
    return arr;
  }


isThere("tall", undefined, "male", undefined)

CodePudding user response:

Currently your code acts as a giant "or" - it pushes the name if any of the conditions are satisfied. You're actually looking for an "and". In addition, if a criteria is not specified, the character should immediately pass the condition, which is why I have added (typeof ... !== "undefined" ? ... : ...).

const characters = [{
    bodyType: "tall",
    element: "air",
    gender: "female",
    name: "Renna",
    weapon: "sword",
  },
  {
    bodyType: "tall",
    element: "fire",
    gender: "male",
    name: "Igneus",
    weapon: "sword",
  },
  {
    bodyType: "medium",
    element: "water",
    gender: "male",
    name: "Dagon",
    weapon: "spear",
  },
];


function isThere(body, element, gender, weapon) {
  const arr = [];
  for (const char of characters) {
    if ((typeof body === "undefined" ? true : char.bodyType === body) &&
        (typeof element === "undefined" ? true : char.element === element) &&
        (typeof gender === "undefined" ? true : char.gender === gender) && 
        (typeof weapon === "undefined" ? true : char.weapon === weapon)) {
      arr.push(char.name)
    }
  }
  return arr;
}


console.log(isThere("tall", undefined, "male", undefined));

CodePudding user response:

New concept: currying

const characters = [
  {
    bodyType: "tall",
    element: "air",
    gender: "female",
    name: "Renna",
    weapon: "sword",
  },
  {
    bodyType: "tall",
    element: "fire",
    gender: "male",
    name: "Igneus",
    weapon: "sword",
  },
  {
    bodyType: "medium",
    element: "water",
    gender: "male",
    name: "Dagon",
    weapon: "spear",
  },
];


const partialMatch = (object) => (character) => Object.entries(object).every(([key,value]) => character[key] === value)

const myMatchingCharacters = characters.filter(partialMatch({bodyType:'tall',gender:'male'}))

console.log(myMatchingCharacters.map(({name}) => name))

CodePudding user response:

In case the OP does not want to plainly write each filter method like e.g. ...

  • (({ bodyType, element, gender, name, weapon }) =>
      (bodyType === 'tall' || element === 'fire') && weapon === 'sword'
    )
    
  • (({ bodyType, element, gender, name, weapon }) =>
      bodyType === 'tall' && gender === 'male'
    )
    

... but wants to use config like objects instead, then the OP needs to come up with helper methods like

  • every and any which each create a single filter function based on the passed config
  • and and as well as or where each creates a single filter function which does process a list of filter functions according to the helper methods logical meaning/purpose.

function every(config) {
  return (item => Object
    .entries(config)
    .every(([key, value]) =>
      item[key] === value
    )
  );
}
function any(config) {
  return (item => Object
    .entries(config)
    .some(([key, value]) =>
      item[key] === value
    )
  );
}
function and(...requirements) {
  return (item => requirements
    .every(requirement =>
      requirement(item)
    )
  );
}
function or(...requirements) {
  return (item => requirements
    .some(requirement =>
      requirement(item)
    )
  );
}

const characters = [{
  bodyType: "tall",
  element: "air",
  gender: "female",
  name: "Renna",
  weapon: "sword",
}, {
  bodyType: "tall",
  element: "fire",
  gender: "male",
  name: "Igneus",
  weapon: "sword",
}, {
  bodyType: "medium",
  element: "water",
  gender: "male",
  name: "Dagon",
  weapon: "spear",
}];

// OP ... > who is character.bodyType: "tall"
//        > or has character.element: "fire"
//        > AND is also character.weapon: "sword"
console.log(
  '({ bodyType: "tall" } OR { element: "fire" }) AND { weapon: "sword" } ... conventionally ...', 
  characters
    .filter(({ bodyType, element, gender, name, weapon }) =>
      (bodyType === 'tall' || element === 'fire') && weapon === 'sword'
    )
);
console.log(
  '({ bodyType: "tall" } OR { element: "fire" }) AND { weapon: "sword" } ... by helper functions ...',
  characters
    .filter(
      and(
        any({ bodyType: "tall", element: "fire" }),
        any({ weapon: "sword" })
      )
    )
);

// OP ... > Let's say I want to display only the names
//        > of characters who are both tall and male.

console.log(
  '{ bodyType: "tall" } AND { gender: "male" } ... conventionally ...', 
  characters
    .filter(({ bodyType, element, gender, name, weapon }) =>
      bodyType === 'tall' && gender === 'male'
    )
);

// either ...
console.log(
  '{ bodyType: "tall" } AND { gender: "male" } ... by helper functions ...',
  characters
    .filter(
      and(
        any({ bodyType: "tall" }),
        any({ gender: "male" })
      )
    )
);
// ... or.
console.log(
  '{ bodyType: "tall" } AND { gender: "male" } ... by helper functions ...',
  characters
    .filter(
      every({ bodyType: "tall", gender: "male" }),
    )
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

CodePudding user response:

Can be achieved using the spread syntax on the args and the array method function every().

const characters=[{bodyType:"tall",element:"air",gender:"female",name:"Renna",weapon:"sword"},{bodyType:"tall",element:"fire",gender:"male",name:"Igneus",weapon:"sword"},{bodyType:"medium",element:"water",gender:"male",name:"Dagon",weapon:"spear"}];

const isThere = (...args) => 
    characters.filter((char)=> 
        ['bodyType', 'element', 'gender', 'weapon'].every((key, index) =>
            args[index] === undefined ? true : args[index]  === char[key]
        )
    )
    .map(({ name })=> name);

console.log(isThere(undefined, undefined, "male", undefined));
console.log(isThere(undefined, undefined, undefined, "sword"));

  • Related