Usuwanie z tablicy podczas wyliczania w Swift?

86

Chcę wyliczyć tablicę w Swift i usunąć niektóre elementy. Zastanawiam się, czy jest to bezpieczne, a jeśli nie, to jak mam to osiągnąć.

Obecnie robiłbym to:

for (index, aString: String) in enumerate(array) {
    //Some of the strings...
    array.removeAtIndex(index)
}
Andrzej
źródło

Odpowiedzi:

72

W Swift 2 jest to dość łatwe w użyciu enumeratei reverse.

var a = [1,2,3,4,5,6]
for (i,num) in a.enumerate().reverse() {
    a.removeAtIndex(i)
}
print(a)
Johnston
źródło
1
Działa, ale filtr jest naprawdę najlepszy
13
@Mayerz False. „Chcę wyliczyć tablicę w Swift i usunąć niektóre elementy”. filterzwraca nową tablicę. Nie usuwasz niczego z tablicy. Nie nazwałbym nawet filterwyliczenia. Zawsze jest więcej niż jeden sposób na oskórowanie kota.
Johnston
6
prawda, moja wina! Nie
56

Możesz rozważyć filtersposób:

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

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

Parametr of filterto po prostu zamknięcie, które przyjmuje instancję typu tablicowego (w tym przypadku String) i zwraca wartość Bool. Kiedy wynik jest true, zachowuje element, w przeciwnym razie element jest odfiltrowywany.

Matteo Piombo
źródło
16
Powiedziałbym wyraźnie, że filternie aktualizuje tablicy, po prostu zwraca nową
Antonio,
Należy usunąć nawiasy; to jest końcowe zamknięcie.
Jessy
@Antonio masz rację. Rzeczywiście, dlatego opublikowałem to jako bezpieczniejsze rozwiązanie. W przypadku dużych macierzy można rozważyć inne rozwiązanie.
Matteo Piombo
Hm, jak mówisz, zwraca to nową tablicę. Czy można więc przekształcić filtermetodę w mutatingjedną (ponieważ przeczytałem, że mutatingsłowo kluczowe umożliwia selfzamiast tego zmianę funkcji takich jak ta )?
Gee.E
@ Gee.E Z pewnością możesz dodać filtr w miejscu jako rozszerzenie Arrayoznaczania go jako mutatingi podobnego do kodu pytania. W każdym razie weź pod uwagę, że nie zawsze może to być zaletą. W każdym razie za każdym razem, gdy usuwasz obiekt, twoja tablica może zostać zreorganizowana w pamięci. W ten sposób może być bardziej wydajne przydzielenie nowej tablicy, a następnie wykonanie atomowego podstawienia wynikiem funkcji filtrującej. Kompilator może wykonać jeszcze więcej optymalizacji, w zależności od kodu.
Matteo Piombo
38

W Swift 3 i 4 wyglądałoby to tak:

Z liczbami, zgodnie z odpowiedzią Johnstona:

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

Ze stringami jako pytaniem 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)

Jednak teraz w wersji Swift 4.2 lub nowszej istnieje jeszcze lepszy, szybszy sposób, który został zalecony przez Apple w WWDC2018:

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

Ten nowy sposób ma kilka zalet:

  1. Jest szybszy niż implementacje z filter.
  2. Eliminuje potrzebę odwracania tablic.
  3. Usuwa elementy w miejscu, a tym samym aktualizuje oryginalną tablicę zamiast przydzielać i zwracać nową tablicę.
jvarela
źródło
co jeśli przedmiot jest przedmiotem i muszę to sprawdzić, {$0 === Class.self}nie działa
TomSawyer
14

Gdy element o określonym indeksie zostanie usunięty z tablicy, pozycja (i indeks) wszystkich kolejnych elementów zostanie zmieniona, ponieważ cofną się o jedną pozycję.

Najlepszym sposobem jest więc nawigowanie po tablicy w odwrotnej kolejności - w tym przypadku sugeruję użycie tradycyjnej pętli for:

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

