Entfernen aus dem Array während der Aufzählung in Swift?

84

Ich möchte ein Array in Swift auflisten und bestimmte Elemente entfernen. Ich frage mich, ob dies sicher ist und wenn nicht, wie ich das erreichen soll.

Derzeit würde ich Folgendes tun:

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

Antworten:

71

In Swift 2 ist dies recht einfach enumerateund reverse.

var a = [1,2,3,4,5,6]
for (i,num) in a.enumerate().reverse() {
    a.removeAtIndex(i)
}
print(a)
Johnston
quelle
1
Funktioniert, aber Filter ist wirklich der
13
@Mayerz Falsch. „Ich mag aufzählen durch eine Anordnung in Swift und bestimmte Elemente entfernen.“ filtergibt ein neues Array zurück. Sie entfernen nichts aus dem Array. Ich würde nicht einmal filtereine Aufzählung nennen. Es gibt immer mehr als einen Weg, eine Katze zu häuten.
Johnston
6
Richtig, mein Schlechtes! Pla keine Haut häuten
55

Sie könnten überlegen, filterwie:

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

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

Der Parameter von filterist nur ein Abschluss, der eine Instanz vom Array-Typ (in diesem Fall String) verwendet und a zurückgibt Bool. Wenn das Ergebnis ist true, bleibt das Element erhalten, andernfalls wird das Element herausgefiltert.

Matteo Piombo
quelle
15
Ich würde explizit darauf filter
Antonio
Klammern sollten gelöscht werden. Das ist ein nachlaufender Verschluss.
Jessy
@ Antonio du hast recht. In der Tat habe ich es deshalb als sicherere Lösung veröffentlicht. Eine andere Lösung könnte für große Arrays in Betracht gezogen werden.
Matteo Piombo
Hm, wie Sie sagen, gibt dies ein neues Array zurück. Ist es dann möglich, die filterMethode in eine mutatingEins zu verwandeln (da ich das mutatingSchlüsselwort gelesen habe, können Funktionen wie diese selfstattdessen geändert werden)?
Gee.E
@ Gee.E Natürlich können Sie einen In-Place- Filter als Erweiterung hinzufügen Array, um ihn als mutatingund ähnlich dem Code der Frage zu markieren . Bedenken Sie jedoch, dass dies möglicherweise nicht immer von Vorteil ist. Auf jeden Fall jedes Mal , wenn Sie ein Objekt entfernen, könnte Ihr Array wird neu geordnet im Speicher. Somit könnte es effizienter sein, ein neues Array zuzuweisen und dann eine atomare Substitution mit dem Ergebnis der Filterfunktion vorzunehmen. Der Compiler kann je nach Code noch mehr Optimierungen vornehmen.
Matteo Piombo
37

In Swift 3 und 4 wäre dies:

Mit Zahlen nach Johnstons Antwort:

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

Mit Strings als OP-Frage:

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

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

In Swift 4.2 oder höher gibt es jedoch noch einen besseren und schnelleren Weg , den Apple im WWDC2018 empfohlen hat:

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

Dieser neue Weg hat mehrere Vorteile:

  1. Es ist schneller als Implementierungen mit filter .
  2. Die Umkehrung von Arrays entfällt.
  3. Es entfernt Elemente an Ort und Stelle und aktualisiert somit das ursprüngliche Array, anstatt ein neues Array zuzuweisen und zurückzugeben.
jvarela
quelle
Was ist, wenn Artikel ein Objekt ist und ich es überprüfen muss, {$0 === Class.self}funktioniert nicht
TomSawyer
14

Wenn ein Element an einem bestimmten Index aus einem Array entfernt wird, wird die Position (und der Index) aller nachfolgenden Elemente geändert, da sie um eine Position zurück verschoben werden.

Der beste Weg ist also, das Array in umgekehrter Reihenfolge zu navigieren - und in diesem Fall empfehle ich die Verwendung einer herkömmlichen for-Schleife:

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

Meiner Meinung nach ist der beste Ansatz jedoch die Verwendung der filterMethode, wie sie von @perlfly in seiner Antwort beschrieben wurde.

