Remova vários elementos da matriz em Javascript / jQuery

117

Eu tenho duas matrizes. O primeiro array contém alguns valores, enquanto o segundo array contém índices dos valores que devem ser removidos do primeiro array. Por exemplo:

var valuesArr = new Array("v1","v2","v3","v4","v5");   
var removeValFromIndex = new Array(0,2,4);

Quero remover os valores presentes nos índices 0,2,4de valuesArr. Achei que o splicemétodo nativo pudesse ajudar, então pensei em:

$.each(removeValFromIndex,function(index,value){
    valuesArr.splice(value,1);
});

Mas não funcionou porque depois de cada um splice, os índices dos valores em valuesArreram diferentes. Eu poderia resolver esse problema usando um array temporário e copiando todos os valores para o segundo array, mas gostaria de saber se há algum método nativo para o qual possamos passar vários índices para remover valores de um array.

Eu preferiria uma solução jQuery. (Não tenho certeza se posso usar grepaqui)

xyz
fonte

Respostas:

256

Sempre há o velho e simples forciclo:

var valuesArr = ["v1","v2","v3","v4","v5"],
    removeValFromIndex = [0,2,4];    

for (var i = removeValFromIndex.length -1; i >= 0; i--)
   valuesArr.splice(removeValFromIndex[i],1);

Percorra removeValFromIndexna ordem inversa e você pode .splice()sem bagunçar os índices dos itens que ainda serão removidos.

Observe acima que usei a sintaxe literal de matriz com colchetes para declarar as duas matrizes. Esta é a sintaxe recomendada porque o new Array()uso é potencialmente confuso, visto que responde de maneira diferente dependendo de quantos parâmetros você passa.

EDIT : Acabei de ver o seu comentário em outra resposta sobre a matriz de índices não estar necessariamente em uma ordem particular. Se for esse o caso, basta classificá-lo em ordem decrescente antes de começar:

removeValFromIndex.sort(function(a,b){ return b - a; });

E siga isso com qualquer $.each()método de loop / / etc. que desejar.

nnnnnn
fonte
1
não vai bagunçar o índice
Muhammad Umer
5
@MuhammadUmer - Não, se você fizer corretamente, que é o que minha resposta explica.
nnnnnn
5
Obrigado pela consciência de ordem inversa.
Daniel Nalbach
2
+1, eu não percebi que tinha que fazer a emenda na ordem inversa, embora em vez de usar forEach, minha abordagem está usando$.each(rvm.reverse(), function(e, i ) {})
Luis Stanley Jovel
1
isso só funcionará se removeValFromIndex estiver classificado em ordem crescente
Kunal Burangi
24

Aqui está um que eu uso quando não vou com lodash / sublinhado:

while(IndexesToBeRemoved.length) {
    elements.splice(IndexesToBeRemoved.pop(), 1);
}
Dan Ochiana
fonte
Solução elegante! A princípio pensei que não funcionaria porque pensei que toda vez que você ligasse sliceteria que recalcular os índices a serem removidos (-1 em IndexestoBeRemoved), mas realmente funciona!
Renato Gama
2
Muito inteligente
Farzad YZ
11
Esta solução funciona se apenas o IndexesToBeRemovedarray for classificado em ordem crescente.
xfg
Esses índices serão invalidados após a primeira emenda.
shinzou
@shinzou - Não se IndexesToBeRemovedfor classificado (crescente).
nnnnnn
18

Não in-place, mas pode ser feito usando grepe inArrayfunções de jQuery.

var arr = $.grep(valuesArr, function(n, i) {
    return $.inArray(i, removeValFromIndex) ==-1;
});

alert(arr);//arr contains V2, V4

verifique este violino.

TheVillageIdiot
fonte
Estaria (próximo o suficiente) no lugar se você dissessevaluesArr = $.grep(...);
nnnnnn
1
@nnnnnn ha ha ha Eu estava saindo para uma reunião (você sabe que eles o tornam tão produtivo), então não experimentei muito.
TheVillageIdiot
1
@ cept0 Por que o voto negativo? OP pediu uma solução jQuery.
Ste77
Muito obrigado! @TheVillageIdiot
ecorvo
jsfiddler - Erro 404. Lamentamos muito, mas essa página não existe.
Ash
17

Eu sugiro que você use Array.prototype.filter

var valuesArr = ["v1","v2","v3","v4","v5"];
var removeValFrom = [0, 2, 4];
valuesArr = valuesArr.filter(function(value, index) {
     return removeValFrom.indexOf(index) == -1;
})
Саша Давиденко
fonte
indexOf dentro do filtro ... não ótimo
Alvaro João
1
voto positivo. mais rápido do que o método de emenda de acordo com jsperf.com/remove-multiple/1
lionbigcat
Ótimo! Agora posso dormir :)
Firmansyah
7
function filtermethod(element, index, array) {  
    return removeValFromIndex.find(index)
}  
var result = valuesArr.filter(filtermethod);

