23

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,
      });
    }
  }
2

5 Answers 5

47

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);

Sign up to request clarification or add additional context in comments.

2 Comments

Oh, thank you that works great! I have 300+ items in array, that is why I thought for loop works slow
thank you, this is exactly what I was looking for with slight modifications. I had 2 arrays, and needed to get the difference of the them in a 3rd array. I just removed the && sub part out. +1
12

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)

Comments

2

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));

1 Comment

JSON key/values are unordered, so JSON.stringify is not reliable
1

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

Comments

-1

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);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.