290

I am looking for an efficient way to remove all elements from a javascript array if they are present in another array.

// If I have this array:
var myArray = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];

// and this one:
var toRemove = ['b', 'c', 'g'];

I want to operate on myArray to leave it in this state: ['a', 'd', 'e', 'f']

With jQuery, I'm using grep() and inArray(), which works well:

myArray = $.grep(myArray, function(value) {
    return $.inArray(value, toRemove) < 0;
});

Is there a pure javascript way to do this without looping and splicing?

Ivar
  • 4,655
  • 12
  • 45
  • 50
Tap
  • 4,862
  • 3
  • 19
  • 24
  • 1
    possible duplicate of [Remove specific element from an array?](http://stackoverflow.com/questions/5767325/remove-specific-element-from-an-array) – mplungjan Nov 13 '13 at 15:15
  • 4
    possible duplicate of [JavaScript array difference](http://stackoverflow.com/questions/1187518/javascript-array-difference) – Explosion Pills Nov 13 '13 at 15:16
  • 1
    possible duplicate of [Can you remove one array from another in javascript or jquery](http://stackoverflow.com/questions/2069096/can-you-remove-one-array-from-another-in-javascript-or-jquery) - you cannot have paid a lot of attention to the suggestions made when you wrote the question – mplungjan Nov 13 '13 at 15:18
  • No matter what, it'll always involve looping at some level. – Blue Skies Nov 13 '13 at 15:23
  • If you genuinely want it to be "efficient", you won't use functional type methods like `.filter()`. Instead you'll use `for` loops. You can avoid `.splice()` if the original order doesn't need to be maintained. Or there are ways to make `.splice()` more efficient if you think there will be many items to remove. – Blue Skies Nov 13 '13 at 15:33
  • Nice, your jQuery solution suited me well. Thanks. – EPurpl3 Oct 25 '19 at 09:23

14 Answers14

486

Use the Array.filter() method:

myArray = myArray.filter( function( el ) {
  return toRemove.indexOf( el ) < 0;
} );

Small improvement, as browser support for Array.includes() has increased:

myArray = myArray.filter( function( el ) {
  return !toRemove.includes( el );
} );

Next adaptation using arrow functions:

myArray = myArray.filter( ( el ) => !toRemove.includes( el ) );
Sirko
  • 65,767
  • 19
  • 135
  • 167
  • 26
    OP: If you're using [Underscore.js](http://underscorejs.org/) there's [`.difference()`](http://underscorejs.org/#difference) which basically does this. – Bill Criswell Nov 13 '13 at 15:22
  • Just what I was looking for. Thank you. @BillCriswell, I will check out underscore. – Tap Nov 13 '13 at 15:25
  • I am not sure why I am getting different results from you guys, but in my case the function in filter is actually `function(index, el)` not `function(el)`. – Floss Jan 14 '16 at 20:00
  • @Floss the first parameter to `filter()` is the element according to https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/filter – Sirko Jan 14 '16 at 20:03
  • How would you make this case insensitive? – AlecRust Jun 17 '17 at 17:23
  • 1
    @AlecRust Convert all elements of `toRemove()` to upper case and change in the callback from `el` to `el.toUpperCase()`. – Sirko Jun 17 '17 at 17:25
  • 8
    or shorter : `myArray = myArray.filter( el => !toRemove.includes( el ) );` – GLAND_PROPRE Sep 02 '17 at 17:51
  • For some reason when I call any of the three functions they never seem to end. – Looft Sep 03 '18 at 11:43
  • 2
    isn't this order n^2? – Frazer Kirkman Feb 18 '19 at 07:01
  • @FrazerKirkman As usual in not performance critical environments or only small problem instances I prefer readability of code over (micro-)optimization. But you're right, that this is suboptimal. From the top of my head I can think of at least an `O( n * log n)` algorithm for that. – Sirko Feb 21 '19 at 06:32
  • 1
    is there a way to achieve this in order of n ? – Praveen Kishor Mar 20 '20 at 08:42
  • @PraveenKishor getting it down to `O(n)` without any prior assumptions on the input data is not possible, from my current understanding. If the data is sorted previously, then this assumption, e.g., would get you to that point. – Sirko Mar 20 '20 at 09:51
  • I tried the latest solution in NodeJS and it didn't work. I had to replace `( el ) => !toRemove.includes( el )` by `( el ) => { return !toRemove.includes( el ) }` . (I added brackets and 'return' keywork) – Dony Jun 12 '20 at 21:37
  • @PraveenKishor, you could make a newArray, and an object indexing the values of everything in toRemove. Then loop over myArray; ForEach value, if it is in not in your index object, push it to newArray. – Frazer Kirkman Apr 20 '21 at 23:42
39

The filter method should do the trick:

const myArray = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
const toRemove = ['b', 'c', 'g'];

// ES5 syntax
const filteredArray = myArray.filter(function(x) { 
  return toRemove.indexOf(x) < 0;
});

If your toRemove array is large, this sort of lookup pattern can be inefficient. It would be more performant to create a map so that lookups are O(1) rather than O(n).

const toRemoveMap = toRemove.reduce(
  function(memo, item) {
    memo[item] = memo[item] || true;
    return memo;
  },
  {} // initialize an empty object
);

const filteredArray = myArray.filter(function (x) {
  return toRemoveMap[x];
});

// or, if you want to use ES6-style arrow syntax:
const toRemoveMap = toRemove.reduce((memo, item) => ({
  ...memo,
  [item]: true
}), {});

const filteredArray = myArray.filter(x => toRemoveMap[x]);
Andriy
  • 12,868
  • 3
  • 39
  • 46
Ashwin Balamohan
  • 3,126
  • 2
  • 23
  • 47
  • I really love this answer... I am just going to point in the last line that should return const filteredArray = myArray.filter(x => !toRemoveMap[x]); // instead of toRemoveMap[x] – Ruslan Gonzalez Sep 01 '20 at 21:32
27

If you are using an array of objects. Then the below code should do the magic, where an object property will be the criteria to remove duplicate items.

In the below example, duplicates have been removed comparing name of each item.

Try this example. http://jsfiddle.net/deepak7641/zLj133rh/

var myArray = [
  {name: 'deepak', place: 'bangalore'}, 
  {name: 'chirag', place: 'bangalore'}, 
  {name: 'alok', place: 'berhampur'}, 
  {name: 'chandan', place: 'mumbai'}
];
var toRemove = [
  {name: 'deepak', place: 'bangalore'},
  {name: 'alok', place: 'berhampur'}
];

for( var i=myArray.length - 1; i>=0; i--){
  for( var j=0; j<toRemove.length; j++){
      if(myArray[i] && (myArray[i].name === toRemove[j].name)){
      myArray.splice(i, 1);
     }
    }
}

alert(JSON.stringify(myArray));
Deepak Acharya
  • 429
  • 5
  • 6
27

ECMAScript 6 sets can permit faster computing of the elements of one array that aren't in the other:

const myArray = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
const toRemove = new Set(['b', 'c', 'g']);

const difference = myArray.filter( x => !toRemove.has(x) );

console.log(difference); // ["a", "d", "e", "f"]

Since the lookup complexity for the V8 engine browsers use these days is O(1), the time complexity of the whole algorithm is O(n).

JohnK
  • 5,481
  • 6
  • 42
  • 69
Benny Neugebauer
  • 40,817
  • 21
  • 196
  • 177
23
var myArray = [
  {name: 'deepak', place: 'bangalore'}, 
  {name: 'chirag', place: 'bangalore'}, 
  {name: 'alok', place: 'berhampur'}, 
  {name: 'chandan', place: 'mumbai'}
];
var toRemove = [
  {name: 'deepak', place: 'bangalore'},
  {name: 'alok', place: 'berhampur'}
];



myArray = myArray.filter(ar => !toRemove.find(rm => (rm.name === ar.name && ar.place === rm.place) ))
Sangwin Gawande
  • 6,028
  • 8
  • 40
  • 58
mojtaba roohi
  • 231
  • 2
  • 3
13

Lodash has an utility function for this as well: https://lodash.com/docs#difference

Crenshinibon
  • 157
  • 1
  • 5
12

How about the simplest possible:

var myArray = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
var toRemove = ['b', 'c', 'g'];

var myArray = myArray.filter((item) => !toRemove.includes(item));
console.log(myArray)
alamincse07
  • 11,674
  • 7
  • 29
  • 48
Eggon
  • 1,045
  • 6
  • 18
9

I just implemented as:

Array.prototype.exclude = function(list){
        return this.filter(function(el){return list.indexOf(el)<0;})
}

Use as:

myArray.exclude(toRemove);
pistou
  • 2,562
  • 3
  • 28
  • 52
user2582833
  • 139
  • 1
  • 5
  • 2
    It is not good practice to extend `prototypes` of native Objects, such `Array`. That can have a long term conflict with future development of the language ( [see the `flatten` case](https://github.com/tc39/proposal-flatMap/pull/56) ) – MarcoL Mar 13 '18 at 09:22
6

If you cannot use new ES5 stuff such filter I think you're stuck with two loops:

for( var i =myArray.length - 1; i>=0; i--){
  for( var j=0; j<toRemove.length; j++){
    if(myArray[i] === toRemove[j]){
      myArray.splice(i, 1);
    }
  }
}
goofballLogic
  • 31,055
  • 8
  • 37
  • 50
MarcoL
  • 9,309
  • 3
  • 34
  • 50
6

Now in one-liner flavor:

console.log(['a', 'b', 'c', 'd', 'e', 'f', 'g'].filter(x => !~['b', 'c', 'g'].indexOf(x)))

Might not work on old browsers.

Matas Vaitkevicius
  • 49,230
  • 25
  • 212
  • 228
4

You can use _.differenceBy from lodash

const myArray = [
  {name: 'deepak', place: 'bangalore'}, 
  {name: 'chirag', place: 'bangalore'}, 
  {name: 'alok', place: 'berhampur'}, 
  {name: 'chandan', place: 'mumbai'}
];
const toRemove = [
  {name: 'deepak', place: 'bangalore'},
  {name: 'alok', place: 'berhampur'}
];
const sorted = _.differenceBy(myArray, toRemove, 'name');

Example code here: CodePen

  • What if the attribute is nested inside the object? Something test like in your case {name: 'deepak', place: 'bangalore' , nested : { test : 1}} – Charith Jayasanka Mar 29 '20 at 07:03
0

Proper way to remove all elements contained in another array is to make source array same object by remove only elements:

Array.prototype.removeContained = function(array) {
  var i, results;
  i = this.length;
  results = [];
  while (i--) {
    if (array.indexOf(this[i]) !== -1) {
      results.push(this.splice(i, 1));
    }
  }
  return results;
};

Or CoffeeScript equivalent:

Array.prototype.removeContained = (array) ->
  i = @length
  @splice i, 1 while i-- when array.indexOf(@[i]) isnt -1

Testing inside chrome dev tools:

19:33:04.447 a=1
19:33:06.354 b=2
19:33:07.615 c=3
19:33:09.981 arr = [a,b,c]
19:33:16.460 arr1 = arr

19:33:20.317 arr1 === arr
19:33:20.331 true

19:33:43.592 arr.removeContained([a,c])
19:33:52.433 arr === arr1
19:33:52.438 true

Using Angular framework is the best way to keep pointer to source object when you update collections without large amount of watchers and reloads.

Community
  • 1
  • 1
  • This answer is bad as it voids best practices. Specifically, never modifying objects you don't own. In this case, you're modifying the Array object, which is a big no-no. – Hybrid web dev Jul 14 '19 at 09:54
0

I build the logic without using any built-in methods, please let me know any optimization or modifications. I tested in JS editor it is working fine.

var myArray = [
            {name: 'deepak', place: 'bangalore'},
            {name: 'alok', place: 'berhampur'},
            {name: 'chirag', place: 'bangalore'},
            {name: 'chandan', place: 'mumbai'},

        ];
        var toRemove = [

            {name: 'chirag', place: 'bangalore'},
            {name: 'deepak', place: 'bangalore'},
            /*{name: 'chandan', place: 'mumbai'},*/
            /*{name: 'alok', place: 'berhampur'},*/


        ];
        var tempArr = [];
        for( var i=0 ; i < myArray.length; i++){
            for( var j=0; j<toRemove.length; j++){
                var toRemoveObj = toRemove[j];
                if(myArray[i] && (myArray[i].name === toRemove[j].name)) {
                    break;
                }else if(myArray[i] && (myArray[i].name !== toRemove[j].name)){
                        var fnd = isExists(tempArr,myArray[i]);
                        if(!fnd){
                            var idx = getIdex(toRemove,myArray[i])
                            if (idx === -1){
                                tempArr.push(myArray[i]);
                            }

                        }

                    }

                }
        }
        function isExists(source,item){
            var isFound = false;
            for( var i=0 ; i < source.length; i++){
                var obj = source[i];
                if(item && obj && obj.name === item.name){
                    isFound = true;
                    break;
                }
            }
            return isFound;
        }
        function getIdex(toRemove,item){
            var idex = -1;
            for( var i=0 ; i < toRemove.length; i++){
                var rObj =toRemove[i];
                if(rObj && item && rObj.name === item.name){
                    idex=i;
                    break;
                }
            }
            return idex;
        }
Shravan R
  • 11
  • 3
0

If you're using Typescript and want to match on a single property value, this should work based on Craciun Ciprian's answer above.

You could also make this more generic by allowing non-object matching and / or multi-property value matching.

/**
 *
 * @param arr1 The initial array
 * @param arr2 The array to remove
 * @param propertyName the key of the object to match on
 */
function differenceByPropVal<T>(arr1: T[], arr2: T[], propertyName: string): T[] {
  return arr1.filter(
    (a: T): boolean =>
      !arr2.find((b: T): boolean => b[propertyName] === a[propertyName])
  );
}
onx2
  • 46
  • 5