Remova todos os elementos contidos em outra matriz

222

Eu estou procurando uma maneira eficiente de remover todos os elementos de uma matriz javascript, se eles estiverem presentes em outra matriz.

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

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

Quero operar no myArray para deixá-lo neste estado: ['a', 'd', 'e', 'f']

Com jQuery, estou usando grep()e inArray(), que funciona bem:

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

Existe uma maneira javascript pura de fazer isso sem fazer loop e splicing?

Toque
fonte
1
possível duplicado de Remover elemento específico de uma matriz?
mplungjan
4
possível duplicata de JavaScript diferença conjunto
Pills Explosão
1
possível duplicata de Can você remover uma variedade de outro em javascript ou jquery - você não pode ter pago um monte de atenção para as sugestões feitas quando você escreveu a pergunta
mplungjan
Não importa o que aconteça, sempre envolverá um loop em algum nível.
Blue Skies
Se você realmente deseja que seja "eficiente", não usará métodos de tipo funcional como .filter(). Em vez disso, você usará forloops. Você pode evitar .splice()se o pedido original não precisar ser mantido. Ou existem maneiras de tornar .splice()mais eficiente se você acha que haverá muitos itens a serem removidos.
Blue Skies

Respostas:

379

Use o Array.filter()método:

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

Pequena melhoria, pois o suporte ao navegador Array.includes()aumentou:

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

Próxima adaptação usando as funções de seta :

myArray = myArray.filter( ( el ) => !toRemove.includes( el ) );
Sirko
fonte
23
OP: Se você estiver usando o Underscore.js, existe o .difference()que basicamente faz isso.
Bill Criswell
É mesmo o que eu procurava. Obrigado. @ BillCriswell, vou verificar o sublinhado.
Toque em
1
@AlecRust Converta todos os elementos de toRemove()para maiúsculas e altere o retorno de chamada de elpara el.toUpperCase().
Sirko
5
ou menor:myArray = myArray.filter( el => !toRemove.includes( el ) );
538ROMEO 02/09
1
essa ordem não é n ^ 2?
Frazer Kirkman
34

O filtermétodo deve fazer o truque:

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

Se sua toRemovematriz for grande, esse tipo de padrão de pesquisa pode ser ineficiente. Seria mais eficiente criar um mapa para que as pesquisas sejam O(1)mais do 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
fonte
24

Se você estiver usando uma matriz de objetos. Em seguida, o código abaixo deve fazer a mágica, onde uma propriedade de objeto será o critério para remover itens duplicados.

No exemplo abaixo, duplicatas foram removidas comparando o nome de cada item.

Tente este exemplo. 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
fonte
11

Os conjuntos ECMAScript 6 podem ser usados ​​para calcular os diferentes elementos de duas matrizes:

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
fonte
8

Acabei de implementar como:

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

Use como:

myArray.exclude(toRemove);
user2582833
fonte
1
Não é uma boa prática estender prototypesobjetos nativos, por exemplo Array. Isso pode ter um conflito de longo prazo com o desenvolvimento futuro da língua ( ver o flattencaso )
Marcol
6

Se você não pode usar coisas novas do ES5, filteracho que você está preso a dois 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);
    }
  }
}
MarcoL
fonte
filtro não é "novo material HTML5"
goofballLogic 11/11
Eu deveria ter escrito "coisas ES5". Não estava disponível no ES3
MarcoL 11/11
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
fonte
Você gostaria de adicionar alguma explicação sobre uma pergunta tão bem recebida, por favor?
harmonica141
1
Procurei horas para encontrar a solução para o problema e apenas o encontrei, simplesmente fantástico. Muito obrigado!
Tarvo Mäesepp
5

Agora com sabor de uma linha:

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

Pode não funcionar em navegadores antigos.

Matas Vaitkevicius
fonte
3

Você pode usar _.differenceBy do 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');

Exemplo de código aqui: CodePen

Craciun Ciprian
fonte
E se o atributo estiver aninhado dentro do objeto? Teste algo como no seu caso {name: "Deepak, lugar: 'Bangalore', aninhados: {teste: 1}}
Charith Jayasanka
2

Que tal o mais simples possível:

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)

Ovo em
fonte
2
Lembre-se de que includesnão está disponível antes do ES7.
Greg
0

A maneira correta de remover todos os elementos contidos em outra matriz é tornar o mesmo objeto da matriz de origem, removendo apenas os 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;
};

Ou equivalente ao CoffeeScript:

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

Teste nas ferramentas de desenvolvimento do 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 true

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

Usar a estrutura Angular é a melhor maneira de manter o ponteiro para o objeto de origem quando você atualiza coleções sem grande quantidade de observadores e recarregamentos.

Михаил Юдин
fonte
Essa resposta é ruim, pois anula as melhores práticas. Especificamente, nunca modifique objetos que você não possui. Nesse caso, você está modificando o objeto Array, que é um grande não-não.
Devo da web híbrida
0

Eu construo a lógica sem usar nenhum método interno. Informe-me sobre qualquer otimização ou modificação. Eu testei no editor JS, está funcionando bem.

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
fonte