A referência MDN está aqui

Riship89
fonte
@ riship89 O violino caiu
mate64
@Karna: o violino caiu
riship89
1
Por favor, note que no momento da escrita (junho de 2014), Array.prototype.find é parte do rascunho ES6 atual e apenas implementado no firefox
Olli K
6

Em JS puro, você pode percorrer a matriz de trás para frente, portanto splice(), não bagunçará os índices dos elementos a seguir no loop:

for (var i = arr.length - 1; i >= 0; i--) {
    if ( yuck(arr[i]) ) {
        arr.splice(i, 1);
    }
}
Watchduck
fonte
Não funciona, eca não é uma função, então presume-se que seja o índice de índices de elementos indesejados, e eca usada [arr [i]]
DavChana
5

Parece necessário postar uma resposta com o O(n)tempo :). O problema com a solução de emenda é que, devido à implementação subjacente do array ser literalmente um array , cada splicechamada levará O(n)algum tempo. Isso é mais evidente quando configuramos um exemplo para explorar este comportamento:

var n = 100
var xs = []
for(var i=0; i<n;i++)
  xs.push(i)
var is = []
for(var i=n/2-1; i>=0;i--)
  is.push(i)

Isso remove elementos do meio para o início, portanto, cada remoção força o mecanismo js a copiar os n/2elementos, temos (n/2)^2operações de cópia no total, que são quadráticas.

A solução de emenda (assumindo que isjá está classificada em ordem decrescente para se livrar dos overheads) é assim:

for(var i=0; i<is.length; i++)
  xs.splice(is[i], 1)

No entanto, não é difícil implementar uma solução de tempo linear, reconstruindo o array do zero, usando uma máscara para ver se copiamos os elementos ou não (sort irá empurrar para O(n)log(n)). O seguinte é tal implementação (não que maskseja booleano invertido para velocidade):

var mask = new Array(xs.length)
for(var i=is.length - 1; i>=0; i--)
  mask[is[i]] = true
var offset = 0
for(var i=0; i<xs.length; i++){
  if(mask[i] === undefined){
    xs[offset] = xs[i]
    offset++
  }
}
xs.length = offset

Eu executei isso em jsperf.com e até mesmo n=100o método de emenda é 90% mais lento. Para maiores nessa diferença será muito maior.

simonzack
fonte
5

Quick ES6 one liner:

const valuesArr = new Array("v1","v2","v3","v4","v5");   
const removeValFromIndex = new Array(0,2,4);

const arrayWithValuesRemoved = valuesArr.filter((value, i) => removeValFromIndex.includes(i))
nevace
fonte
Seu código deve ser executado mais rápido se você fizer removeValFromIndexum Set()e usar em removeValFromIndex.hasvez de includes.
Boris
5

Uma solução simples e eficiente (complexidade linear) usando filtro e Conjunto :

const valuesArr = ['v1', 'v2', 'v3', 'v4', 'v5'];   
const removeValFromIndex = [0, 2, 4];

const indexSet = new Set(removeValFromIndex);

const arrayWithValuesRemoved = valuesArr.filter((value, i) => !indexSet.has(i));

console.log(arrayWithValuesRemoved);

A grande vantagem dessa implementação é que a operação Set lookup ( hasfunção) leva um tempo constante, sendo mais rápida do que a resposta de Nevace, por exemplo.

Alberto Trindade Tavares
fonte
@MichaelPaccione Fico feliz em ajudar :)
Alberto Trindade Tavares
3

Isso funciona bem para mim e também ao excluir de uma série de objetos:

var array = [ 
    { id: 1, name: 'bob', faveColor: 'blue' }, 
    { id: 2, name: 'jane', faveColor: 'red' }, 
    { id: 3, name: 'sam', faveColor: 'blue' }
];

// remove people that like blue

array.filter(x => x.faveColor === 'blue').forEach(x => array.splice(array.indexOf(x), 1));

Pode haver uma maneira mais curta e mais eficiente de escrever isso, mas funciona.

StuartMc
fonte
2

Uma solução simples usando ES5. Isso parece mais apropriado para a maioria dos aplicativos hoje em dia, já que muitos não querem mais depender do jQuery etc.

Quando os índices a serem removidos são classificados em ordem crescente:

var valuesArr = ["v1", "v2", "v3", "v4", "v5"];   
var removeValFromIndex = [0, 2, 4]; // ascending

removeValFromIndex.reverse().forEach(function(index) {
  valuesArr.splice(index, 1);
});

Quando os índices a serem removidos não são classificados:

var valuesArr = ["v1", "v2", "v3", "v4", "v5"];   
var removeValFromIndex = [2, 4, 0];  // unsorted

removeValFromIndex.sort(function(a, b) { return b - a; }).forEach(function(index) {
  valuesArr.splice(index, 1);
});
Kaspar Fenner
fonte
1

