Swift Array - Überprüfen Sie, ob ein Index vorhanden ist

139

Gibt es in Swift eine Möglichkeit zu überprüfen, ob ein Index in einem Array vorhanden ist, ohne dass ein schwerwiegender Fehler ausgelöst wird?

Ich hatte gehofft, ich könnte so etwas tun:

let arr: [String] = ["foo", "bar"]
let str: String? = arr[1]
if let str2 = arr[2] as String? {
    // this wouldn't run
    println(str2)
} else {
    // this would be run
}

Aber ich verstehe

Schwerwiegender Fehler: Array-Index außerhalb des Bereichs

chrishale
quelle

Antworten:

441

Ein eleganter Weg in Swift:

let isIndexValid = array.indices.contains(index)
Manuel
quelle
10
Im wirklichen Leben sollte es keine Rolle spielen, aber in Bezug auf die Zeitkomplexität wäre es nicht viel besser, es zu verwenden index < array.count?
7.
2
Ich denke ehrlich, das Beispiel, das Sie gegeben haben, ist ein bisschen erfunden, da ich noch nie einen negativen Indexwert erlebt habe, aber wenn das wirklich der Fall sein sollte, wären zwei wahr / falsch-Prüfungen immer noch besser: dh index >= 0 && index < array.countanstatt dass der schlimmste Fall n ist Vergleiche.
7.
13
Falls Sie sich fragen, wie groß der Geschwindigkeitsunterschied ist, habe ich ihn mit den Xcode-Tools gemessen und er ist vernachlässigbar. gist.github.com/masonmark/a79bfa1204c957043687f4bdaef0c2ad
Mason
7
Nur um einen weiteren Punkt hinzuzufügen, warum dies richtig ist: Wenn Sie bei der Arbeit an einem dieselbe Logik anwenden ArraySlice, ist der erste Index nicht 0, daher ist dies index >= 0keine ausreichend gute Prüfung. .indicesfunktioniert stattdessen auf jeden Fall.
DeFrenZ
2
Ich liebe Swift dafür. Hier ist mein Anwendungsfall: Erstellen einer beschissenen JSON-Struktur für den Dienst, also musste ich Folgendes tun: "attendee3": names.indices.contains (2)? Namen [2]: ""
Lee Probert
64

Typerweiterung:

extension Collection {

    subscript(optional i: Index) -> Iterator.Element? {
        return self.indices.contains(i) ? self[i] : nil
    }

}

Wenn Sie dies verwenden, erhalten Sie einen optionalen Wert zurück, wenn Sie das Schlüsselwort optional zu Ihrem Index hinzufügen. Dies bedeutet, dass Ihr Programm nicht abstürzt, selbst wenn der Index außerhalb des Bereichs liegt. In Ihrem Beispiel:

let arr = ["foo", "bar"]
let str1 = arr[optional: 1] // --> str1 is now Optional("bar")
if let str2 = arr[optional: 2] {
    print(str2) // --> this still wouldn't run
} else {
    print("No string found at that index") // --> this would be printed
}
Benno Kress
quelle
4
Hervorragende Antwort 👏 Am wichtigsten ist, dass es während der Verwendung optionalim Parameter lesbar ist. Vielen Dank!
Jakub
2
Großartigkeit: D Hat meinen Tag gerettet.
Codetard
2
Das ist wunderschön
JoeGalind
32

Überprüfen Sie einfach, ob der Index kleiner als die Arraygröße ist:

if 2 < arr.count {
    ...
} else {
    ...
}
Antonio
quelle
3
Was ist, wenn die Arraygröße unbekannt ist?
Nathan McKaskle
8
@ NathanMcKaskle Das Array weiß immer, wie viele Elemente es enthält, so dass die Größe nicht unbekannt sein kann
Antonio
16
Entschuldigung, ich habe dort nicht gedacht. Der Morgenkaffee trat immer noch in den Blutkreislauf ein.
Nathan McKaskle
4
@ NathanMcKaskle keine Sorge ... manchmal passiert mir das auch nach mehreren Kaffees ;-)
Antonio
In mancher Hinsicht ist dies die beste Antwort, da sie in O (1) anstelle von O (n) ausgeführt wird.
ScottyBlades
12

Fügen Sie etwas Verlängerungszucker hinzu:

extension Collection {
  subscript(safe index: Index) -> Iterator.Element? {
    guard indices.contains(index) else { return nil }
    return self[index]
  }
}

if let item = ["a", "b", "c", "d"][safe: 3] { print(item) }//Output: "d"
//or with guard:
guard let anotherItem = ["a", "b", "c", "d"][safe: 3] else {return}
print(anotherItem) // "d"

Verbessert die Lesbarkeit bei der Stilcodierung if letin Verbindung mit Arrays

