Зацикливание через массив и удаление элементов, без нарушения цикла
У меня есть следующий цикл for, и когда я использую splice()
для удаления элемента, я получаю, что "секунды" undefined. Я мог проверить, если это undefined, но я чувствую, что, возможно, более элегантный способ сделать это. Желание просто удалить элемент и продолжать движение.
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);
}
}
Ответы
Ответ 1
Массив переиндексируется, когда вы выполняете .splice()
, что означает, что вы пропустите индекс при удалении, а ваш кешированный .length
устарел.
Чтобы исправить это, вам нужно либо уменьшить i
после .splice()
, либо просто перебрать в обратном направлении...
var i = Auction.auctions.length
while (i--) {
...
if (...) {
Auction.auctions.splice(i, 1);
}
}
Таким образом, повторная индексация не влияет на следующий элемент в итерации, поскольку индексирование влияет только на элементы с текущей точки на конец массива, а следующий элемент на итерации ниже, чем текущий точка.
Ответ 2
Это довольно распространенная проблема. Решение состоит в следующем:
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);
}
}
Не имеет значения, если вы выталкиваете их из конца, потому что индексы будут сохранены по мере продвижения назад.
Ответ 3
Пересчитать длину каждый раз через цикл, а не только с самого начала, например:
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.
Ответ 4
Хотя ваш вопрос касается удаления элементов из массива, над которым выполняется итерация, а не эффективного удаления элементов (в дополнение к какой-либо другой обработке), я думаю, что следует пересмотреть это, если в подобной ситуации.
Алгоритмическая сложность этого подхода заключается в том, что 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);
Ответ 5
Auction.auctions = Auction.auctions.filter(function(el) {
return --el["seconds"] > 0;
});
Ответ 6
Вот еще один пример правильного использования сращивания. Этот пример собирается удалить атрибут из массива.
for (var i = array.length; i--;) {
if (array[i] === 'attribute') {
array.splice(i, 1);
}
}
Ответ 7
Еще одно простое решение для вычисления элементов массива один раз:
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
Если вы используете ES6 + - почему бы просто не использовать метод Array.filter?
Auction.auctions = Auction.auctions.filter((auction) => {
auction['seconds'] --;
return (auction.seconds > 0)
})
Обратите внимание, что изменение элемента массива во время итерации фильтра работает только для объектов и не будет работать для массива примитивных значений.
Ответ 9
Каждому человеку, который ответил на этот самый базовый вопрос с кодом, содержащим 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)
Ответ 10
В этой теме уже есть много замечательных ответов. Однако я хотел поделиться своим опытом, когда попытался решить "удалить 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);
}
Ответ 11
Попробуйте передать массив в 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;
Ответ 12
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--;
}
}
Ответ 13
Вы можете просто просмотреть и использовать shift()