web-dev-qa-db-fra.com

Faire une boucle dans un tableau et supprimer des éléments sans rompre la boucle

J'ai le code suivant pour la boucle et lorsque j'utilise splice() pour supprimer un élément, le message «secondes» n'est pas défini. Je pourrais vérifier si ce n'est pas défini, mais j'estime qu'il existe probablement un moyen plus élégant de le faire. Le désir est simplement de supprimer un élément et de continuer. 

for (i = 0, len = Auction.auctions.length; i < len; i++) {
    auction = Auction.auctions[i];
    Auction.auctions[i]['seconds'] --;
    if (auction.seconds < 0) { 
        Auction.auctions.splice(i, 1);
    }           
}
382
dzm

Le tableau est en cours de réindexation lorsque vous effectuez une .splice(), ce qui signifie que vous ignorez un index lorsque celui-ci est supprimé et que votre .length mis en cache est obsolète.

Pour résoudre ce problème, vous devez soit décrémenter i après une .splice(), soit simplement effectuer une itération inverse ...

var i = Auction.auctions.length
while (i--) {
    ...
    if (...) { 
        Auction.auctions.splice(i, 1);
    } 
}

Ainsi, la réindexation n'affecte pas l'élément suivant de l'itération, car elle n'affecte que les éléments du point actuel à la fin du tableau et que l'élément suivant de l'itération est inférieur au point actuel.

746
user1106925

C'est un problème assez commun. La solution consiste à effectuer une boucle en arrière:

for (var i = Auction.auctions.length - 1; i >= 0; i--) {
    Auction.auctions[i].seconds--;
    if (Auction.auctions[i].seconds < 0) { 
        Auction.auctions.splice(i, 1);
    }
}

Peu importe si vous les sortez de la fin, car les indices seront préservés à mesure que vous reculerez.

110
frattaro

Recalculez la longueur à chaque fois dans la boucle plutôt que juste au début, par exemple:

for (i = 0; i < Auction.auctions.length; i++) {
      auction = Auction.auctions[i];
      Auction.auctions[i]['seconds'] --;
      if (auction.seconds < 0) { 
          Auction.auctions.splice(i, 1);
          i--; //decrement
      }
}

De cette façon, vous ne dépasserez pas les limites.

EDIT: ajout d'un décrément dans l'instruction if.

38
Marc

