Видалення з масиву під час перерахування в Swift?


86

Я хочу перерахувати масив у Swift та видалити певні елементи. Мені цікаво, чи безпечно це робити, а якщо ні, як я маю цього досягти.

В даний час я буду робити це:

for (index, aString: String) in enumerate(array) {
    //Some of the strings...
    array.removeAtIndex(index)
}

Відповіді:


72

У Swift 2 це досить просто використовувати enumerateі reverse.

var a = [1,2,3,4,5,6]
for (i,num) in a.enumerate().reverse() {
    a.removeAtIndex(i)
}
print(a)

1
Працює, але фільтр - це справді шлях

13
@Mayerz False. "Я хочу перерахувати масив у Swift та видалити певні елементи." filterповертає новий масив. Ви нічого не видаляєте з масиву. Я б навіть не називав filterперелік. Завжди існує кілька способів зняти шкіру з кота.
Джонстон

6
rigth, мій поганий! Пла не шкіра будь-яких котів

56

Ви можете розглянути filterспосіб:

var theStrings = ["foo", "bar", "zxy"]

// Filter only strings that begins with "b"
theStrings = theStrings.filter { $0.hasPrefix("b") }

Параметр filterпросто - це закриття, яке приймає екземпляр типу масиву (у цьому випадку String) і повертає a Bool. Коли результат є, trueвін зберігає елемент, інакше елемент фільтрується.


16
Я б чітко зазначив, що filterмасив не оновлюється, він просто повертає новий
Антоніо

Дужки слід видалити; це остаточне закриття.
Джессі

@Antonio ти маєш рацію. Дійсно, тому я опублікував це як більш безпечне рішення. Для величезних масивів можна було б розглянути інше рішення.
Маттео Піомбо

Хм, як ви говорите, це повертає новий масив. Чи можливо тоді filterметод перетворити на mutatingодин (як я вже прочитав, mutatingключове слово дозволяє замінювати такі функції self)?
Gee.E

@ Gee.E, звичайно, ви можете додати фільтр на місці як розширення для Arrayпозначення його як mutatingі подібного до коду запитання. У будь-якому випадку вважайте, що це не завжди може бути перевагою. У будь-якому випадку кожного разу, коли ви вилучаєте об’єкт, ваш масив може бути реорганізований у пам’ять. Таким чином, може бути ефективніше розподілити новий масив, а потім зробити атомну підстановку в результаті функції фільтра. Компілятор може зробити ще більше оптимізацій, залежно від вашого коду.
Маттео Піомбо

38

У Swift 3 та 4 це буде:

З цифрами, відповідно до відповіді Джонстона:

var a = [1,2,3,4,5,6]
for (i,num) in a.enumerated().reversed() {
   a.remove(at: i)
}
print(a)

З рядками як запитання OP:

var b = ["a", "b", "c", "d", "e", "f"]

for (i,str) in b.enumerated().reversed()
{
    if str == "c"
    {
        b.remove(at: i)
    }
}
print(b)

Однак зараз у Swift 4.2 або пізнішої версії є навіть кращий, швидший спосіб, який рекомендував Apple у WWDC2018:

var c = ["a", "b", "c", "d", "e", "f"]
c.removeAll(where: {$0 == "c"})
print(c)

Цей новий спосіб має ряд переваг:

  1. Це швидше, ніж реалізації з filter.
  2. Це позбавляє від необхідності реверсивних масивів.
  3. Він видаляє елементи на місці, і, отже, оновлює вихідний масив замість виділення та повернення нового масиву.

а що, якщо предмет є об’єктом, і мені потрібно це перевірити, {$0 === Class.self}не працює
TomSawyer

14

Коли елемент із певним індексом буде видалено з масиву, позиція (та індекс) усіх наступних елементів буде змінена, оскільки вони зміщуються назад на одну позицію.

Тож найкращий спосіб - це навігація масивом у зворотному порядку - і в цьому випадку я пропоную використовувати традиційний цикл for:

for var index = array.count - 1; index >= 0; --index {
    if condition {
        array.removeAtIndex(index)
    }
}

