Swift Array - Sprawdź, czy istnieje indeks

140

Czy w języku Swift można sprawdzić, czy indeks istnieje w tablicy bez zgłaszania błędu krytycznego?

Miałem nadzieję, że uda mi się zrobić coś takiego:

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
}

Ale rozumiem

błąd krytyczny: indeks tablicy poza zakresem

chrishale
źródło

Odpowiedzi:

442

Elegancki sposób w Swift:

let isIndexValid = array.indices.contains(index)
Manuel
źródło
10
W prawdziwym życiu nie powinno to mieć znaczenia, ale ze względu na złożoność czasu, czy nie byłoby lepiej go użyć index < array.count?
funct7
2
Szczerze myślę, że podany przez ciebie przykład jest trochę wymyślony, ponieważ nigdy nie doświadczyłem ujemnej wartości indeksu, ale jeśli tak powinno być, jeszcze lepsze byłyby dwa testy prawda / fałsz: tj. index >= 0 && index < array.countZamiast najgorszego przypadku n porównania.
funct7
13
Jeśli zastanawiasz się, jaka jest różnica prędkości, zmierzyłem ją za pomocą narzędzi Xcode i jest ona znikoma. gist.github.com/masonmark/a79bfa1204c957043687f4bdaef0c2ad
Mason
7
Żeby dodać jeszcze jeden punkt wyjaśniający, dlaczego jest to słuszne: jeśli zastosujesz tę samą logikę podczas pracy nad ArraySlice, pierwszy indeks nie będzie wynosił 0, więc index >= 0nie będzie to wystarczająco dobre sprawdzenie. .indiceszamiast tego działa w każdym przypadku.
DeFrenZ
2
Uwielbiam za to Swifta. Oto mój przypadek użycia: budowanie gównianej struktury JSON dla usługi, więc musiałem to zrobić: "attache3": names.indices.contains (2)? nazwy [2]: ""
Lee Probert
64

Rozszerzenie typu:

extension Collection {

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

}

Używając tego, otrzymujesz opcjonalną wartość z powrotem podczas dodawania słowa kluczowego opcjonalne do swojego indeksu, co oznacza, że ​​program nie ulega awarii, nawet jeśli indeks jest poza zakresem. W twoim przykładzie:

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
źródło
4
Doskonała odpowiedź 👏 Najważniejsze, że jest czytelny podczas używania optionalparametru. Dzięki!
Jakub
2
Awesomeness: D Uratowałem mój dzień.
Codetard
2
To jest piękne
JoeGalind
32

Po prostu sprawdź, czy indeks jest mniejszy niż rozmiar tablicy:

if 2 < arr.count {
    ...
} else {
    ...
}
Antonio
źródło
3
A jeśli rozmiar tablicy jest nieznany?
Nathan McKaskle
8
@NathanMcKaskle Tablica zawsze wie, ile zawiera elementów, więc rozmiar nie może być nieznany
Antonio
16
Przepraszam, że tam nie myślałem. Poranna kawa wciąż docierała do krwiobiegu.
Nathan McKaskle
4
@NathanMcKaskle nie martw się ... czasami mi się to zdarza, nawet po kilku kawach ;-)
Antonio
W pewnym sensie jest to najlepsza odpowiedź, ponieważ działa w O (1) zamiast O (n).
ScottyBlades
12

Dodaj trochę cukru przedłużającego:

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"

Zwiększa czytelność podczas if letkodowania stylów w połączeniu z tablicami

eonista
źródło
2
szczerze mówiąc, jest to najszybszy sposób na zrobienie tego z maksymalną czytelnością i przejrzystością
barndog,
1
@barndog Też mi się podoba. Zwykle dodaję to jako Sugar w każdym projekcie, który zaczynam. Dodano również przykład strażnika. Podziękowania dla szybkiej luźnej społeczności za wymyślenie tego.
eonista
dobre rozwiązanie ... ale Output wyświetli "d" w przykładzie, który podajesz ...
jayant rawat
To powinno być częścią języka Swift. Kwestia indeksu poza zakresem jest nie mniej problematyczna niż wartości zerowe. Sugerowałbym nawet użycie podobnej składni: może coś takiego jak: myArray [? 3]. Jeśli myArray jest opcjonalne, otrzymasz myArray? [? 3]
Andy Weinstein
@Andy Weinstein Powinieneś zaproponować to Apple 💪
eonist
7

Możesz przepisać to w bezpieczniejszy sposób, aby sprawdzić rozmiar tablicy i użyć warunku trójskładnikowego:

if let str2 = (arr.count > 2 ? arr[2] : nil) as String?
dasblinkenlight
źródło
1
To, co sugeruje Antonio, jest znacznie bardziej przejrzyste (i wydajne) Tam, gdzie trójskładnik byłby odpowiedni, gdybyś miał łatwo dostępną wartość do użycia i całkowicie wyeliminował if, pozostawiając po prostu instrukcję let.
David Berry,
1
@David To, co sugeruje Antonio, wymaga dwóch ifinstrukcji zamiast jednej ifinstrukcji w oryginalnym kodzie. Mój kod zastępuje drugi ifoperator warunkowy, umożliwiając zachowanie jednego elsezamiast wymuszania dwóch oddzielnych elsebloków.
dasblinkenlight
1
Nie widzę dwóch instrukcji if w jego kodzie, wstaw let str2 = arr [1] jako jego wielokropek i gotowe. Jeśli zastąpisz OP if let instrukcję instrukcją antonio if i przeniesiesz przypisanie do środka (lub nie, ponieważ jedynym zastosowaniem str2 jest wydrukowanie go, nie ma żadnej potrzeby, po prostu umieść dereferencję w linii w println. że operator trójskładnikowy jest po prostu niejasnym (dla niektórych :)) if / else. Jeśli arr. count> 2, to arr [2] nigdy nie może być zero, więc po co mapować go na String? tylko po to, abyś mógł zastosować kolejne stwierdzenie if.
David Berry,
@David Całe ifpytanie z OP trafi do gałęzi „then” odpowiedzi Antonio, więc będą dwa zagnieżdżone pytania if. Traktuję kod OP jako mały przykład, więc zakładam, że nadal chciałby mieć if. Zgadzam się z Tobą, że w jego przykładzie ifnie jest to konieczne. Ale z drugiej strony cała instrukcja jest bezcelowa, ponieważ OP wie, że tablica nie ma wystarczającej długości i żaden z jej elementów nie ma nil, więc mógł usunąć ifi zachować tylko jej elseblok.
dasblinkenlight,
Nie, nie byłoby. Antonio's if zastępuje instrukcję if OP. Ponieważ typ tablicy to [String], wiesz, że nigdy nie może zawierać zera, więc nie musisz sprawdzać niczego poza długością.
David Berry,
7

Rozszerzenie Swift 4:

Ja wolę metodę podobną.

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

Dzięki @Benno Kress

YannSteph
źródło
1

Potwierdzanie, czy istnieje indeks tablicy:

Ta metodologia jest świetna, jeśli nie chcesz dodawać cukru do przedłużania:

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

}

Obsługa indexOutOfBounds to praca dla mnie .

Pratik Sodha
źródło
A co jeśli indeks jest ujemny? Prawdopodobnie powinieneś również to sprawdzić.
Frankie Simon