Faire une boucle dans le tableau et supprimer des éléments, sans casser pour la boucle


462

J'ai ce qui suit pour la boucle, et lorsque j'utilise splice()pour supprimer un élément, j'obtiens alors que «secondes» n'est pas défini. Je pourrais vérifier si ce n'est pas défini, mais je pense qu'il y a probablement une façon plus élégante de le faire. Le souhait 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);
    }           
}

11
En plus d'itérer en arrière et d'ajuster la longueur, vous pouvez également simplement placer les membres de votre choix dans un nouveau tableau.
RobG

2
Pourquoi dites-vous Auction.auctions[i]['seconds']--au lieu de auction.seconds--?
Don Hatch

vous voudrez probablement examiner la fonction prédéfinie .shift ();
raku

Réponses:


856

Le tableau est réindexé lorsque vous effectuez un .splice(), ce qui signifie que vous sauterez un index lorsqu'il est supprimé et que votre cache .lengthest obsolète.

Pour y remédier, vous devez soit décrémenter iaprès a .splice(), soit simplement répéter en sens inverse ...

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

De cette façon, la réindexation n'affecte pas l'élément suivant dans l'itération, car l'indexation affecte uniquement les éléments du point actuel à la fin du tableau, et l'élément suivant de l'itération est inférieur au point actuel.


151

C'est un problème assez courant. La solution est de boucler 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 que vous les supprimiez de la fin, car les indices seront conservés lorsque vous reculerez.


48

Recalculez la longueur à chaque fois dans la boucle au lieu de simplement 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.


32

Bien que votre question concerne la suppression d'éléments du tableau en cours d'itération et non la suppression efficace d'éléments (en plus d'un autre traitement), je pense que l'on devrait le reconsidérer si dans une situation similaire.

La complexité algorithmique de cette approche se présente O(n^2)sous la forme d'une fonction d'épissage et la boucle for itère sur le tableau (la fonction d'épissage déplace tous les éléments du tableau dans le pire des cas). Au lieu de cela, vous pouvez simplement pousser les éléments requis vers le nouveau tableau, puis simplement affecter ce tableau à la variable souhaitée (qui vient d'être 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.filterpour tout ranger sur une seule ligne:

Auction.auctions = Auction.auctions.filter(auction => --auction.seconds >= 0);

22
Auction.auctions = Auction.auctions.filter(function(el) {
  return --el["seconds"] > 0;
});

10

Si vous utilisez ES6 + - pourquoi ne pas simplement utiliser 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 pendant l'itération du filtre ne fonctionne que pour les objets et ne fonctionnera pas pour le tableau de valeurs primitives.


9

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

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

8

À toute personne qui a répondu à cette question très simple avec un code ayant splice () dans une boucle, qui a exécuté le temps O (n 2 ), ou qui a voté une telle réponse, au cours des sept années écoulées depuis 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 ou plus.

// 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 d'origine en place plutôt que de créer un nouveau tableau; le faire en place comme ceci peut être avantageux, par exemple dans le cas où le tableau est le goulot d'étranglement de mémoire unique du programme; dans ce cas, vous ne voulez pas créer un autre tableau de la même taille, même temporairement.


Je n'avais jamais réalisé que vous pouviez attribuer la longueur d'un tableau!
Michael

Je ne savais pas Array.splice(i,1)créer une nouvelle instance de tableau à chaque fois. J'ai très honte.
dehart

2
@dehart Ha, bon :-) En fait, il ne crée pas à chaque fois une nouvelle instance de tableau; mais il doit bump chaque élément dont l'indice est supérieur à i vers le bas, ce qui est en moyenne n / 2 bosses.
Don Hatch

1

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

Les tableaux JavaScript ont différentes méthodes pour ajouter / supprimer des éléments du début ou de la 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 intéressant à noter est que cela contraste avec l'utilisation de l'itérateur java qui permet de supprimer le nième élément d'une collection pendant l'itération.

Cela nous laisse essentiellement avec une seule méthode de tableau Array.splicepour effectuer la suppression du nième élément (il y a d'autres choses que vous pourriez faire avec ces méthodes également, mais dans le contexte de cette question, je me concentre sur la suppression des é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 les éléments supprimés. Cela ne modifie pas non plus 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);
  }
}

Cela dit, nous pouvons toujours utiliser Array.slicepour supprimer le nième élément comme indiqué ci-dessous. Cependant c'est 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 Array.sliceméthode est extrêmement importante pour atteindre l'immuabilité dans la programmation fonctionnelle à la redux


Notez que plus de code ne doit pas être une mesure de l'efficacité d'un code.
kano

0

Essayez de relayer un tableau dans newArray lors de la 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

Deux exemples qui fonctionnent:

(Example ONE)
// Remove from Listing the Items Checked in Checkbox for Delete
let temp_products_images = store.state.c_products.products_images
if (temp_products_images != null) {
    for (var l = temp_products_images.length; l--;) {
        // 'mark' is the checkbox field
        if (temp_products_images[l].mark == true) {
            store.state.c_products.products_images.splice(l,1);         // THIS WORKS
            // this.$delete(store.state.c_products.products_images,l);  // THIS ALSO WORKS
        }
    }
}

(Example TWO)
// Remove from Listing the Items Checked in Checkbox for Delete
let temp_products_images = store.state.c_products.products_images
if (temp_products_images != null) {
    let l = temp_products_images.length
    while (l--)
    {
        // 'mark' is the checkbox field
        if (temp_products_images[l].mark == true) {
            store.state.c_products.products_images.splice(l,1);         // THIS WORKS
            // this.$delete(store.state.c_products.products_images,l);  // THIS ALSO WORKS
        }
    }
}

0

Essayez ceci

RemoveItems.forEach((i, j) => {
    OriginalItems.splice((i - j), 1);
});

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

7
Une bonne réponse aura toujours une explication de ce qui a été fait et pourquoi cela a été fait de cette manière, non seulement pour le PO mais pour les futurs visiteurs de SO.
B001

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.