Прокручування масиву та видалення елементів, не порушуючи цикл


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 ();
raku

Відповіді:


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

Таким чином ви не будете перевищувати межі.

EDIT: додано декремент у операторі 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

Для кожної людини , який відповів на це дуже просте питання з кодом , що має зрощенням () в циклі, який має під час виконання О (п 2 ), або хто upvoted такої відповіді, в протягом семи років , так як це питання було відправлено: ви повинні соромитися .

Ось просте лінійне часове рішення цієї простої задачі лінійного часу.

Коли я запускаю цей фрагмент, з 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 Ha, добре :-) Насправді він щоразу не створює новий екземпляр масиву; але він повинен збивати кожен елемент, індекс якого більший за i вниз, що становить в середньому 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Метод є надзвичайно важливим для досягнення незмінності в функціональному програмуванні ля Redux


Зауважте, що більше коду не повинно бути показником ефективності коду.
кано

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 ᛦ

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.