Você pode corrigir seu código substituindo removeValFromIndexpor removeValFromIndex.reverse(). Se não for garantido que essa matriz use a ordem crescente, você pode usar removeValFromIndex.sort(function(a, b) { return b - a }).

minopret
fonte
Parece bom para mim - jsfiddle.net/mrtsherman/gDcFu/2 . Embora isso faça a suposição de que a lista de remoção está em ordem.
mrtsherman
@minopret: Obrigado, mas só funcionará se os índices removeValFromIndexestiverem em ordem crescente.
xyz
1

Se você estiver usando o underscore.js , você pode usar _.filter()para resolver o seu problema.

var valuesArr = new Array("v1","v2","v3","v4","v5");
var removeValFromIndex = new Array(0,2,4);
var filteredArr = _.filter(valuesArr, function(item, index){
                  return !_.contains(removeValFromIndex, index);
                });

Além disso, se você estiver tentando remover itens usando uma lista de itens em vez de índices, você pode simplesmente usar _.without(), assim:

var valuesArr = new Array("v1","v2","v3","v4","v5");
var filteredArr = _.without(valuesArr, "V1", "V3");

Agora filteredArrdeveria ser["V2", "V4", "V5"]

Johnny Zhao
fonte
Como o contains é implementado em sublinhado ... tome cuidado se é equivalente a indexOf dentro do filtro ... não é o ideal de forma alguma ...
Alvaro Joao
1

filtro + indexOf (IE9 +):

function removeMany(array, indexes) {
  return array.filter(function(_, idx) {
    return indexes.indexOf(idx) === -1;
  });
}); 

Ou com filtro ES6 + encontrar (Edge +):

function removeMany(array, indexes = []) {
  return array.filter((_, idx) => indexes.indexOf(idx) === -1)
}
Daviestar
fonte
indexOf dentro do filtro ... não ótimo
Alvaro João
1

Aqui está uma rapidinha.

function removeFromArray(arr, toRemove){
    return arr.filter(item => toRemove.indexOf(item) === -1)
}

const arr1 = [1, 2, 3, 4, 5, 6, 7]
const arr2 = removeFromArray(arr1, [2, 4, 6]) // [1,3,5,7]
Merrick Kavolsky
fonte
indexOf dentro do filtro ... não ótimo
Alvaro João
0

Parece que o Apply pode ser o que você está procurando.
talvez algo assim funcionasse?

Array.prototype.splice.apply(valuesArray, removeValFromIndexes );
Roubar
fonte
Mas o .splice()método não espera uma lista de elementos para remover, ele espera um único índice do elemento no qual iniciar a remoção, seguido pelo número de elementos a serem removidos ...
nnnnnn
0

Para vários itens ou item único:

Eu sugiro que você use Array.prototype.filter

Nunca use indexOf se você já conhece o índice !:

var valuesArr = ["v1","v2","v3","v4","v5"];
var removeValFrom = [0, 2, 4];

valuesArr = valuesArr.filter(function(value, index) {
     return removeValFrom.indexOf(index) == -1;
}); // BIG O(N*m) where N is length of valuesArr and m is length removeValFrom

Faz:

com Hashes ... usando Array.prototype.map

  var valuesArr = ["v1","v2","v3","v4","v5"];
  var removeValFrom = {};
  ([0, 2, 4]).map(x=>removeValFrom[x]=1); //bild the hash.
  valuesArr = valuesArr.filter(function(value, index) {
      return removeValFrom[index] == 1;
  }); // BIG O(N) where N is valuesArr;
Alvaro joao
fonte
0
var valuesArr = new Array("v1","v2","v3","v4","v5");   
var removeValFromIndex = new Array(0,2,4);

console.log(valuesArr)
let arr2 = [];

for (let i = 0; i < valuesArr.length; i++){
  if (    //could also just imput this below instead of index value
    valuesArr[i] !== valuesArr[0] && // "v1" <--
    valuesArr[i] !== valuesArr[2] && // "v3" <--
    valuesArr[i] !== valuesArr[4]    // "v5" <--
  ){
    arr2.push(valuesArr[i]);
  }
}

console.log(arr2);

Isso funciona. No entanto, você faria uma nova matriz no processo. Não tenho certeza se isso você deseja ou não, mas tecnicamente seria uma matriz contendo apenas os valores que você deseja.


fonte
-1

Você pode tentar e usar delete array[index]Isso não removerá completamente o elemento, mas definirá o valor como undefined.

Henesnarfel
fonte
Obrigado, mas quero remover os elementos.
xyz
-1

Você pode construir um a Setpartir do array e então criar um array a partir do conjunto.

const array = [1, 1, 2, 3, 5, 5, 1];
const uniqueArray = [...new Set(array)];
console.log(uniqueArray); // Result: [1, 2, 3, 5]
Mohib
fonte