Sichere (durch Grenzen überprüfte) Array-Suche in Swift durch optionale Bindungen?

271

Wenn ich ein Array in Swift habe und versuche, auf einen Index zuzugreifen, der außerhalb der Grenzen liegt, liegt ein nicht überraschender Laufzeitfehler vor:

var str = ["Apple", "Banana", "Coconut"]

str[0] // "Apple"
str[3] // EXC_BAD_INSTRUCTION

Ich hätte jedoch gedacht, dass es bei all der optionalen Verkettung und Sicherheit , die Swift mit sich bringt, trivial wäre, etwas zu tun wie:

let theIndex = 3
if let nonexistent = str[theIndex] { // Bounds check + Lookup
    print(nonexistent)
    ...do other things with nonexistent...
}

Anstatt:

let theIndex = 3
if (theIndex < str.count) {         // Bounds check
    let nonexistent = str[theIndex] // Lookup
    print(nonexistent)   
    ...do other things with nonexistent... 
}

Dies ist jedoch nicht der Fall - ich muss die alte ifAnweisung verwenden, um zu überprüfen und sicherzustellen, dass der Index kleiner als ist str.count.

Ich habe versucht, meine eigene subscript()Implementierung hinzuzufügen , bin mir jedoch nicht sicher, wie ich den Aufruf an die ursprüngliche Implementierung weiterleiten oder auf die Elemente zugreifen soll (indexbasiert), ohne die tiefgestellte Notation zu verwenden:

extension Array {
    subscript(var index: Int) -> AnyObject? {
        if index >= self.count {
            NSLog("Womp!")
            return nil
        }
        return ... // What?
    }
}
Craig Otis
quelle
2
Mir ist klar, dass dies ein wenig OT ist, aber ich denke auch, dass es schön wäre, wenn Swift eine klare Syntax für die Durchführung jeglicher Art von Grenzüberprüfung, einschließlich Listen, hätte. Wir haben bereits ein passendes Schlüsselwort dafür, in. Also zum Beispiel, wenn X in (1,2,7) ... oder wenn X in myArray
Maury Markowitz

Antworten:

652

Alex 'Antwort hat gute Ratschläge und Lösungen für die Frage, aber ich bin zufällig auf eine schönere Art gestoßen, diese Funktionalität zu implementieren:

Swift 3.2 und neuer

extension Collection {

    /// Returns the element at the specified index if it is within bounds, otherwise nil.
    subscript (safe index: Index) -> Element? {
        return indices.contains(index) ? self[index] : nil
    }
}

Swift 3.0 und 3.1

extension Collection where Indices.Iterator.Element == Index {

    /// Returns the element at the specified index if it is within bounds, otherwise nil.
    subscript (safe index: Index) -> Generator.Element? {
        return indices.contains(index) ? self[index] : nil
    }
}

Dank an Hamish für die Entwicklung der Lösung für Swift 3 .

Swift 2

extension CollectionType {

    /// Returns the element at the specified index if it is within bounds, otherwise nil.
    subscript (safe index: Index) -> Generator.Element? {
        return indices.contains(index) ? self[index] : nil
    }
}

Beispiel

let array = [1, 2, 3]

for index in -20...20 {
    if let item = array[safe: index] {
        print(item)
    }
}
Nikita Kukushkin
quelle
45
Ich denke, das verdient definitiv Aufmerksamkeit - gute Arbeit. Ich mag den enthaltenen safe:Parameternamen, um den Unterschied sicherzustellen.
Craig Otis
11
Ab Swift 2 (Xcode 7) muss dies ein wenig verbessert werden:return self.indices ~= index ? self[index] : nil;
Tim
7
In Bezug auf die Swift 3-Version: möglicherweise eine Eingabeaufforderung nur für Eckfälle, aber dennoch eine Eingabeaufforderung: Es gibt Fälle, in denen die oben genannte "sichere" Indexversion nicht sicher ist (während die Swift 2-Version dies war): für CollectionTypen, bei denen dies der Fall Indicesist nicht zusammenhängend. ZB muss für SetFälle, wenn wir durch den Index (ein Set Element zugreifen waren SetIndex<Element>), können wir in Laufzeitausnahmen für Indizes ausführen, sind >= startIndexund < endIndex, in diesem Fall der sichere Index (siehe zB versagt dieses erfundene Beispiel ).
dfri
12
WARNUNG! Das Überprüfen von Arrays auf diese Weise kann sehr teuer sein. Die containsMethode durchläuft alle Indizes und macht dies zu einem O (n). Ein besserer Weg ist es, den Index zu verwenden und zu zählen, um die Grenzen zu überprüfen.
Stefan Vasiljevic
6
Um zu verhindern, dass Indizes generiert und über diese iteriert werden (O (n)), ist es besser, Vergleiche zu verwenden (O (1)): return index >= startIndex && index < endIndex ? self[index] : nil CollectionTypen haben startIndex, endIndexwelche sind Comparable. Natürlich funktioniert dies nicht für einige seltsame Sammlungen, die zum Beispiel keine Indizes in der Mitte haben. Die Lösung mit indicesist allgemeiner.
Zubko
57