Antonio
quelle
aber leider wurde es in schnell 3 entfernt
Sergey Brazhnik
4

Nein, es ist nicht sicher, Arrays während der Aufzählung zu mutieren. Ihr Code stürzt ab.

Wenn Sie nur wenige Objekte löschen möchten, können Sie die filterFunktion verwenden.

Sternenschrei
quelle
3
Dies ist für Swift falsch. Arrays sind Werttypen, so dass sie „kopiert“ , wenn sie Funktionen übergeben werden, Variablen zugewiesen oder in Aufzählung verwendet. (Swift implementiert die Copy-on-Write-Funktion für Werttypen, sodass das tatsächliche Kopieren auf ein Minimum beschränkt ist.) Überprüfen Sie Folgendes: var x = [1, 2, 3, 4, 5]; print (x); var i = 0; für v in x {if (v% 2 == 0) {x.remove (at: i)} else {i + = 1}}; print (x)
404compilernotfound
Ja, Sie haben Recht, vorausgesetzt, Sie wissen genau, was Sie tun. Vielleicht habe ich meine Antwort nicht klar ausgedrückt. Ich hätte sagen sollen, dass es möglich ist, aber es ist nicht sicher . Dies ist nicht sicher, da Sie die Containergröße ändern und wenn Sie einen Fehler in Ihrem Code machen, stürzt Ihre App ab. Bei Swift dreht sich alles um das Schreiben von sicherem Code, der zur Laufzeit nicht unerwartet abstürzt. Deshalb filterist es sicherer , funktionale Programmierfunktionen zu verwenden . Hier ist mein dummes Beispiel:var y = [1, 2, 3, 4, 5]; print(y); for (index, value) in y.enumerated() { y.remove(at: index) } print(y)
Starscream
Ich wollte nur unterscheiden, dass es möglich ist, die in Swift aufgezählte Sammlung zu ändern, im Gegensatz zu dem Ausnahmeverhalten, das beim Durchlaufen von NSArray mit schneller Aufzählung oder sogar den Sammlungstypen von C # auftritt. Es ist nicht die Modifikation, die hier eine Ausnahme auslösen würde, sondern die Möglichkeit, Indizes schlecht zu verwalten und Grenzen zu überschreiten (weil sie die Größe verringert hatten). Aber ich stimme Ihnen definitiv zu, dass es normalerweise sicherer und klarer ist, die Methoden der funktionalen Programmierung zu verwenden, um Sammlungen zu manipulieren. Besonders in Swift.
404compilernotfound
2

Erstellen Sie entweder ein veränderbares Array, um die zu löschenden Elemente zu speichern, und entfernen Sie diese Elemente nach der Aufzählung aus dem Original. Oder erstellen Sie eine Kopie des Arrays (unveränderlich), listen Sie diese auf und entfernen Sie die Objekte (nicht nach Index) aus dem Original, während Sie auflisten.

Wain
quelle
2

Die herkömmliche for-Schleife kann durch eine einfache while-Schleife ersetzt werden. Dies ist nützlich, wenn Sie vor dem Entfernen auch einige andere Operationen an jedem Element ausführen müssen.

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

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

     index -= 1
}
Locutus
quelle
1

Ich empfehle, Elemente während der Aufzählung auf Null zu setzen und nach Abschluss alle leeren Elemente mit der Methode arrays filter () zu entfernen.

freele
quelle
1
Dies funktioniert nur, wenn der gespeicherte Typ optional ist. Beachten Sie auch, dass die filterMethode nicht entfernt, sondern ein neues Array generiert.
Antonio
Zustimmen. Umgekehrte Reihenfolge ist bessere Lösung.
Freele
0

Wenn Sie mehrere Arrays haben und jedes Element im Index N von Array A mit dem Index N von Array B verknüpft ist, können Sie weiterhin die Methode verwenden, mit der das aufgezählte Array umgekehrt wird (wie in den vorherigen Antworten). Denken Sie jedoch daran, dass Sie beim Zugriff auf und Löschen der Elemente der anderen Arrays diese nicht umkehren müssen.

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
quelle