Bezpieczne (sprawdzone granice) wyszukiwanie tablic w Swift, poprzez opcjonalne wiązania?

271

Jeśli mam tablicę w Swift i próbuję uzyskać dostęp do indeksu, który jest poza zakresem, pojawia się nieoczekiwany błąd w czasie wykonywania:

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

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

Jednak pomyślałbym z całym opcjonalnym łańcuchem i bezpieczeństwem, jakie zapewnia Swift, byłoby trywialne zrobić coś takiego:

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

Zamiast:

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

Ale tak nie jest - muszę użyć instrukcji ol ' if, aby sprawdzić i upewnić się, że indeks jest mniejszy niż str.count.

Próbowałem dodać własną subscript()implementację, ale nie jestem pewien, jak przekazać wywołanie do oryginalnej implementacji lub uzyskać dostęp do elementów (na podstawie indeksu) bez użycia notacji w indeksie dolnym:

extension Array {
    subscript(var index: Int) -> AnyObject? {
        if index >= self.count {
            NSLog("Womp!")
            return nil
        }
        return ... // What?
    }
}
Craig Otis
źródło
2
Zdaję sobie sprawę, że jest to nieco OT, ale wydaje mi się, że byłoby miło, gdyby Swift miał jasną składnię do wykonywania dowolnego sprawdzania granic, w tym list. Mamy już odpowiednie słowo kluczowe do tego, w. Więc na przykład, jeśli X w (1,2,7) ... lub jeśli X w myArray
Maury Markowitz

Odpowiedzi:

652

Odpowiedź Alexa zawiera dobrą radę i rozwiązanie tego pytania, jednak natknąłem się na lepszy sposób implementacji tej funkcjonalności:

Swift 3.2 i nowsze

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 i 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
    }
}

Podziękowania dla Hamisha za opracowanie rozwiązania dla 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
    }
}

Przykład

let array = [1, 2, 3]

for index in -20...20 {
    if let item = array[safe: index] {
        print(item)
    }
}
Nikita Kukushkin
źródło
45
Myślę, że to zdecydowanie zasługuje na uwagę - dobra robota. Podoba mi się dołączona safe:nazwa parametru, aby zapewnić różnicę.
Craig Otis
11
Począwszy od Swift 2 (Xcode 7) wymaga to drobnych poprawek:return self.indices ~= index ? self[index] : nil;
Tim
7
W odniesieniu do wersji Swift 3: być może monit tylko w rogu, ale jednak monit: istnieją przypadki, w których „bezpieczna” wersja indeksu dolnego powyżej nie jest bezpieczna (podczas gdy wersja Swift 2 była): dla Collectiontypów, w których Indicessą nie ciągłe. Na Setprzykład, jeśli mielibyśmy uzyskać dostęp do ustawionego elementu przez index ( SetIndex<Element>), możemy napotkać wyjątki w czasie wykonywania dla indeksów, które są >= startIndexiw < endIndextakim przypadku bezpieczny indeks dolny zawodzi (patrz np. Ten wymyślony przykład ).
dfri
12
OSTRZEŻENIE! Sprawdzanie tablic w ten sposób może być naprawdę drogie. containsMetoda iterację wszystkich wskaźników w ten sposób dzięki czemu jest to O (n). Lepszym sposobem jest użycie indeksu i liczenia do sprawdzenia granic.
Stefan Vasiljevic
6
Aby zapobiec generowaniu indeksów i iteracji nad nimi (O (n)), lepiej jest używać porównań (O (1)): return index >= startIndex && index < endIndex ? self[index] : nil Collectiontypy mają startIndex, endIndexktóre są Comparable. Oczywiście to nie zadziała w przypadku niektórych dziwnych kolekcji, które na przykład nie mają wskaźników w środku, rozwiązanie z indicesbardziej ogólnym.
zubko
57