Wenn Sie dieses Verhalten wirklich wollen, riecht es so, als wollten Sie ein Wörterbuch anstelle eines Arrays. Wörterbücher Rückkehr nilbeim Zugriff auf Schlüssel fehlen, was Sinn macht , weil es viel schwieriger zu wissen , ist , wenn ein Schlüssel in einem Wörterbuch vorhanden ist , da diese Schlüssel können alles sein, wo in einem Array der Schlüssel muss in einem Bereich von: 0zu count. Und es ist unglaublich üblich, über diesen Bereich zu iterieren, wobei Sie absolut sicher sein können, dass jede Iteration einer Schleife einen echten Wert hat.

Ich denke, der Grund, warum es nicht so funktioniert, ist eine Designentscheidung der Swift-Entwickler. Nehmen Sie Ihr Beispiel:

var fruits: [String] = ["Apple", "Banana", "Coconut"]
var str: String = "I ate a \( fruits[0] )"

Wenn Sie bereits wissen, dass der Index vorhanden ist, wie Sie es in den meisten Fällen tun, wenn Sie ein Array verwenden, ist dieser Code großartig. Wenn der Zugriff auf einen Index jedoch möglicherweise zurückkehren könnte nil, haben Sie den Rückgabetyp der Methode von Array' subscriptoptional geändert . Dies ändert Ihren Code in:

var fruits: [String] = ["Apple", "Banana", "Coconut"]
var str: String = "I ate a \( fruits[0]! )"
//                                     ^ Added

Dies bedeutet, dass Sie jedes Mal, wenn Sie ein Array durchlaufen oder eine andere Option mit einem bekannten Index ausführen, ein optionales Element auspacken müssen, nur weil Sie selten auf einen Index außerhalb der Grenzen zugreifen können. Die Swift-Designer entschieden sich für weniger Auspacken von Optionen auf Kosten einer Laufzeitausnahme beim Zugriff auf Indizes außerhalb der Grenzen. Und ein Absturz ist einem logischen Fehler vorzuziehen, der durch einen Fehler verursacht wird, den nilSie in Ihren Daten nicht irgendwo erwartet haben.

Und ich stimme ihnen zu. Sie werden also die Standardimplementierung nicht ändern Array, da Sie den gesamten Code beschädigen würden, der nicht optionale Werte von Arrays erwartet.

Stattdessen können Sie eine Unterklasse Arrayerstellen und überschreiben subscript, um eine Option zurückzugeben. Praktischerweise können Sie dies auch Arraymit einer nicht tiefgestellten Methode erweitern.

extension Array {

    // Safely lookup an index that might be out of bounds,
    // returning nil if it does not exist
    func get(index: Int) -> T? {
        if 0 <= index && index < count {
            return self[index]
        } else {
            return nil
        }
    }
}

var fruits: [String] = ["Apple", "Banana", "Coconut"]
if let fruit = fruits.get(1) {
    print("I ate a \( fruit )")
    // I ate a Banana
}

if let fruit = fruits.get(3) {
    print("I ate a \( fruit )")
    // never runs, get returned nil
}

Swift 3 Update

func get(index: Int) ->T? muss ersetzt werden durch func get(index: Int) ->Element?

