Swift Array - Vérifiez si un index existe

140

Dans Swift, existe-t-il un moyen de vérifier si un index existe dans un tableau sans qu'une erreur fatale ne soit générée?

J'espérais pouvoir faire quelque chose comme ça:

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
}

Mais je reçois

erreur fatale: index de tableau hors limites

chrishale
la source

Réponses:

442

Une manière élégante dans Swift:

let isIndexValid = array.indices.contains(index)
Manuel
la source
10
Dans la vraie vie, cela ne devrait pas avoir d'importance, mais en termes de complexité temporelle, ne serait-il pas beaucoup mieux à utiliser index < array.count?
funct7
2
Je pense honnêtement que l'exemple que vous avez donné est un peu artificiel car je n'ai jamais vu une valeur d'index négative, mais si cela devait vraiment être le cas, deux vérifications vrai / faux seraient encore meilleures: c'est-à-dire index >= 0 && index < array.countau lieu que le pire des cas soit n comparaisons.
funct7
13
Au cas où vous vous demandez quelle est la différence de vitesse, je l'ai mesurée avec les outils de Xcode et c'est négligeable. gist.github.com/masonmark/a79bfa1204c957043687f4bdaef0c2ad
Mason
7
Juste pour ajouter un autre point sur la raison pour laquelle cela est juste: si vous appliquez la même logique lorsque vous travaillez sur un ArraySlice, le premier index ne sera pas 0, donc ce index >= 0ne sera pas une vérification suffisante. .indicesau lieu de cela fonctionne dans tous les cas.
DeFrenZ
2
J'adore Swift pour ça. Voici mon cas d'utilisation: construire une structure JSON merdique pour le service, donc je devais faire ceci: "participant3": names.indices.contains (2)? names [2]: ""
Lee Probert
64

Extension de type:

extension Collection {

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

}

En utilisant cela, vous obtenez une valeur facultative lorsque vous ajoutez le mot-clé facultatif à votre index, ce qui signifie que votre programme ne plante pas même si l'index est hors de portée. Dans votre exemple:

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
la source
4
Excellente réponse 👏 Le plus important, il est lisible lors de l'utilisation optionalen paramètre. Merci!
Jakub
2
Awesomeness: D a sauvé ma journée.
Codetard le
2
C'est beau
JoeGalind
32

Vérifiez simplement si l'index est inférieur à la taille du tableau:

if 2 < arr.count {
    ...
} else {
    ...
}
Antonio
la source
3
Que faire si la taille du tableau est inconnue?
Nathan McKaskle
8
@NathanMcKaskle Le tableau sait toujours combien d'éléments il contient, donc la taille ne peut pas être inconnue
Antonio
16
Désolé, je n'y pensais pas. Le café du matin entrait toujours dans la circulation sanguine.
Nathan McKaskle
4
@NathanMcKaskle pas de soucis ... parfois cela m'arrive, même après plusieurs cafés ;-)
Antonio
D'une certaine manière, c'est la meilleure réponse, car elle s'exécute dans O (1) au lieu de O (n).
ScottyBlades
12

Ajoutez du sucre d'extension:

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"

Améliore la lisibilité lors du if letcodage de style en conjonction avec des tableaux

éoniste
la source
2
Honnêtement, c'est le moyen le plus rapide de le faire avec un maximum de lisibilité et de clarté
barndog
1
@barndog J'adore ça aussi. J'ajoute généralement ceci comme Sugar dans n'importe quel projet que je commence. Ajout d'un exemple de garde également. Nous remercions la communauté Swift Slack d'avoir créé celui-ci.
eonist
bonne solution ... mais Output affichera "d" dans l'exemple que vous donnez ...
jayant rawat le
Cela devrait faire partie du langage Swift. La question de l'indice hors limites n'est pas moins problématique que les valeurs nulles. Je suggérerais même d'utiliser une syntaxe similaire: peut-être quelque chose comme: myArray [? 3]. Si myArray est facultatif, vous obtiendrez myArray? [? 3]
Andy Weinstein
@Andy Weinstein Vous devriez le proposer à Apple 💪
eonist
7

Vous pouvez réécrire ceci de manière plus sûre pour vérifier la taille du tableau et utiliser une condition ternaire:

if let str2 = (arr.count > 2 ? arr[2] : nil) as String?
dasblinkenlight
la source
1
Ce qu'Antonio suggère est beaucoup plus transparent (et efficace) Là où le ternaire serait approprié, c'est si vous aviez une valeur facilement disponible à utiliser et d'éliminer complètement le if, en laissant simplement une instruction let.
David Berry
1
@David Ce qu'Antonio suggère nécessite deux ifinstructions au lieu d'une ifinstruction dans le code d'origine. Mon code remplace le second ifpar un opérateur conditionnel, vous permettant de garder un seul elseau lieu de forcer deux elseblocs séparés .
dasblinkenlight
1
Je ne vois pas deux instructions if dans son code, mettez let str2 = arr [1] pour ses points de suspension et c'est fait. Si vous remplacez l'instruction OP if let par l'instruction if d'Antonio, et que vous déplacez l'affectation à l'intérieur (ou non, puisque la seule utilisation de str2 est de l'imprimer, ce n'est pas du tout nécessaire, mettez simplement la déréférence en ligne dans println. que l'opérateur ternaire est juste un obscur (pour certains :)) if / else. Si arr. count> 2, alors arr [2] ne peut jamais être nul, alors pourquoi le mapper à String? juste pour que vous puissiez appliquer une autre instruction if.
David Berry
@David La totalité ifde la question de OP se retrouvera dans la branche "alors" de la réponse d'Antonio, donc il y aurait deux ifs imbriqués . Je considère le code OP comme un petit exemple, donc je suppose qu'il voudrait toujours un fichier if. Je suis d'accord avec vous que dans son exemple ifn'est pas nécessaire. Mais là encore, l'instruction entière est inutile, car OP sait que le tableau n'a pas assez de longueur, et qu'aucun de ses éléments ne l'est nil, donc il pourrait supprimer le ifet ne conserver que son elsebloc.
dasblinkenlight
Non, ce ne serait pas le cas. Antonio's if remplace l'instruction OP's if. Puisque le type de tableau est [String], vous savez qu'il ne peut jamais contenir de nil, vous n'avez donc pas besoin de vérifier quoi que ce soit au-delà de la longueur.
David Berry
7

Extension Swift 4:

Pour moi, je préfère comme méthode.

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

Merci à @Benno Kress

YannSteph
la source
1

Affirmer si un index de tableau existe:

Cette méthodologie est idéale si vous ne souhaitez pas ajouter de sucre d'extension:

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
éoniste
la source
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
    }

}

C'est un travail pour moi de gérer indexOutOfBounds .

Pratik Sodha
la source
Et si l'indice est négatif? Vous devriez probablement vérifier cela aussi.
Frankie Simon