CharStar
CharStar

Reputation: 427

Javascript map over array of obj with another array to get different key value

So I am not sure why I having such a difficult time with this, but I have an array of ids that I am trying to use to map over an array of objects to find the corresponding id but return the value from a different key.

i.e.: arr=[13, 1, 16]

arrObj= [{
          id: 1,
          name: "cat"
         }, {
          id: 10,
          name: "tiger", 
         }, {
          id: 3,
          name: "dog", 
         }, {
          id: 16,
          name: "bear", 
         }, {
          id: 8,
          name: "fish", 
         }, {
          id: 13,
          name: "goat", 
         }]

and I want it to return: ["goat", "cat", "bear"]

I have a nested map function that does this but returns undefined for the objects that do not have a corresponding ID. I could filter out the undefineds from the returned array, but it seems that there is a cleaner/more efficient way to do this.

What is the cleanest way to achieve this?

Upvotes: 6

Views: 11013

Answers (5)

user2693928
user2693928

Reputation:

Try this:

var arr=[13, 1, 16];
var arrObj= [{
          id: 1,
          name: "cat"
         }, {
          id: 10,
          name: "tiger", 
         }, {
          id: 3,
          name: "dog", 
         }, {
          id: 16,
          name: "bear", 
         }, {
          id: 8,
          name: "fish", 
         }, {
          id: 13,
          name: "goat", 
         }];

var result = arr.map(id => arrObj.find(x => x.id == id)).map(x => x.name)
console.log(result);
// ["goat", "cat", "bear"]

.map() (from MDN web docs):

method creates a new array with the results of calling a provided function on every element in the calling array.

.find() (from MDN web docs):

method returns the value of the first element in the array that satisfies the provided testing function. Otherwise undefined is returned.

Upvotes: 4

Nina Scholz
Nina Scholz

Reputation: 386848

You could use Array#map and search with Array#find for the corresponding object. Then take name as return value.

var arr = [13, 1, 16],
    arrObj = [{ id: 1, name: "cat" }, { id: 10, name: "tiger" }, { id: 3, name: "dog" }, { id: 16, name: "bear" }, { id: 8, name: "fish" }, { id: 13, name: "goat" }],
    result = arr.map(id => arrObj.find(o => o.id === id).name);

console.log(result);

For a lots of data, you could take a Map and build it by mapping key value pairs and then map the result of the map.

var arr = [13, 1, 16],
    arrObj = [{ id: 1, name: "cat" }, { id: 10, name: "tiger" }, { id: 3, name: "dog" }, { id: 16, name: "bear" }, { id: 8, name: "fish" }, { id: 13, name: "goat" }],
    result = arr.map(
        Map.prototype.get,
        new Map(arrObj.map(({ id, name }) => [id, name]))
    );

console.log(result);

Upvotes: 6

Mohammed Siddeeq
Mohammed Siddeeq

Reputation: 818

The correct way is to not nest any loops:

  1. reduce your array of objects to a map of 'id-values' idValueMap
  2. map through your array of 'ids' idArr using the idValueMap to get the corresponding values for each id in constant time.

Thought Process

Understand that the biggest problem is the data itself. Don't let insufficient data types or structures force you you have a bad solution/code. Transform the data such to what you need so that you can create a proper solution.

Basically imagine if the objArr was just a Map that you could use to look up the values for an id.... then the solution is straight forward. So let's make that happen and then the rest falls into place. Data Data Data is what I always say :)

Rule of thumb NEVER run a loop inside of a loop if you can help it. FYI: filter, find, map, indexOf .... are all loops internally, do not nest them unless you absolutely must.

This solution is by far the most performant. This way you are not running O(n^2) (very BAD), you are instead running O(n) (very GOOD):

    const idArr = [ 13, 1, 16 ];
    const objArr= [
      {
        id: 1,
        name: "cat"
      }, 
      {
        id: 10,
        name: "tiger", 
      }, 
      {
        id: 3,
        name: "dog", 
      }, 
      {
        id: 16,
        name: "bear", 
      }, 
      {
        id: 8,
        name: "fish", 
      }, 
      {
        id: 13,
        name: "goat", 
      }
    ];
    
    const idValueMap = objArr.reduce((acc, { id, name }) => ({ ...acc, [id]: name }), {});
    let output = idArr.map((id) => idValueMap[id]);
    console.log(output);

Upvotes: 1

Pavlo
Pavlo

Reputation: 1197

There have been a lot of good answers already, however i feel the need to push for newer syntax as it is greater to work with in the long run.

const getNamesBasedOnIds = (arr, listOfIds) => {
   return arr.reduce(((sum, element) => 
      [...sum, ...(listOfIds.includes(element.id) ? [element.name] : [])]),
   []);
}

const animals = getNamesBasedOnIds(arrObj, [13,1,16]);

Using Array.filter, then Array.map requires you to run through the Array max twice.

With Array.reduce you can add elements to the sum if they exists in listOfIds, then after running through the arrObj once you have the result

[...sum, ...(listOfIds.includes(element.id) ? [element.name] : [])] is pretty slow, but its more for showing the use of spear operators

The above is equivalent to

sum.concat(listOfIds.includes(element.id) ? element.name : [])]

The fastest way, is to use Array.push

if(listOfIds.includes(element.id)){
   sum.push(element.name);
}
return sum;

Upvotes: 1

Muhammad Usman
Muhammad Usman

Reputation: 10148

You can first use .filter to filter and then use .map to output the desired properties

here

var arr = [13, 1, 16],
    arrObj = [{
        id: 1,
        name: "cat"
    }, {
        id: 10,
        name: "tiger",
    }, {
        id: 3,
        name: "dog",
    }, {
        id: 16,
        name: "bear",
    }, {
        id: 8,
        name: "fish",
    }, {
        id: 13,
        name: "goat",
    }];

var res = arrObj.filter(o => arr.indexOf(o.id) >= 0);

console.log(res.map(o => o['name']))

Upvotes: 0

Related Questions