Alex Wayne
quelle
2
+1 (und das Akzeptieren) für die Erwähnung des Problems beim Ändern des Rückgabetyps subscript()in einen optionalen Typ - dies war die primäre Hürde beim Überschreiben des Standardverhaltens. (Ich konnte es überhaupt nicht zum Laufen bringen . ) Ich habe es vermieden, eine get()Erweiterungsmethode zu schreiben , was in anderen Szenarien (Obj-C-Kategorien, irgendjemand?) Die offensichtliche Wahl ist, aber get(nicht viel größer als [und macht es Es ist klar, dass sich das Verhalten möglicherweise von dem unterscheidet, was andere Entwickler vom Swift-Indexoperator erwarten. Danke dir!
Craig Otis
3
Um es noch kürzer zu machen, benutze ich at ();) Danke!
Hyouuu
7
Ab Swift 2.0 Twurde in umbenannt Element. Nur eine freundliche Erinnerung :)
Stas Zhukovskiy
3
Ein weiterer Grund, warum die Überprüfung von Grenzen nicht in Swift eingebrannt wird, um ein optionales Element zurückzugeben, besteht darin, dass das Zurückgeben, nilanstatt eine Ausnahme von einem Index außerhalb der Grenzen zu verursachen, nicht eindeutig wäre. Da zB Array<String?>auch nil als gültiges Mitglied der Sammlung zurückgegeben werden könnte, könnten Sie zwischen diesen beiden Fällen nicht unterscheiden. Wenn Sie einen eigenen Sammlungstyp haben, von dem Sie wissen, dass er niemals einen nilWert zurückgeben kann, auch bekannt als kontextbezogen zur Anwendung, können Sie Swift für die Überprüfung sicherer Grenzen erweitern, wie in diesem Beitrag beantwortet.
Aaron
Funktioniert wunderbar
kamyFC
20

Um auf der Antwort von Nikita Kukushkin aufzubauen, müssen Sie manchmal Array-Indizes sicher zuweisen und daraus lesen, d. H.

myArray[safe: badIndex] = newValue

Hier ist also ein Update von Nikitas Antwort (Swift 3.2), mit dem Sie auch sicher in veränderbare Array-Indizes schreiben können, indem Sie den Namen safe: parameter hinzufügen.

extension Collection {
    /// Returns the element at the specified index iff it is within bounds, otherwise nil.
    subscript(safe index: Index) -> Element? {
        return indices.contains(index) ? self[ index] : nil
    }
}

extension MutableCollection {
    subscript(safe index: Index) -> Element? {
        get {
            return indices.contains(index) ? self[ index] : nil
        }

        set(newValue) {
            if let newValue = newValue, indices.contains(index) {
                self[ index] = newValue
            }
        }
    }
}
SafeFastExpressive
quelle
2
Extrem unterschätzte Antwort! Dies ist der richtige Weg, um dies zu tun!
Reid
14

Gültig in Swift 2

Obwohl dies bereits oft beantwortet wurde, möchte ich eine Antwort geben, die mehr der Mode der Swift-Programmierung entspricht, die in Crustys Worten¹ lautet: "Think protocols first".

• Was wollen wir machen?
- Holen Sie sich ein Element eines Arraybestimmten Index nur, wenn es sicher ist, und nilansonsten.
• Worauf sollte diese Funktionalität bei der Implementierung basieren?
- Array subscripting
• Wo es diese Funktion von bekommt?
- Seine Definition struct Arrayim SwiftModul hat es
• Nichts allgemeiner / abstrakter?
- Es nimmt an, protocol CollectionTypewas es auch sicherstellt.
• Nichts allgemeineres / abstrakteres?
- Es nimmt protocol Indexableauch an ...
• Ja, klingt nach dem Besten, was wir tun können. Können wir es dann erweitern, um diese Funktion zu haben, die wir wollen?
- Aber wir haben sehr begrenzte Typen (nein Int) und Eigenschaften (neincount) jetzt arbeiten!
• Es wird genug sein. Swifts stdlib ist ziemlich gut gemacht;)

extension Indexable {
    public subscript(safe safeIndex: Index) -> _Element? {
        return safeIndex.distanceTo(endIndex) > 0 ? self[safeIndex] : nil
    }
}

¹: nicht wahr, aber es gibt die Idee

