Eliminar todos los elementos contenidos en otra matriz

222

Estoy buscando una forma eficiente de eliminar todos los elementos de una matriz de JavaScript si están presentes en otra matriz.

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

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

Quiero operar en myArray para dejarlo en este estado: ['a', 'd', 'e', 'f']

Con jQuery, estoy usando grep()y inArray(), que funciona bien:

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

¿Existe una manera pura de javascript para hacer esto sin bucles y empalmes?

Grifo
fuente
1
posible duplicado de Eliminar elemento específico de una matriz?
mplungjan
44
posible duplicado de JavaScript diferencia matriz
Pastillas de explosión
1
posible duplicado de ¿Puede eliminar una matriz de otra en javascript o jquery ? No puede haber prestado mucha atención a las sugerencias hechas cuando escribió la pregunta
mplungjan
Pase lo que pase, siempre implicará bucles en algún nivel.
Blue Skies
Si realmente quiere que sea "eficiente", no utilizará métodos de tipo funcional como .filter(). En su lugar, usarás forbucles. Puede evitar .splice()si no es necesario mantener el pedido original. O bien, hay formas de hacerlo .splice()más eficiente si cree que habrá muchos elementos para eliminar.
Blue Skies

Respuestas:

379

Usa el Array.filter()método:

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

Pequeña mejora, ya que el soporte del navegador Array.includes()ha aumentado:

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

Próxima adaptación usando las funciones de flecha :

myArray = myArray.filter( ( el ) => !toRemove.includes( el ) );
Sirko
fuente
23
OP: Si está utilizando Underscore.js, hay .difference()básicamente lo que hace esto.
Bill Criswell
Justo lo que estaba buscando. Gracias. @BillCriswell, revisaré el guión bajo.
Toque
1
@AlecRust Convierta todos los elementos de toRemove()mayúsculas y cambie la devolución de llamada de ela el.toUpperCase().
Sirko
55
o menos:myArray = myArray.filter( el => !toRemove.includes( el ) );
538ROMEO
1
¿No es este orden n ^ 2?
Frazer Kirkman
34

El filtermétodo debería hacer el truco:

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

Si su toRemovematriz es grande, este tipo de patrón de búsqueda puede ser ineficiente. Sería más eficiente crear un mapa para que las búsquedas sean O(1)más que 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]);
Ashwin Balamohan
fuente
24

Si está utilizando una matriz de objetos. Luego, el siguiente código debe hacer la magia, donde una propiedad de objeto será el criterio para eliminar elementos duplicados.

En el siguiente ejemplo, se han eliminado los duplicados comparando el nombre de cada elemento.

Prueba este ejemplo. 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
fuente
11

Los conjuntos ECMAScript 6 se pueden usar para calcular los diferentes elementos de dos matrices:

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

const difference = new Set([...myArray].filter((x) => !toRemove.has(x)));

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

Benny Neugebauer
fuente
8

Acabo de implementar como:

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

Usar como:

myArray.exclude(toRemove);
usuario2582833
fuente
1
No es una buena práctica extender prototypesobjetos nativos, por ejemplo Array. Eso puede tener un conflicto a largo plazo con el desarrollo futuro del lenguaje ( ver el flattencaso )
MarcoL
6

Si no puede usar cosas nuevas de ES5, filtercreo que está atrapado con dos bucles:

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);
    }
  }
}
MarcoL
fuente
filtro no es "cosas nuevas HTML5"
goofballLogic
Debería haber escrito "ES5 cosas". No estaba disponible con ES3
MarcoL
6
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) ))
mojtaba roohi
fuente
¿Le importaría agregar alguna explicación sobre una pregunta tan bien recibida, por favor?
armónica141
1
Busqué horas para la solución del problema y lo encontré, simplemente increíble. ¡Muchas gracias!
Tarvo Mäesepp
5

Ahora en sabor de una sola línea:

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

Puede que no funcione en navegadores antiguos.

Matas Vaitkevicius
fuente
3

Puedes usar _.differenceBy de 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');

Código de ejemplo aquí: CodePen

Craciun Ciprian
fuente
¿Qué pasa si el atributo está anidado dentro del objeto? Algo como en su caso {nombre: 'deepak', lugar: 'bangalore', anidado: {prueba: 1}}
Charith Jayasanka
2

¿Qué tal el más simple posible:

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)

Incitar
fuente
2
Tenga en cuenta que includesno está disponible antes de ES7.
Greg
0

La forma correcta de eliminar todos los elementos contenidos en otra matriz es hacer que la matriz de origen sea el mismo objeto eliminando solo elementos:

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

O equivalente de CoffeeScript:

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

Prueba dentro de las herramientas de desarrollo de Chrome:

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 verdadero

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

El uso del marco angular es la mejor manera de mantener el puntero al objeto de origen cuando actualiza colecciones sin una gran cantidad de observadores y recargas.

Михаил Юдин
fuente
Esta respuesta es mala ya que anula las mejores prácticas. Específicamente, nunca modifique objetos que no le pertenecen. En este caso, está modificando el objeto Array, que es un gran no-no.
Desarrollador web híbrido
0

Construyo la lógica sin usar ningún método incorporado, por favor hágame saber cualquier optimización o modificación. Probé en el editor JS que funciona bien.

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
fuente