forループを壊さずに、配列をループしてアイテムを削除する


462

次のforループがありsplice()、アイテムの削除に使用すると、「秒」が未定義であることがわかります。それが未定義かどうかを確認することはできましたが、おそらくこれを行うためのよりエレガントな方法があると思います。目的は、単にアイテムを削除して続行することです。

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
逆方向に反復して長さを調整するだけでなく、必要なメンバーを新しい配列に配置することもできます。
RobG 2012年

2
なぜあなたはAuction.auctions[i]['seconds']--代わりに言うのauction.seconds--ですか?
ドンハッチ

おそらく、事前定義された関数.shift();を調べたいと思います。

回答:


856

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

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

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

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


151

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

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

インデックスを後方に移動しても保持されるため、それらを最後からポップするかどうかは関係ありません。


48

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

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ステートメントに減分を追加しました。


32

あなたの質問は、反復されている配列から要素を削除することについてであり(他の処理に加えて)要素を効率的に削除することではありませんが、同様の状況にある場合は再考する必要があると思います。

このアプローチのアルゴリズムの複雑さは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以降はArray.prototype.filter、1行に収めることができます。

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

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

10

ES6 +を使用している場合-Array.filterメソッドを使用しないのはなぜですか?

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

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


9

配列要素を1回ダイジェストするもう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

これは、スプライスの適切な使用の別の例です。この例では、「配列」から「属性」を削除しようとしています。

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

8

実行時間はO(n持っているループ内のコード持っスプライス()、この非常に基本的な質問答えたすべての人に2を、この質問が投稿されてから7年の間に、そのような答えをupvotedしている、または):あなたがすべき恥ずかしい

この単純な線形時間問題の単純な線形時間ソリューションを次に示します。

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

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

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


配列の長さを割り当てることができるとは思いもしませんでした!
マイケル

Array.splice(i,1)毎回新しい配列インスタンスを作成することを知りませんでした。私はとても恥ずかしいです。
デハート

2
@dehart Ha、good :-)実際、毎回新しい配列インスタンスを作成するわけではありません。ただし、インデックスがiよりも大きいすべての項目を下方にバンプする必要があります。これは、平均でn / 2のバンプです。
Don Hatch、

1

このスレッドにはすでに多くのすばらしい答えがあります。ただし、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イテレータの使用とは対照的です。

これは基本的にArray.splice、n番目の要素の削除を実行するための配列メソッドを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);
}

もう1つの注目すべき方法は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方法は、関数型プログラミングで再プログラミングの不変性を実現するために非常に重要です。


より多くのコードがコードの効率の尺度であってはならないことに注意してください。
カノ

0

ループ時に配列を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

機能する2つの例:

(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

これを試してみてください

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
良い答えは常に行われていたもの、それはOPのためではなく、SOに、将来の訪問者のためだけではなく、そのようなAの方法で行われた理由の説明があります。
B001ᛦ2016

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.