Eonist
quelle
2
Ehrlich gesagt, ist dies der schnellste Weg, dies mit maximaler Lesbarkeit und Klarheit zu tun
Barndog
1
@barndog Ich liebe es auch. Normalerweise füge ich dies als Zucker in jedem Projekt hinzu, das ich starte. Wachbeispiel ebenfalls hinzugefügt. Dank an die schnelle, lockere Community, die sich diese ausgedacht hat.
Eonist
Gute Lösung ... aber die Ausgabe gibt "d" in dem Beispiel aus, das Sie geben ...
jayant rawat
Dies sollte Teil der Swift-Sprache sein. Das Problem des Index außerhalb des Bereichs ist nicht weniger problematisch als Nullwerte. Ich würde sogar vorschlagen, eine ähnliche Syntax zu verwenden: vielleicht so etwas wie: myArray [? 3]. Wenn myArray optional ist, würden Sie myArray bekommen? [? 3]
Andy Weinstein
@Andy Weinstein Du solltest es Apfel vorschlagen e
Eonist
7

Sie können dies sicherer umschreiben, um die Größe des Arrays zu überprüfen und eine ternäre Bedingung zu verwenden:

if let str2 = (arr.count > 2 ? arr[2] : nil) as String?
dasblinkenlight
quelle
1
Was Antonio vorschlägt, ist viel transparenter (und effizienter). Wo das Ternäre angemessen wäre, wäre, wenn Sie einen leicht verfügbaren Wert hätten, um das Wenn insgesamt zu verwenden und zu eliminieren, indem Sie einfach eine let-Anweisung hinterlassen.
David Berry
1
@ David Was Antonio vorschlägt, erfordert zwei ifAnweisungen anstelle einer ifAnweisung im Originalcode. Mein Code ersetzt den zweiten ifdurch einen bedingten Operator, sodass Sie einen einzelnen behalten können, elseanstatt zwei separate elseBlöcke zu erzwingen .
Dasblinkenlight
1
Ich sehe keine zwei if-Anweisungen in seinem Code. Geben Sie str2 = arr [1] für seine Auslassungspunkte ein und fertig. Wenn Sie die OP-if-let-Anweisung durch die ifio-Anweisung von antonio ersetzen und die Zuweisung nach innen verschieben (oder nicht, da str2 nur zum Drucken verwendet wird, ist dies überhaupt nicht erforderlich). Fügen Sie die Dereferenzierung einfach in den Druck ein. Ganz zu schweigen davon dass der ternäre Operator nur ein dunkler (für einige :)) ist, wenn / sonst. Wenn arr. count> 2, dann kann arr [2] niemals null sein. Warum also String zuordnen? Nur damit Sie eine weitere if-Anweisung anwenden können.
David Berry
@ David Die gesamte ifFrage von OP wird im "Dann" -Zweig von Antonios Antwort landen, sodass es zwei verschachtelte ifs geben würde . Ich betrachte OPs Code als kleines Beispiel, also gehe ich davon aus, dass er immer noch einen haben möchte if. Ich stimme Ihnen zu, dass in seinem Beispiel ifnicht notwendig ist. Andererseits ist die gesamte Anweisung sinnlos, da OP weiß, dass das Array nicht genügend Länge hat und keines seiner Elemente vorhanden ist nil, sodass er das entfernen ifund nur seinen elseBlock behalten kann .
Dasblinkenlight
Nein, würde es nicht. Antonio's if ersetzt die if-Anweisung des OP. Da der Array-Typ [String] ist, wissen Sie, dass er niemals eine Null enthalten kann. Sie müssen also nichts über die Länge hinaus überprüfen.
David Berry
7

Swift 4 Erweiterung:

Für mich bevorzuge ich wie Methode.

// MARK: - Extension Collection

extension Collection {

    /// Get at index object
    ///
    /// - Parameter index: Index of object
    /// - Returns: Element at index or nil
    func get(at index: Index) -> Iterator.Element? {
        return self.indices.contains(index) ? self[index] : nil
    }
}

Vielen Dank an @Benno Kress

YannSteph
quelle
1

Feststellen, ob ein Array-Index vorhanden ist:

Diese Methode eignet sich hervorragend, wenn Sie keinen Erweiterungszucker hinzufügen möchten:

let arr = [1,2,3]
if let fourthItem = (3 < arr.count ?  arr[3] : nil ) {
     Swift.print("fourthItem:  \(fourthItem)")
}else if let thirdItem = (2 < arr.count ?  arr[2] : nil) {
     Swift.print("thirdItem:  \(thirdItem)")
}
//Output: thirdItem: 3
Eonist
quelle
1
extension Array {
    func isValidIndex(_ index : Int) -> Bool {
        return index < self.count
    }
}

let array = ["a","b","c","d"]

func testArrayIndex(_ index : Int) {

    guard array.isValidIndex(index) else {
        print("Handle array index Out of bounds here")
        return
    }

}

Es ist Arbeit für mich, mit indexOutOfBounds umzugehen .

Pratik Sodha
quelle
Was ist, wenn der Index negativ ist? Sie sollten wahrscheinlich auch das überprüfen.
Frankie Simon