Jeśli naprawdę chcesz tego zachowania, pachnie tak, jakbyś chciał Słownik zamiast Tablicy. Słowniki powracają nilpodczas uzyskiwania dostępu do brakujących kluczy, co ma sens, ponieważ znacznie trudniej jest ustalić, czy klucz jest obecny w słowniku, ponieważ klucze te mogą być dowolne, gdzie w tablicy klucz musi zawierać się w przedziale: 0do count. I to jest niezwykle powszechne, aby iterować w tym zakresie, gdzie możesz być absolutnie pewien, że ma realną wartość na każdej iteracji pętli.

Myślę, że powodem, dla którego nie działa w ten sposób, jest wybór projektu dokonany przez programistów Swift. Weź przykład:

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

Jeśli już wiesz, że indeks istnieje, podobnie jak w większości przypadków, gdy używasz tablicy, ten kod jest świetny. Jednakże, jeżeli uzyskanie dostępu do indeks mógłby powrócić nilpotem zmieniły typ zwracany z Array„s subscriptmetody być opcjonalne. To zmienia kod na:

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

Co oznacza, że ​​będziesz musiał rozpakować opcjonalne za każdym razem, gdy przeprowadzasz iterację przez tablicę lub robisz cokolwiek innego ze znanym indeksem, tylko dlatego, że rzadko możesz uzyskać dostęp do indeksu poza zakresem. Projektanci Swift zdecydowali się na mniejsze rozpakowywanie opcji, kosztem wyjątku czasu wykonywania podczas uzyskiwania dostępu do indeksów poza granicami. Awaria jest lepsza niż błąd logiczny spowodowany przez nilnieoczekiwane gdzieś dane.

I zgadzam się z nimi. Nie zmienisz więc domyślnej Arrayimplementacji, ponieważ zniszczyłbyś cały kod, który oczekuje od opcjonalnych wartości z tablic.

Zamiast tego możesz podklasę Arrayi zastąpić, subscriptaby zwrócić opcjonalną. Lub, bardziej praktycznie, można rozszerzyć Arrayza pomocą metody innej niż indeks dolny, która to robi.

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
}

Aktualizacja Swift 3

func get(index: Int) ->T? musi zostać zastąpiony przez func get(index: Int) ->Element?

Alex Wayne
źródło
2
+1 (i zaakceptuj) za wzmiankę o problemie ze zmianą typu zwracanego subscript()na opcjonalny - była to podstawowa przeszkoda w zastąpieniu domyślnego zachowania. (Właściwie nie mogłem go uruchomić . ) Unikałem pisania get()metody rozszerzenia, która jest oczywistym wyborem w innych scenariuszach (kategorie Obj-C, ktoś?), Ale get(nie jest znacznie większa [i sprawia, że ​​jest jasne, że zachowanie może różnić się od tego, czego inni programiści mogą oczekiwać od operatora indeksów Swift. Dziękuję Ci!
Craig Otis,
3
Aby było jeszcze krótsze, używam at ();) Dzięki!
hyouuu,
7
Od Swift 2.0 Tprzemianowano na Element. Tylko przyjazne przypomnienie :)
Stas Zhukovskiy
3
Aby dodać do tej dyskusji, kolejnym powodem, dla którego sprawdzanie granic nie jest zapisywane w Swift w celu zwrócenia opcjonalnego, jest to, że zwracanie nilzamiast powodowania wyjątku z indeksu poza zakresem byłoby niejednoznaczne. Ponieważ np. Array<String?>Może również zwrócić zero jako prawidłowy element kolekcji, nie będziesz w stanie rozróżnić tych dwóch przypadków. Jeśli masz swój własny typ kolekcji, o którym wiesz, że nigdy nie może zwrócić nilwartości, czyli jest kontekstowy dla aplikacji, możesz rozszerzyć Swift o bezpieczne sprawdzanie granic, zgodnie z odpowiedzią w tym poście.
Aaron,
Działa pięknie
kamyFC
20