Bien que votre question porte sur la suppression d'éléments de du tableau itéré sur et non pas sur la suppression d'éléments (en plus d'un autre traitement) de manière efficace, je pense que vous devriez le réexaminer si la situation est similaire.

La complexité algorithmique de cette approche est O(n^2) en tant que fonction d’épissage et la boucle for se répète sur le tableau (la fonction d’épissage décale tous les éléments du tableau dans le pire des cas). Au lieu de cela, vous pouvez simplement pousser les éléments requis dans le nouveau tableau, puis assigner ce tableau à la variable souhaitée (qui a simplement été itérée).

var newArray = [];
for (var i = 0, len = Auction.auctions.length; i < len; i++) {
    auction = Auction.auctions[i];
    auction.seconds--;
    if (!auction.seconds < 0) { 
        newArray.Push(auction);
    }
}
Auction.auctions = newArray;

Depuis ES2015, nous pouvons utiliser Array.prototype.filter pour tout adapter sur une seule ligne:

Auction.auctions = Auction.auctions.filter(auction => --auction.seconds >= 0);
23
0xc0de
Auction.auction = Auction.auctions.filter(function(el) {
  return --el["seconds"] > 0;
});
16
Aesthete

Voici un autre exemple d'utilisation correcte de l'épissure. Cet exemple est sur le point de supprimer "attribut" de "tableau".

for (var i = array.length; i--;) {
    if (array[i] === 'attribute') {
        array.splice(i, 1);
    }
}
9
daniel.szaniszlo

Une autre solution simple pour digérer une fois les éléments d'un tableau:

while(Auction.auctions.length){
    // From first to last...
    var auction = Auction.auctions.shift();
    // From last to first...
    var auction = Auction.auctions.pop();

    // Do stuff with auction
}
8
Pablo

Si vous utilisez ES6 +, pourquoi ne pas utiliser simplement la méthode Array.filter? 

Auction.auctions = Auction.auctions.filter((auction) => {
  auction['seconds'] --;
  return (auction.seconds > 0)
})  

Notez que la modification de l'élément de tableau lors de l'itération du filtre ne fonctionne que pour les objets et ne fonctionne pas pour le tableau de valeurs primitives.

4
Rubinsh

A chaque personne qui a répondu à cette question très élémentaire avec du code ayant splice () dans une boucle dont le temps d'exécution est O (n2), ou qui a voté en faveur d'une telle réponse, au cours des sept années qui suivent la publication de cette question: vous devriez avoir honte .

Voici une solution de temps linéaire simple à ce problème de temps linéaire simple.

Lorsque j'exécute cet extrait, avec n = 1 million, chaque appel à filterInPlace () prend 0,013 à 0,016 seconde. Une solution quadratique (par exemple, la réponse acceptée) prendrait un million de fois plus ou moins.

// Remove from array every item such that !condition(item).
function filterInPlace(array, condition) {
   var iOut = 0;
   for (var i = 0; i < array.length; i++)
     if (condition(array[i]))
       array[iOut++] = array[i];
   array.length = iOut;
}

// Try it out.  A quadratic solution would take a very long time.
var n = 1*1000*1000;
console.log("constructing array...");
var Auction = {auctions: []};
for (var i = 0; i < n; ++i) {
  Auction.auctions.Push({seconds:1});
  Auction.auctions.Push({seconds:2});
  Auction.auctions.Push({seconds:0});
}
console.log("array length should be "+(3*n)+": ", Auction.auctions.length)
filterInPlace(Auction.auctions, function(auction) {return --auction.seconds >= 0; })
console.log("array length should be "+(2*n)+": ", Auction.auctions.length)
filterInPlace(Auction.auctions, function(auction) {return --auction.seconds >= 0; })
console.log("array length should be "+n+": ", Auction.auctions.length)
filterInPlace(Auction.auctions, function(auction) {return --auction.seconds >= 0; })
console.log("array length should be 0: ", Auction.auctions.length)

Notez que cela modifie le tableau original en place plutôt que de créer un nouveau tableau; le faire en place de cette façon peut être avantageux, par ex. dans le cas où la matrice est le seul goulot d'étranglement en mémoire du programme; dans ce cas, vous ne voulez pas créer un autre tableau de la même taille, même temporairement.

2
Don Hatch

Il y a déjà beaucoup de réponses merveilleuses sur ce fil. Cependant, je souhaitais partager mon expérience lorsque j'ai tenté de résoudre le problème "supprimer le nième élément d'un tableau" dans le contexte ES5. 

Les tableaux JavaScript ont différentes méthodes pour ajouter/supprimer des éléments de début ou de fin. Ceux-ci sont:

arr.Push(ele) - To add element(s) at the end of the array 
arr.unshift(ele) - To add element(s) at the beginning of the array
arr.pop() - To remove last element from the array 
arr.shift() - To remove first element from the array 

Essentiellement, aucune des méthodes ci-dessus ne peut être utilisée directement pour supprimer le nième élément du tableau. 

Un fait à noter est que ceci est en contraste avec l'itérateur Java avec lequel il est possible de supprimer le nième élément d’une collection en itérant.

Cela nous laisse essentiellement avec une seule méthode de tableau Array.splice pour supprimer le nième élément (vous pouvez également faire d'autres choses avec ces méthodes, mais dans le contexte de cette question, je me concentre sur la suppression d'éléments):

Array.splice(index,1) - removes the element at the index 

Voici le code copié de la réponse originale (avec commentaires):

var arr = ["one", "two", "three", "four"];
var i = arr.length; //initialize counter to array length 

while (i--) //decrement counter else it would run into IndexOutBounds exception
{
  if (arr[i] === "four" || arr[i] === "two") {
    //splice modifies the original array
    arr.splice(i, 1); //never runs into IndexOutBounds exception 
    console.log("Element removed. arr: ");

  } else {
    console.log("Element not removed. arr: ");
  }
  console.log(arr);
}

Une autre méthode remarquable est Array.slice. Cependant, le type de retour de cette méthode est constitué des éléments supprimés. De plus, cela ne modifie pas le tableau d'origine. Extrait de code modifié comme suit:

var arr = ["one", "two", "three", "four"];
var i = arr.length; //initialize counter to array length 

while (i--) //decrement counter 
{
  if (arr[i] === "four" || arr[i] === "two") {
    console.log("Element removed. arr: ");
    console.log(arr.slice(i, i + 1));
    console.log("Original array: ");
    console.log(arr);
  }
}

Ceci dit, nous pouvons toujours utiliser Array.slice pour supprimer le nième élément de la manière suivante et, comme vous pouvez le constater, il y a beaucoup plus de code (donc inefficace). 

var arr = ["one", "two", "three", "four"];
var i = arr.length; //initialize counter to array length 

while (i--) //decrement counter 
{
  if (arr[i] === "four" || arr[i] === "two") {
    console.log("Array after removal of ith element: ");
    arr = arr.slice(0, i).concat(arr.slice(i + 1));
    console.log(arr);
  }

}

La méthode Array.slice est extrêmement importante à atteindre immuabilité en programmation fonctionnelle à la redux

1
Bhanuprakash D
for (i = 0, len = Auction.auctions.length; i < len; i++) {
    auction = Auction.auctions[i];
    Auction.auctions[i]['seconds'] --;
    if (auction.seconds < 0) {
        Auction.auctions.splice(i, 1);
        i--;
        len--;
    }
}
1
Dmitry Ragozin

Essayez de relayer un tableau dans newArray lors de la mise en boucle:

var auctions = Auction.auctions;
var auctionIndex;
var auction;
var newAuctions = [];

for (
  auctionIndex = 0; 
  auctionIndex < Auction.auctions.length;
  auctionIndex++) {

  auction = auctions[auctionIndex];

  if (auction.seconds >= 0) { 
    newAuctions.Push(
      auction);
  }    
}

Auction.auctions = newAuctions;
0
Zon

Vous pouvez simplement regarder à travers et utiliser shift()

0
user8533067