Удаление из массива при перечислении в 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 Ложь. «Я хочу выполнить перечисление через массив в Swift и удалить определенные элементы». filterвозвращает новый массив. Вы ничего не удаляете из массива. Я бы даже не стал называть filterперечисление. Всегда есть несколько способов снять шкуру с кошки.
Johnston
6
Право, плохо мое! Pla dont skin any cats
56

Вы можете рассмотреть filterспособ:

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

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

Параметр filter- это просто замыкание, которое принимает экземпляр типа массива (в данном случае String) и возвращает Bool. Когда в результате trueон сохраняет элемент, в противном случае элемент отфильтровывается.

Маттео Пьомбо
источник
16
Я бы сказал, что filterне обновляет массив, а просто возвращает новый
Антонио
Скобки следует удалить; это конечное закрытие.
Джесси,
@ Антонио, ты прав. Действительно, поэтому я разместил его как более безопасное решение. Для огромных массивов можно рассмотреть другое решение.
Маттео Пиомбо,
Хм, как вы говорите, это возвращает новый массив. Можно ли тогда превратить этот filterметод в mutatingодин (поскольку я читал, что mutatingключевое слово позволяет selfвместо этого изменять такие функции )?
Gee.E 06
@ 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]; печать (х); var i = 0; для v в x {if (v% 2 == 0) {x.remove (at: i)} else {i + = 1}}; print (x)
404compilernotfound 01
Да, вы правы, при условии, что вы точно знаете, что делаете. Может, я не ясно выразил свой ответ. Я должен был сказать, что это возможно, но это небезопасно . Это небезопасно, потому что вы изменяете размер контейнера, и если вы сделаете ошибку в коде, ваше приложение выйдет из строя. Swift - это все о написании безопасного кода, который не приведет к неожиданным сбоям во время выполнения. Именно поэтому с помощью функции программирования Функциональной таких как 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 #. Это не модификация, которая вызовет здесь исключение, а возможность неправильно управлять индексами и выйти за пределы (потому что они уменьшили размер). Но я определенно согласен с вами в том, что обычно безопаснее и понятнее использовать методы функционального программирования для управления коллекциями. Особенно в Swift.
404compilernotfound
2

Либо создайте изменяемый массив для хранения элементов, которые нужно удалить, а затем, после перечисления, удалите эти элементы из оригинала. Или создайте копию массива (неизменяемую), перечислите ее и удалите объекты (не по индексу) из оригинала при перечислении.

Wain
источник
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
}
Locutus
источник
1

Я рекомендую установить для элементов значение nil во время перечисления, а после завершения удалить все пустые элементы с помощью метода массивов filter ().

бесплатно
источник
1
Это работает, только если сохраненный тип является необязательным. Также обратите внимание, что filterметод не удаляет, а генерирует новый массив.
Антонио
Согласен. Обратный порядок - лучшее решение.
Freele
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) // ["!", "@", "$"]
Гленн Посадас
источник