Aby skorzystać z odpowiedzi Nikity Kukushkin, czasami trzeba bezpiecznie przypisać indeksy tablic, a także odczytać z nich, tj.

myArray[safe: badIndex] = newValue

Oto aktualizacja odpowiedzi Nikity (Swift 3.2), która pozwala również bezpiecznie zapisywać do indeksów tablic zmiennych, dodając nazwę parametru safe:.

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
źródło
2
Bardzo niedoceniana odpowiedź! To jest właściwy sposób, aby to zrobić!
Reid,
14

Obowiązuje w Swift 2

Chociaż na to pytanie udzielano już wiele razy, chciałbym przedstawić odpowiedź bardziej zgodną z kierunkami programowania Swift, która według słów Crusty'ego: „ protocolNajpierw myśl ”

• Co chcemy robić?
- Zdobądź element Arraydanego indeksu tylko wtedy, gdy jest bezpieczny, a nilpoza tym
• Na czym powinna polegać ta funkcjonalność?
- Array subscripting
• Skąd ta funkcja?
- Ma swoją definicję struct Arrayw Swiftmodule.
• Nic bardziej ogólnego / abstrakcyjnego?
- Przyjmuje, protocol CollectionTypeco również to zapewnia
• Nic bardziej ogólnego / abstrakcyjnego?
- Przyjmuje protocol Indexablerównież ...
• Tak, brzmi jak najlepiej, co możemy zrobić. Czy możemy go następnie rozszerzyć, aby uzyskać tę funkcję, której chcemy?
- Ale mamy bardzo ograniczone typy (nie Int) i właściwości (niecount) do pracy teraz!
• To wystarczy. Stdlib Swifta jest całkiem niezły;)

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

¹: nieprawda, ale daje pomysł

DeFrenZ
źródło
2
Jako Szybki początkujący nie rozumiem tej odpowiedzi. Co oznacza kod na końcu? Czy to rozwiązanie, a jeśli tak, to jak mam z niego korzystać?
Thomas Tempelmann
3
Niestety, ta odpowiedź nie jest już ważna dla Swift 3, ale proces z pewnością jest. Jedyną różnicą jest to, że teraz powinieneś zatrzymać się Collectionprawdopodobnie :)
DeFrenZ
11
extension Array {
    subscript (safe index: Index) -> Element? {
        return 0 <= index && index < count ? self[index] : nil
    }
}
  • Wydajność O (1)
  • wpisz bezpieczny
  • poprawnie obsługuje Optionals dla [MyType?] (zwraca MyType ??, które można rozpakować na obu poziomach)
  • nie powoduje problemów dla zestawów
  • zwięzły kod

Oto kilka testów, które dla ciebie przeprowadziłem:

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
źródło
10
  • Ponieważ tablice mogą przechowywać wartości zerowe, zwracanie wartości zerowej nie ma sensu, jeśli wywołanie tablicy [indeks] jest poza zakresem.
  • Ponieważ nie wiemy, jak użytkownik chciałby poradzić sobie z problemami wykraczającymi poza granice, używanie operatorów niestandardowych nie ma sensu.
  • Natomiast do rozpakowywania obiektów używaj tradycyjnego przepływu sterowania i zapewniaj bezpieczeństwo typu.

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
źródło
1
Ciekawe podejście Jakikolwiek powód SafeIndexjest klasą, a nie strukturą?
stef,
8

Szybki 4

Rozszerzenie dla tych, którzy wolą bardziej tradycyjną składnię:

extension Array {

    func item(at index: Int) -> Element? {
        return indices.contains(index) ? self[index] : nil
    }
}
Matjan
źródło
nie trzeba ograniczać elementów tablicy do równoznacznych, aby sprawdzić, czy indeksy zawierają jego indeks.
Leo Dabus
tak - dobra uwaga - byłoby potrzebne tylko w przypadku dodatkowych bezpiecznych metod, takich jak deleteObject itp.
Matjan
5