DeFrenZ
quelle
2
Als Swift-Neuling verstehe ich diese Antwort nicht. Was bedeutet der Code am Ende? Ist das eine Lösung und wenn ja, wie verwende ich sie tatsächlich?
Thomas Tempelmann
3
Entschuldigung, diese Antwort ist für Swift 3 nicht mehr gültig, aber der Prozess ist es sicherlich. Der einzige Unterschied ist, dass Sie jetzt Collectionwahrscheinlich anhalten sollten :)
DeFrenZ
11
extension Array {
    subscript (safe index: Index) -> Element? {
        return 0 <= index && index < count ? self[index] : nil
    }
}
  • O (1) Leistung
  • Typ sicher
  • geht korrekt mit Optionen für [MyType?] um (gibt MyType ?? zurück, das auf beiden Ebenen ausgepackt werden kann)
  • führt nicht zu Problemen für Sets
  • prägnanter Code

Hier sind einige Tests, die ich für Sie durchgeführt habe:

let itms: [Int?] = [0, nil]
let a = itms[safe: 0] // 0 : Int??
a ?? 5 // 0 : Int?
let b = itms[safe: 1] // nil : Int??
b ?? 5 // nil : Int?
let c = itms[safe: 2] // nil : Int??
c ?? 5 // 5 : Int?
thetrutz
quelle
10
  • Da Arrays möglicherweise keine Nullwerte speichern, ist es nicht sinnvoll, eine Null zurückzugeben, wenn ein Array [Index] -Aufruf außerhalb der Grenzen liegt.
  • Da wir nicht wissen, wie ein Benutzer Probleme außerhalb der Grenzen behandeln möchte, ist es nicht sinnvoll, benutzerdefinierte Operatoren zu verwenden.
  • Verwenden Sie im Gegensatz dazu den herkömmlichen Kontrollfluss zum Auspacken von Objekten und gewährleisten Sie die Typensicherheit.

if let index = array.checkIndexForSafety (index: Int)

  let item = array[safeIndex: index] 

if let index = array.checkIndexForSafety (index: Int)

  array[safeIndex: safeIndex] = myObject
extension Array {

    @warn_unused_result public func checkIndexForSafety(index: Int) -> SafeIndex? {

        if indices.contains(index) {

            // wrap index number in object, so can ensure type safety
            return SafeIndex(indexNumber: index)

        } else {
            return nil
        }
    }

    subscript(index:SafeIndex) -> Element {

        get {
            return self[index.indexNumber]
        }

        set {
            self[index.indexNumber] = newValue
        }
    }

    // second version of same subscript, but with different method signature, allowing user to highlight using safe index
    subscript(safeIndex index:SafeIndex) -> Element {

        get {
            return self[index.indexNumber]
        }

        set {
            self[index.indexNumber] = newValue
        }
    }

}

public class SafeIndex {

    var indexNumber:Int

    init(indexNumber:Int){
        self.indexNumber = indexNumber
    }
}
MS Cline
quelle
1
Interessanter Ansatz. Gibt SafeIndexes einen Grund für eine Klasse und keine Struktur?
stef
8

Swift 4

Eine Erweiterung für diejenigen, die eine traditionellere Syntax bevorzugen:

extension Array {

    func item(at index: Int) -> Element? {
        return indices.contains(index) ? self[index] : nil
    }
}
Matjan
quelle
Sie müssen die Array-Elemente nicht auf äquatable beschränken, um zu überprüfen, ob der Index den Index enthält.
Leo Dabus
ja - guter Punkt - es würde nur für zusätzliche sichere Methoden wie deleteObject usw. benötigt
Matjan
5

Ich fand sicheres Array abrufen, setzen, einfügen, entfernen sehr nützlich. Ich ziehe es vor, die Fehler zu protokollieren und zu ignorieren, da alles andere bald schwer zu verwalten ist. Vollständiger Code unten

/**
 Safe array get, set, insert and delete.
 All action that would cause an error are ignored.
 */
extension Array {

    /**
     Removes element at index.
     Action that would cause an error are ignored.
     */
    mutating func remove(safeAt index: Index) {
        guard index >= 0 && index < count else {
            print("Index out of bounds while deleting item at index \(index) in \(self). This action is ignored.")
            return
        }

        remove(at: index)
    }

    /**
     Inserts element at index.
     Action that would cause an error are ignored.
     */
    mutating func insert(_ element: Element, safeAt index: Index) {
        guard index >= 0 && index <= count else {
            print("Index out of bounds while inserting item at index \(index) in \(self). This action is ignored")
            return
        }

        insert(element, at: index)
    }

