Цикл массива и удаление элементов без прерывания цикла


462

У меня есть следующее для цикла, и когда я использую 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

2
Почему ты говоришь Auction.auctions[i]['seconds']--вместо auction.seconds--?
Дон Хэтч

вы, вероятно, хотите посмотреть в предопределенную функцию .shift ();
Раку

Ответы:


856

Массив переиндексируется, когда вы делаете a .splice(), что означает, что вы пропустите индекс, когда он удален, и ваш кеш .lengthустарел.

Чтобы это исправить, вам нужно либо уменьшить iпосле a .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чтобы уместить все это в одну строку:

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


10

Если вы используете ES6 + - почему бы просто не использовать метод Array.filter?

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

Обратите внимание, что изменение элемента массива во время итерации фильтра работает только для объектов и не будет работать для массива примитивных значений.


9

Еще одно простое решение для переваривания элементов массива один раз:

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

Каждому человеку, который ответил на этот самый базовый вопрос с кодом, содержащим splice () в цикле, который имеет время выполнения O (n 2 ), или который проголосовал за такой ответ, в течение семи лет с момента публикации этого вопроса: вам следует стыдиться .

Вот простое линейное временное решение этой простой линейной временной задачи.

Когда я запускаю этот фрагмент с n = 1 миллионом, каждый вызов filterInPlace () занимает от 0,013 до 0,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)

Обратите внимание, что это изменяет исходный массив на месте, а не создает новый массив; делать это на месте, как это, может быть выгодно, например, в случае, если массив является узким местом программы в памяти; в этом случае вы не хотите создавать другой массив того же размера, даже временно.


Я никогда не понимал, что вы можете назначить длину массива!
Майкл

Я не знал, Array.splice(i,1)будет ли каждый раз создавать новый экземпляр массива. Мне очень стыдно.
Дехарт

2
@dehart Ха, хорошо :-) На самом деле он не создает новый экземпляр массива каждый раз; но он должен столкнуться с каждым элементом, чей индекс больше, чем у меня вниз, что в среднем n / 2 ударов.
Дон Хэтч

1

В этой теме уже есть много замечательных ответов. Однако я хотел поделиться своим опытом, когда попытался решить «удалить n-й элемент из массива» в контексте ES5.

Массивы 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-го элемента из массива.

Стоит отметить тот факт, что это противоречит использованию java-итератора, при котором можно удалить n-й элемент для коллекции во время итерации.

Это в основном оставляет нам только один метод массива Array.spliceдля выполнения удаления n-го элемента (есть и другие вещи, которые вы могли бы сделать с этими методами, но в контексте этого вопроса я сосредоточился на удалении элементов):

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метод чрезвычайно важен для достижения неизменности в функциональном программировании по принципу избыточности


Обратите внимание, что больше кода не должно быть мерой эффективности кода.
Кано

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

Два примера, которые работают:

(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 , но для посетителей будущего так.
B001 ᛦ

-2

Вы можете просто просмотреть и использовать shift()


3
Пожалуйста, добавьте пример, используя этот метод.
Иван
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.