Zauważyłem, że bezpieczna tablica jest bardzo przydatna, ustawia, wstawia i usuwa. Wolę rejestrować i ignorować błędy, ponieważ wszystko inne wkrótce staje się trudne do zarządzania. Pełny kod poniżej

/**
 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)
            }
        }
    }
}

Testy

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
źródło
3
extension Array {
  subscript (safe index: UInt) -> Element? {
    return Int(index) < count ? self[Int(index)] : nil
  }
}

Używając powyższej wzmianki, zwróć zero, jeśli indeks w dowolnym momencie wykracza poza ramy.

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

wynik - zero

Vinayak Pal
źródło
3

Zdaję sobie sprawę, że to stare pytanie. Używam Swift5.1 w tym momencie, OP było dla Swift 1 lub 2?

Potrzebowałem dziś czegoś takiego, ale nie chciałem dodawać rozszerzenia o pełnej skali tylko dla jednego miejsca i chciałem czegoś bardziej funkcjonalnego (bardziej bezpieczny dla wątków?). Nie musiałem też chronić się przed ujemnymi indeksami, tylko tymi, które mogą być poza końcem tablicy:

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

Co robisz z argumentami o sekwencjach z zerami, co robisz z właściwościami firsti lastwłaściwościami zwracającymi zero dla pustych kolekcji?

Podobało mi się to, ponieważ mogłem po prostu złapać istniejące rzeczy i użyć go, aby uzyskać pożądany rezultat. Wiem również, że dropFirst (n) nie jest kopią całej kolekcji, tylko wycinek. I wtedy zachowuje się już istniejące zachowanie pierwszego.

Travis Griggs
źródło
1

Myślę, że to nie jest dobry pomysł. Wydaje się, że lepiej jest zbudować solidny kod, który nie spowoduje próby zastosowania indeksów spoza zakresu.

Proszę wziąć pod uwagę, że taki błąd nie powiedzie się po cichu (jak sugeruje powyższy kod) przez powrót, niljest podatny na generowanie jeszcze bardziej złożonych, trudniejszych do rozwiązania błędów.

Możesz zrobić to w podobny sposób, w jaki użyłeś i po prostu napisać indeksy na swój własny sposób. Jedyną wadą jest to, że istniejący kod nie będzie kompatybilny. Myślę, że znalezienie haka do zastąpienia ogólnego x [i] (również bez preprocesora tekstu jak w C) będzie trudne.

Najbliższe, o czym myślę, to

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

EDYCJA : To faktycznie działa. One-liner !!

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
źródło
6
Nie zgodziłbym się - chodzi o to, że opcjonalne wiązanie jest skuteczne tylko wtedy, gdy warunek jest spełniony. (W przypadku opcji opcjonalnej oznacza to, że istnieje wartość.) Użycie if letw tym przypadku nie powoduje, że program jest bardziej złożony, a błędy nie są łatwiejsze do usunięcia. Po prostu łączy tradycyjne ifsprawdzanie granic dwóch instrukcji i faktyczne wyszukiwanie w jedno-liniową, skróconą instrukcję. Istnieją przypadki (szczególnie pracujących w interfejsie), gdzie to jest normalne dla indeksem się poza boiskiem, jak pytać NSTableViewdla selectedRowbez selekcji.
Craig Otis
3
@Mundi wydaje się to być komentarzem, a nie odpowiedzią na pytanie PO.
jlehr
1
@CraigOtis Nie jestem pewien, czy się zgadzam. Państwo może napisać ten test zwięźle w „pojedynczej linii, skrócone sprawozdanie”, na przykład za pomocą countElementslub PO zrobił z count, ale nie w sposób definiuje język pisania indeksów tablicy.
Mundi
1
@jlehr Może nie. Uczciwą grą jest kwestionowanie zamiaru lub mądrości postawionego problemu.
Mundi,
2
@Mundi Heh, zwłaszcza jeśli później go edytujesz, aby faktycznie odpowiedzieć na pytanie. :-)
jlehr
1

nilW moim przypadku użycia wstawiłem tablicę s:

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
}

Sprawdź także rozszerzenie indeksu dolnego z safe:etykietą Erica Sadun / Mike Ash: http://ericasadun.com/2015/06/01/swift-safe-array-indexing-my-favorite-thing-of-the-new-week/

Marián Černý
źródło
0

Lista „Powszechnie odrzucane zmiany” dla Swift zawiera wzmiankę o zmianie dostępu do indeksu tablicy Array w celu zwrócenia opcjonalnego zamiast zawieszania się:

Zwróć Array<T>dostęp do indeksu dolnego T?lub T!zamiast T: Bieżące zachowanie tablicy jest zamierzone , ponieważ dokładnie odzwierciedla fakt, że dostęp do tablicy poza zakresem jest błędem logicznym. Zmiana obecnego zachowania spowolniłaby Arraydostęp w niedopuszczalnym stopniu. Temat ten pojawiał się wiele razy wcześniej, ale jest bardzo mało prawdopodobne, aby został zaakceptowany.

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

Tak więc podstawowy dostęp do indeksu dolnego nie zmieni się, aby zwrócić opcjonalny.

Jednak zespół / społeczność Swift wydaje się być otwarta na dodanie do tablic nowego, opcjonalnie zwracanego wzorca dostępu, za pomocą funkcji lub indeksu dolnego.

Zostało to zaproponowane i omówione na forum Swift Evolution tutaj:

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

W szczególności Chris Lattner dał pomysł „+1”:

Zgadzam się, najczęściej sugerowaną pisownią do tego jest yourArray[safe: idx]:, co wydaje mi się świetne. Mam bardzo +1 za dodanie tego.

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

Może to być możliwe od razu po wyjęciu z pudełka w niektórych przyszłych wersjach Swift. Zachęcam każdego, kto chce, aby przyczynił się do tego wątku Szybkiej Ewolucji.

pkamb
źródło
0

Aby propagować przyczyny niepowodzenia operacji, błędy są lepsze niż opcjonalne. Indeksy dolne nie mogą generować błędów, więc musi to być metoda.

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
źródło
0

Nie jestem pewien, dlaczego nikt nie wprowadził rozszerzenia, które ma również narzędzie do ustawiania automatycznego powiększania tablicy

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
        }
    }
}

Użycie jest łatwe i działa od 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")]

Uwaga: koszta wydajności należy rozumieć (zarówno w czasie, jak i przestrzeni) przy szybkim rozwijaniu macierzy - ale w przypadku drobnych problemów czasami wystarczy, aby Swift przestał się przesuwać w stopie

Shaheen Ghiassy
źródło
-1

Zrobiłem proste rozszerzenie dla tablicy

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
        }
    }

}

działa idealnie zgodnie z przeznaczeniem

Przykład

   if let firstElemntToLoad = roots.iof(0)?.children?.iof(0)?.cNode, 
Mohamed Deux
źródło
-1

Szybkie użycie 5

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

skończyło się na, ale naprawdę chciałem zrobić ogólnie jak

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

ale ponieważ kolekcja jest kontekstowa, myślę, że jest właściwa.

slashlos
źródło
Czym różni się ta odpowiedź od przyjętej? Jak dla mnie wygląda dokładnie tak samo (duplikat).
Legonaftik
-1

Gdy potrzebujesz tylko uzyskać wartości z tablicy i nie przeszkadza ci niewielka utrata wydajności (tj. Jeśli twoja kolekcja nie jest ogromna), istnieje alternatywa oparta na słowniku, która nie wymaga (zbyt ogólna, jak dla mnie smak) rozszerzenie kolekcji:

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