    /**
     Safe get set subscript.
     Action that would cause an error are ignored.
     */
    subscript (safe index: Index) -> Element? {
        get {
            return indices.contains(index) ? self[index] : nil
        }
        set {
            remove(safeAt: index)

            if let element = newValue {
                insert(element, safeAt: index)
            }
        }
    }
}

Tests

import XCTest

class SafeArrayTest: XCTestCase {
    func testRemove_Successful() {
        var array = [1, 2, 3]

        array.remove(safeAt: 1)

        XCTAssert(array == [1, 3])
    }

    func testRemove_Failure() {
        var array = [1, 2, 3]

        array.remove(safeAt: 3)

        XCTAssert(array == [1, 2, 3])
    }

    func testInsert_Successful() {
        var array = [1, 2, 3]

        array.insert(4, safeAt: 1)

        XCTAssert(array == [1, 4, 2, 3])
    }

    func testInsert_Successful_AtEnd() {
        var array = [1, 2, 3]

        array.insert(4, safeAt: 3)

        XCTAssert(array == [1, 2, 3, 4])
    }

    func testInsert_Failure() {
        var array = [1, 2, 3]

        array.insert(4, safeAt: 5)

        XCTAssert(array == [1, 2, 3])
    }

    func testGet_Successful() {
        var array = [1, 2, 3]

        let element = array[safe: 1]

        XCTAssert(element == 2)
    }

    func testGet_Failure() {
        var array = [1, 2, 3]

        let element = array[safe: 4]

        XCTAssert(element == nil)
    }

    func testSet_Successful() {
        var array = [1, 2, 3]

        array[safe: 1] = 4

        XCTAssert(array == [1, 4, 3])
    }

    func testSet_Successful_AtEnd() {
        var array = [1, 2, 3]

        array[safe: 3] = 4

        XCTAssert(array == [1, 2, 3, 4])
    }

    func testSet_Failure() {
        var array = [1, 2, 3]

        array[safe: 4] = 4

        XCTAssert(array == [1, 2, 3])
    }
}
Ivan Rep
quelle
3
extension Array {
  subscript (safe index: UInt) -> Element? {
    return Int(index) < count ? self[Int(index)] : nil
  }
}

Wenn Sie die oben erwähnte Erweiterung verwenden, geben Sie null zurück, wenn der Index zu irgendeinem Zeitpunkt nicht mehr gebunden ist.

let fruits = ["apple","banana"]
print("result-\(fruits[safe : 2])")

Ergebnis - Null

Vinayak Pal
quelle
3

Mir ist klar, dass dies eine alte Frage ist. Ich benutze Swift5.1 zu diesem Zeitpunkt, das OP war für Swift 1 oder 2?

Ich brauchte heute so etwas, aber ich wollte nicht nur für einen Ort eine vollständige Erweiterung hinzufügen und wollte etwas funktionaleres (threadsicherer?). Ich musste mich auch nicht vor negativen Indizes schützen, sondern nur vor solchen, die möglicherweise hinter dem Ende eines Arrays liegen:

let fruit = ["Apple", "Banana", "Coconut"]

let a = fruit.dropFirst(2).first // -> "Coconut"
let b = fruit.dropFirst(0).first // -> "Apple"
let c = fruit.dropFirst(10).first // -> nil

Was tun Sie für diejenigen, die über Nullen diskutieren, mit den Eigenschaften firstund last, die für leere Sammlungen Null zurückgeben?

Ich mochte das, weil ich einfach an vorhandenen Sachen greifen und sie verwenden konnte, um das gewünschte Ergebnis zu erzielen. Ich weiß auch, dass dropFirst (n) keine ganze Sammlungskopie ist, sondern nur ein Slice. Und dann übernimmt für mich das bereits existierende Verhalten von erst.

Travis Griggs
quelle
1

Ich denke, das ist keine gute Idee. Es scheint vorzuziehen, soliden Code zu erstellen, der nicht dazu führt, dass versucht wird, Out-of-Bound-Indizes anzuwenden.

Bitte beachten Sie, dass ein stillschweigender Fehler eines solchen Fehlers (wie in Ihrem obigen Code vorgeschlagen) bei der Rückgabe nildazu neigt, noch komplexere, unlösbarere Fehler zu erzeugen.