Jednak moim zdaniem najlepszym podejściem jest zastosowanie filtermetody opisanej przez @perlfly w swojej odpowiedzi.

Antonio
źródło
ale niestety został usunięty w szybkim 3
Sergey Brazhnik
4

Nie, mutowanie tablic podczas wyliczania nie jest bezpieczne, twój kod ulegnie awarii.

Jeśli chcesz usunąć tylko kilka obiektów, możesz skorzystać z filterfunkcji.

Starscream
źródło
3
To jest nieprawidłowe dla Swift. Tablice są typami wartości , więc są „kopiowane”, gdy są przekazywane do funkcji, przypisywane do zmiennych lub używane w wyliczaniu. (Swift implementuje funkcję kopiowania przy zapisie dla typów wartości, więc faktyczne kopiowanie jest ograniczone do minimum). Spróbuj wykonać następujące czynności, aby sprawdzić: var x = [1, 2, 3, 4, 5]; print (x); var i = 0; for v in x {if (v% 2 == 0) {x.remove (at: i)} else {i + = 1}}; print (x)
404compilernotfound
Tak, masz rację, pod warunkiem, że wiesz dokładnie, co robisz. Może nie wyraziłem jasno swojej odpowiedzi. Powinienem był powiedzieć, że to możliwe, ale nie jest bezpieczne . Nie jest to bezpieczne, ponieważ zmieniasz rozmiar kontenera i jeśli popełnisz błąd w kodzie, aplikacja ulegnie awarii. Swift polega na pisaniu bezpiecznego kodu, który nie ulegnie nieoczekiwanej awarii w czasie wykonywania. Dlatego korzystanie z funkcji programowania funkcjonalnego, takich jak filterjest bezpieczniejsze . Oto mój głupi przykład:var y = [1, 2, 3, 4, 5]; print(y); for (index, value) in y.enumerated() { y.remove(at: index) } print(y)
Starscream
Chciałem tylko zauważyć, że można zmodyfikować wyliczaną kolekcję w Swift, w przeciwieństwie do zachowania wyrzucania wyjątków podczas iteracji przez NSArray z szybkim wyliczaniem, a nawet typami kolekcji C #. To nie modyfikacja spowodowałaby tutaj wyjątek, ale możliwość niewłaściwego zarządzania indeksami i wykraczania poza granice (ponieważ zmniejszyły rozmiar). Ale zdecydowanie zgadzam się z tobą, że zwykle bezpieczniej i jaśniej jest używać metod programowania funkcjonalnego do manipulowania zbiorami. Zwłaszcza w Swift.
404compilernot znaleziony
2

Utwórz zmienną tablicę do przechowywania elementów do usunięcia, a następnie po wyliczeniu usuń te elementy z oryginału. Lub utwórz kopię tablicy (niezmienną), wylicz ją i usuń obiekty (nie według indeksu) z oryginału podczas wyliczania.

Fura
źródło
2

Tradycyjną pętlę for można zastąpić prostą pętlą while, przydatną, jeśli przed usunięciem trzeba wykonać również inne operacje na każdym elemencie.

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

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

     index -= 1
}
Locutus
źródło
1

Polecam ustawić elementy na nil podczas wyliczania, a po zakończeniu usunąć wszystkie puste elementy za pomocą metody Arrays filter ().

freele
źródło
1
Działa to tylko wtedy, gdy przechowywany typ jest opcjonalny. Zauważ również, że filtermetoda nie usuwa, generuje nową tablicę.
Antonio
Zgodzić się. Odwrócona kolejność jest lepszym rozwiązaniem.
freele
0

Wystarczy dodać, że jeśli masz wiele tablic i każdy element w indeksie N tablicy A jest powiązany z indeksem N tablicy B, to nadal możesz użyć metody odwracającej wyliczoną tablicę (jak poprzednie odpowiedzi). Pamiętaj jednak, że podczas uzyskiwania dostępu i usuwania elementów innych tablic nie ma potrzeby ich odwracania.

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) // ["!", "@", "$"]
Glenn Posadas
źródło