web-dev-qa-db-ja.com

Forループを壊さずに配列をループして項目を削除する

次のforループがあり、splice()を使って項目を削除すると、 'seconds'は未定義になります。未定義かどうかを確認できますが、もっとエレガントな方法があると思います。欲しいのは、単にアイテムを削除し続けていくことです。

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

.splice()を実行すると配列のインデックスが再作成されます。つまり、インデックスが削除されるとインデックスがスキップされ、キャッシュされた.lengthは廃止されます。

それを修正するには、.splice()の後にiをデクリメントするか、単に逆方向に繰り返す必要があります。

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

このように、インデックス付けは現在の点から配列の最後までの項目にのみ影響し、繰り返しの次の項目は現在の点より低いため、再インデックスは繰り返しの次の項目には影響しません。

763
user1106925

これはかなり一般的な問題です。解決策は逆方向にループすることです。

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

後方に移動してもインデックスは維持されるため、最後までポップしても構いません。

117
frattaro

最初だけでなくループを通るたびに長さを再計算します。

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

そうすれば、限界を超えることはありません。

編集:if文にデクリメントを追加しました。

40
Marc

あなたの質問は反復される配列から要素を削除することであり、(他の処理に加えて)要素を効率的に削除することではありませんが、似たような状況であれば再考するべきです。

このアプローチのアルゴリズム上の複雑さは、スプライス関数としてのO(n^2)とforループの両方が配列に対して反復することです(最悪の場合、スプライス関数は配列のすべての要素をシフトします)。代わりに、必要な要素を新しい配列にプッシュしてから、その配列を目的の変数に代入することができます(これは反復されたばかりです)。

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;

ES2015以降、すべてを1行に収めるために Array.prototype.filter を使用できます。

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

これがスプライスの正しい使い方の別の例です。この例は、 'array'から 'attribute'を削除しようとしています。

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

配列要素を一度ダイジェストするもう1つの簡単な解決策:

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

ES6 +を使用しているのであれば、なぜArray.filterメソッドを使用しないのでしょうか。

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

フィルタの反復中に配列要素を変更すると、オブジェクトに対してのみ機能し、プリミティブ値の配列に対しては機能しなくなります。

5
Rubinsh

この非常に基本的な質問に答えたすべての人に、実行時間O(n2)、またはこの質問が投稿されてから7年間に誰がそのような答えを支持したか: あなたは恥ずかしいはずです

これは、この単純な線形時間問題に対する単純な線形時間ソリューションです。

N = 100万でこのスニペットを実行すると、filterInPlace()の呼び出しごとに.013〜.016秒かかります。二次解決策(たとえば、受け入れられた答え)は、それの百万倍かそれくらいかかります。

// 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)

これは、新しい配列を作成するのではなく、元の配列を変更することに注意してください。このような場所でそれを行うことは、たとえば配列がプログラムの単一メモリのボトルネックである場合。その場合、一時的にであっても同じサイズの別の配列を作成する必要はありません。

2
Don Hatch

このスレッドにはすでにすばらしい答えがたくさんあります。しかし、ES5のコンテキストで「配列からn番目の要素を削除」を解決しようとしたときの経験を共有したいと思いました。

JavaScript配列には、開始または終了から要素を追加または削除するためのさまざまな方法があります。これらは:

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 

本質的に上記の方法のどれも配列からn番目の要素を削除するために直接使用することはできません。

注目に値する事実は、これが繰り返し中にコレクションのn番目の要素を削除することが可能であるJavaイテレータの使用とは対照的であるということです。

これにより、n番目の要素の削除を実行するための配列メソッドArray.spliceは1つだけになります(これらのメソッドでもできることは他にもありますが、この質問の文脈では要素の削除に焦点を当てています)。

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

これは、元の答えから(コメント付きで)コピーされたコードです。

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

別の注目すべき方法はArray.sliceです。ただし、このメソッドの戻り型は削除された要素です。また、これは元の配列を変更しません。コードスニペットを次のように修正しました。

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

そうは言っても、次のようにしてArray.sliceを使用してn番目の要素を削除することができます。ご覧のとおり、もっと多くのコードが使用されています(したがって非効率的です)。

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

}

Array.sliceメソッドは、関数型プログラミングで不変性を実現するために非常に重要です。

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

ループするときに配列をnewArrayに中継しようとします。

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

shift() を見て、使うだけです。

0
user8533067