Sie können Ihre Überschreibung auf eine ähnliche Weise durchführen, die Sie verwendet haben, und die Indizes einfach auf Ihre eigene Weise schreiben. Der einzige Nachteil ist, dass vorhandener Code nicht kompatibel ist. Ich denke, einen Hook zu finden, um das generische x [i] zu überschreiben (auch ohne Textpräprozessor wie in C), wird eine Herausforderung sein.

Das nächste, an das ich denken kann, ist

// compile error:
if theIndex < str.count && let existing = str[theIndex]

EDIT : Das funktioniert tatsächlich. Einzeiler!!

func ifInBounds(array: [AnyObject], idx: Int) -> AnyObject? {
    return idx < array.count ? array[idx] : nil
}

if let x: AnyObject = ifInBounds(swiftarray, 3) {
    println(x)
}
else {
    println("Out of bounds")
}
Mundi
quelle
6
Ich würde nicht zustimmen - der Punkt der optionalen Bindung ist, dass sie nur erfolgreich ist, wenn die Bedingung erfüllt ist. (Für eine Option bedeutet dies, dass es einen Wert gibt.) Die Verwendung von a macht if letin diesem Fall weder das Programm komplexer noch die Fehler unlösbarer. Es verdichtet einfach die traditionelle ifÜberprüfung der Grenzen mit zwei Anweisungen und die tatsächliche Suche zu einer einzeiligen, komprimierten Anweisung. Es gibt Fälle (insbesondere in einem UI - Betrieb) , wo es normal ist , ein Index der Grenzen heraus, wie eine zu fragen NSTableViewfür die selectedRowohne Auswahl.
Craig Otis
3
@ Mundi Dies scheint eher ein Kommentar als eine Antwort auf die Frage des OP zu sein.
jlehr
1
@CraigOtis Ich bin mir nicht sicher, ob ich damit einverstanden bin. Sie können diese Prüfung kurz und bündig in einer "einzeiligen, komprimierten Anweisung" schreiben, z. B. mit countElementsoder wie beim OP count, nur nicht so, wie die Sprache das Schreiben von Array-Indizes definiert.
Mundi
1
@jlehr Vielleicht nicht. Es ist ein faires Spiel, die Absicht oder Weisheit eines gestellten Problems in Frage zu stellen.
Mundi
2
@ Mundi Heh, besonders wenn Sie es später bearbeiten, um die Frage tatsächlich zu beantworten. :-)
jlehr
1

Ich habe das Array nilin meinem Anwendungsfall mit s aufgefüllt :

let components = [1, 2]
var nilComponents = components.map { $0 as Int? }
nilComponents += [nil, nil, nil]

switch (nilComponents[0], nilComponents[1], nilComponents[2]) {
case (_, _, .Some(5)):
    // process last component with 5
default:
    break
}

Überprüfen Sie auch die tiefgestellte Erweiterung mit dem safe:Etikett von Erica Sadun / Mike Ash: http://ericasadun.com/2015/06/01/swift-safe-array-indexing-my-favorite-thing-of-the-new-week/

Marián Černý
quelle
0

Die Liste "Häufig abgelehnte Änderungen" für Swift enthält eine Erwähnung der Änderung des Array-Indexzugriffs, um einen optionalen statt eines Absturzes zurückzugeben:

Geben Sie den Array<T>Indexzugriff zurück T?oder T!anstelle von T: Das aktuelle Array-Verhalten ist beabsichtigt , da es genau die Tatsache widerspiegelt, dass der Array-Zugriff außerhalb der Grenzen ein logischer Fehler ist. Eine Änderung des aktuellen Verhaltens würde den ArrayZugriff in nicht akzeptablem Maße verlangsamen . Dieses Thema wurde bereits mehrfach angesprochen, es ist jedoch sehr unwahrscheinlich, dass es akzeptiert wird.

https://github.com/apple/swift-evolution/blob/master/commonly_proposed.md#strings-characters-and-collection-types

Der grundlegende Indexzugriff wird also nicht geändert, um ein optionales Element zurückzugeben.

Das Swift-Team / die Swift-Community scheint jedoch offen dafür zu sein , Arrays ein neues optional zurückgegebenes Zugriffsmuster hinzuzufügen , entweder über eine Funktion oder einen Index.

Dies wurde im Swift Evolution-Forum hier vorgeschlagen und diskutiert:

https://forums.swift.org/t/add-accessor-with-bounds-check-to-array/16871

Insbesondere Chris Lattner gab der Idee eine "+1":

