arif
arif

Reputation: 471

filter array of objects by another array of objects

I want to filter array of objects by another array of objects.

I have 2 array of objects like this:

const array = [
    { id: 1, name: 'a1', sub: { id: 6, name: 'a1 sub' } },
    { id: 2, name: 'a2', sub: null },
    { id: 3, name: 'a3', sub: { id: 8, name: 'a3 sub' } },
    { id: 4, name: 'a4', sub: null },
    { id: 5, name: 'a5', sub: { id: 10, name: 'a5 sub' } },
];
const anotherArray = [
    { id: 1, name: 'a1', sub: { id: 6, name: 'a1 sub' } },
    { id: 2, name: 'a2', sub: null },
    { id: 5, name: 'a5', sub: { id: 10, name: 'a5 sub' } },
];

and I want filter array by anotherArray and return items that is not exist in anotherArray and have sub.

So my desired output is:

[ { id: 3, name: 'a3', sub: { id: 8, name: 'a3 sub' } ]

Note: I've done this with for loop but it work too slow. I want to do this with using Arrays filter method

Code I have with for loop:

for (let i = 0; i < array.length; i += 1) {
    let exist = false;
    const item = array[i];
    for (let j = 0; j < anotherArray.length; j += 1) {
      const anotherItem = anotherArray[j];
      if (item.id === anotherItem.id) {
        exist = true;
      }
    }
    if (item.sub && !exist) {
      this.newArray.push({
        text: `${item.sub.name} / ${item.name}`,
        value: item.id,
      });
    }
  }

Upvotes: 23

Views: 44104

Answers (5)

DecPK
DecPK

Reputation: 25408

OPTIMIZED VERSION

const array = [{
    id: 1,
    name: "a1",
    sub: {
      id: 6,
      name: "a1 sub"
    }
  },
  {
    id: 2,
    name: "a2",
    sub: null
  },
  {
    id: 3,
    name: "a3",
    sub: {
      id: 8,
      name: "a3 sub"
    }
  },
  {
    id: 4,
    name: "a4",
    sub: null
  },
  {
    id: 5,
    name: "a5",
    sub: {
      id: 10,
      name: "a5 sub"
    }
  },
];
const anotherArray = [{
    id: 1,
    name: "a1",
    sub: {
      id: 6,
      name: "a1 sub"
    }
  },
  {
    id: 2,
    name: "a2",
    sub: null
  },
  {
    id: 5,
    name: "a5",
    sub: {
      id: 10,
      name: "a5 sub"
    }
  },
];

const dict = anotherArray.reduce((acc, curr) => {
  const { id } = curr;
  acc[id] = curr;
  return acc;
}, {});

const result = array.filter((obj) => {
  const search = dict[obj.id];
  if (!search && obj.sub) return true;
  return false;
});

console.log(result);

Upvotes: -1

Akrion
Akrion

Reputation: 18515

You can use Array.filter and then Array.some since the later would return boolean instead of the element like Array.find would:

const a1 = [ { id: 1, name: 'a1', sub: { id: 6, name: 'a1 sub' } }, { id: 2, name: 'a2', sub: null }, { id: 3, name: 'a3', sub: { id: 8, name: 'a3 sub' } }, { id: 4, name: 'a4', sub: null }, { id: 5, name: 'a5', sub: { id: 10, name: 'a5 sub' } }, ]; 
const a2 = [ { id: 1, name: 'a1', sub: { id: 6, name: 'a1 sub' } }, { id: 2, name: 'a2', sub: null }, { id: 5, name: 'a5', sub: { id: 10, name: 'a5 sub' } }, ];

const result = a1.filter(({id, sub}) => !a2.some(x => x.id == id) && sub)

console.log(result)

Upvotes: 12

Nicholas Peretti
Nicholas Peretti

Reputation: 177

Ok, let's solve this step by step.

To simplify the process let's suppose that two elements can be considered equals if they both have the same id.

The first approach that I would use is to iterate the first array and, for each element, iterate the second one to check the conditions that you've defined above.

const A = [ /* ... */]
const B = [ /* ... */]

A.filter(el => {
  let existsInB = !!B.find(e => {
    return e.id === el.id
  }

  return existsInB && !!B.sub
})

If we are sure that the elements in A and in B are really the same when they have the same ID, we could skip all the A elements without the sub property to perform it up a little bit

A.filter(el => {
  if (!el.sub) return false

  let existsInB = !!B.find(e => {
    return e.id === el.id
  }

  return existsInB
})

Now, if our arrays are bigger than that, it means that we are wasting a lot of time looking for the element into B. Usually, in these cases, I transform the array where I look for into a map, like this

var BMap = {}
B.forEach(el => {
  BMap[el.id] = el
})

A.filter(el => {
  if (!el.sub) return false

  return !!BMap[el.id]
})

In this way you "waste" a little bit of time to create your map at the beginning, but then you can find your elements quicker.

From here there could be even more optimizations but I think this is enought for this question

Upvotes: 1

Adrian Brand
Adrian Brand

Reputation: 21638

You could use JSON.stringify to compare the two objects. It would be better to write a function that compares all properties on the objects recursively.

const array = [
    { id: 1, name: 'a1', sub: { id: 6, name: 'a1 sub' } },
    { id: 2, name: 'a2', sub: null },
    { id: 3, name: 'a3', sub: { id: 8, name: 'a3 sub' } },
    { id: 4, name: 'a4', sub: null },
    { id: 5, name: 'a5', sub: { id: 10, name: 'a5 sub' } },
];
const anotherArray = [
    { id: 1, name: 'a1', sub: { id: 6, name: 'a1 sub' } },
    { id: 2, name: 'a2', sub: null },
    { id: 5, name: 'a5', sub: { id: 10, name: 'a5 sub' } },
];

const notIn = (array1, array2) => array1.filter(item1 => {
    const item1Str = JSON.stringify(item1);
    return !array2.find(item2 => item1Str === JSON.stringify(item2))
  }
);

console.log(notIn(array, anotherArray));

Upvotes: 2

kind user
kind user

Reputation: 41893

Like Felix mentioned, Array#filter won't work faster than native for loop, however if you really want it as functional way, here's one possible solution:

const array = [
    { id: 1, name: 'a1', sub: { id: 6, name: 'a1 sub' } },
    { id: 2, name: 'a2', sub: null },
    { id: 3, name: 'a3', sub: { id: 8, name: 'a3 sub' } },
    { id: 4, name: 'a4', sub: null },
    { id: 5, name: 'a5', sub: { id: 10, name: 'a5 sub' } },
];

const anotherArray = [
    { id: 1, name: 'a1', sub: { id: 6, name: 'a1 sub' } },
    { id: 2, name: 'a2', sub: null },
    { id: 5, name: 'a5', sub: { id: 10, name: 'a5 sub' } },
];

const r = array.filter((elem) => !anotherArray.find(({ id }) => elem.id === id) && elem.sub);

console.log(r);

Upvotes: 47

Related Questions