Однак, на мою думку, найкращим підходом є використання filterметоду, як описано @perlfly у своїй відповіді.


але, на жаль, його було знято в стрімкий 3
Сергій Бражник

4

Ні, не безпечно мутувати масиви під час енумації, ваш код вийде з ладу.

Якщо ви хочете видалити лише кілька об'єктів, ви можете скористатися цією filterфункцією.


3
Це неправильно для Swift. Масиви є типами значень , тому їх "копіюють", коли вони передаються функціям, присвоюються змінним або використовуються при перерахуванні. (Swift реалізує функцію копіювання на запис для типів значень, тому фактичне копіювання зведено до мінімуму.) Спробуйте наступне, щоб перевірити: var x = [1, 2, 3, 4, 5]; друк (x); var i = 0; для v у x {if (v% 2 == 0) {x.remove (at: i)} else {i + = 1}}; print (x)
404compilernotfound

Так, ти маєш рацію за умови, що ти точно знаєш, що робиш. Можливо, я чітко не висловив свою відповідь. Я повинен був сказати, що це можливо, але це не безпечно . Це не безпечно, оскільки ви мутуєте розмір контейнера, і якщо ви помилитесь у своєму коді, ваш додаток вийде з ладу. Свіфт займається написанням безпечного коду, який несподівано не вийде з ладу під час роботи. Саме тому з допомогою функції програмування Функціональної таких як filterє більш безпечним . Ось мій німий приклад:var y = [1, 2, 3, 4, 5]; print(y); for (index, value) in y.enumerated() { y.remove(at: index) } print(y)
Starscream

Я просто хотів відрізнити, що можна змінити колекцію, що перераховується в Swift, на відміну від поведінки, що викликає винятки, коли це робиться під час ітерації через NSArray із швидким переліченням або навіть типами колекцій C #. Тут не виняток буде модифікація, а можливість неправильно керувати індексами та виходити за рамки (оскільки вони зменшили розмір). Але я безумовно погоджуюсь з вами, що, як правило, безпечніше і зрозуміліше використовувати методи функціонального програмування для маніпулювання колекціями. Особливо в Свіфті.
404compilernotfound

2

Або створіть змінний масив для зберігання елементів, які потрібно видалити, а потім після перерахування видаліть ці елементи з оригіналу. Або створіть копію масиву (незмінний), перелічіть це та видаліть об’єкти (не за індексом) з оригіналу під час перерахування.


2

Традиційний цикл for можна замінити простим циклом while, що корисно, якщо вам також потрібно виконати деякі інші операції з кожним елементом перед видаленням.

var index = array.count-1
while index >= 0 {

     let element = array[index]
     //any operations on element
     array.remove(at: index)

     index -= 1
}

1

Я рекомендую встановити для нуля елементи під час перерахування, а після завершення видалити всі порожні елементи за допомогою методу масивів filter ().


1
Це працює, лише якщо збережений тип є необов’язковим. Зауважте також, що filterметод не видаляє, він генерує новий масив.
Антоніо

Погодьтеся. Змінений порядок є кращим рішенням.
халява

0

Просто додамо, якщо у вас є кілька масивів, і кожен елемент в індексі N масиву A пов’язаний з індексом N масиву B, ви все ще можете використовувати метод, що повертає перерахований масив (як минулі відповіді). Але пам’ятайте, що при доступі до елементів інших масивів та їх видаленні немає необхідності їх змінювати.

Like so, (one can copy and paste this on Playground)

var a = ["a", "b", "c", "d"]
var b = [1, 2, 3, 4]
var c = ["!", "@", "#", "$"]

// remove c, 3, #

for (index, ch) in a.enumerated().reversed() {
    print("CH: \(ch). INDEX: \(index) | b: \(b[index]) | c: \(c[index])")
    if ch == "c" {
        a.remove(at: index)
        b.remove(at: index)
        c.remove(at: index)
    }
}

print("-----")
print(a) // ["a", "b", "d"]
print(b) // [1, 2, 4]
print(c) // ["!", "@", "$"]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.