Einverstanden, die am häufigsten vorgeschlagene Schreibweise hierfür ist : yourArray[safe: idx], was mir großartig erscheint. Ich bin sehr +1 für das Hinzufügen.

https://forums.swift.org/t/add-accessor-with-bounds-check-to-array/16871/13

Dies ist möglicherweise in einer zukünftigen Version von Swift sofort möglich. Ich würde jeden ermutigen, der möchte, dass es zu diesem Swift Evolution-Thread beiträgt.

pkamb
quelle
0

Um zu verbreiten, warum Operationen fehlschlagen, sind Fehler besser als Optionen. Indizes können keine Fehler auslösen, daher muss es sich um eine Methode handeln.

public extension Collection {
  /// - Returns: same as subscript, if index is in bounds
  /// - Throws: CollectionIndexingError
  func element(at index: Index) throws -> Element {
    guard indices.contains(index)
    else { throw CollectionIndexingError() }

    return self[index]
  }
}

/// Thrown when `element(at:)` is called with an invalid index.
public struct CollectionIndexingError: Error { }
XCTAssertThrowsError( try ["🐾", "🥝"].element(at: 2) )

let optionals = [1, 2, nil]
XCTAssertEqual(try optionals.element(at: 0), 1)

XCTAssertThrowsError( try optionals.element(at: optionals.endIndex) )
{ XCTAssert($0 is CollectionIndexingError) }
Jessy
quelle
0

Ich bin mir nicht sicher, warum niemand eine Erweiterung eingerichtet hat, die auch einen Setter hat, um das Array automatisch zu vergrößern

extension Array where Element: ExpressibleByNilLiteral {
    public subscript(safe index: Int) -> Element? {
        get {
            guard index >= 0, index < endIndex else {
                return nil
            }

            return self[index]
        }

        set(newValue) {
            if index >= endIndex {
                self.append(contentsOf: Array(repeating: nil, count: index - endIndex + 1))
            }

            self[index] = newValue ?? nil
        }
    }
}

Die Verwendung ist einfach und funktioniert ab Swift 5.1

var arr:[String?] = ["A","B","C"]

print(arr) // Output: [Optional("A"), Optional("B"), Optional("C")]

arr[safe:10] = "Z"

print(arr) // [Optional("A"), Optional("B"), Optional("C"), nil, nil, nil, nil, nil, nil, nil, Optional("Z")]

Hinweis: Sie sollten die Leistungskosten (sowohl zeitlich als auch räumlich) verstehen, wenn Sie ein Array schnell vergrößern. Bei kleinen Problemen müssen Sie jedoch manchmal nur Swift dazu bringen, Swifting selbst im Fuß zu stoppen

Shaheen Ghiassy
quelle
-1

Ich habe eine einfache Erweiterung für Array gemacht

extension Array where Iterator.Element : AnyObject {
    func iof (_ i : Int ) -> Iterator.Element? {
        if self.count > i {
            return self[i] as Iterator.Element
        }
        else {
            return nil
        }
    }

}

es funktioniert perfekt wie geplant

Beispiel

   if let firstElemntToLoad = roots.iof(0)?.children?.iof(0)?.cNode, 
Mohamed Deux
quelle
-1

Schnelle 5 Verwendung

extension WKNavigationType {
    var name : String {
        get {
            let names = ["linkAct","formSubm","backForw","reload","formRelo"]
            return names.indices.contains(self.rawValue) ? names[self.rawValue] : "other"
        }
    }
}

endete mit aber wollte eigentlich generell gerne machen

[<collection>][<index>] ?? <default>

aber da die Sammlung kontextbezogen ist, denke ich, ist es richtig.

Slashlos
quelle
Wie unterscheidet sich diese Antwort von der akzeptierten? Was mich betrifft, sieht es genauso aus (Duplikat).
Legonaftik
-1

Wenn Sie müssen nur bekommen Werte aus einem Array und Sie nicht über eine kleine Leistungseinbuße ausmachen (dh , wenn Sie Ihre Sammlung ist nicht sehr groß), eine Wörterbuch-basierte Alternative, die nicht zu allgemein mit sich bringt (für meine Geschmack) Sammlungserweiterung:

// Assuming you have a collection named array:
let safeArray = Dictionary(uniqueKeysWithValues: zip(0..., array))
let value = safeArray[index] ?? defaultValue;
Sorin Dolha
quelle