Swift Array: controlla se esiste un indice


140

In Swift, c'è un modo per verificare se esiste un indice in un array senza che venga generato un errore fatale?

Speravo di poter fare qualcosa del genere:

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
}

Ma capisco

errore irreversibile: indice di array fuori intervallo

Risposte:


442

Un modo elegante in Swift:

let isIndexValid = array.indices.contains(index)

10
Nella vita reale, non dovrebbe importare ma in termini di complessità temporale non sarebbe molto meglio usarlo index < array.count?
funct7,

2
Sinceramente penso che l'esempio che hai dato sia un po 'inventato poiché non ho mai avuto un valore di indice negativo, ma se così fosse, due verifiche veritiere / false sarebbero ancora migliori: cioè index >= 0 && index < array.countinvece del caso peggiore essendo n confronti.
funct7,

13
Nel caso ti chiedi quale sia la differenza di velocità, l'ho misurata con gli strumenti di Xcode ed è trascurabile. gist.github.com/masonmark/a79bfa1204c957043687f4bdaef0c2ad
Mason

7
Solo per aggiungere un altro punto sul perché questo è giusto: se si applica la stessa logica quando si lavora su un ArraySlice, il primo indice non sarà 0, quindi fare index >= 0un controllo abbastanza buono. .indicesinvece funziona in ogni caso.
DeFrenZ,

2
Adoro Swift per questo. Ecco il mio caso d'uso: costruire una struttura JSON di merda per il servizio, quindi ho dovuto fare questo: "attendee3": names.indices.contains (2)? names [2]: ""
Lee Probert

64

Estensione del tipo:

extension Collection {

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

}

Usando questo ottieni un valore opzionale quando aggiungi la parola chiave opzionale al tuo indice, il che significa che il tuo programma non si arresta in modo anomalo anche se l'indice non è compreso nell'intervallo. Nel tuo esempio:

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
}

4
Risposta eccellente 👏 Soprattutto è leggibile mentre si utilizza optionalnel parametro. Grazie!
Jakub,

2
Awesomeness: D Mi ha salvato la giornata.
Codetard,

2
È bellissimo
Joe Galind,

32

Basta controllare se l'indice è inferiore alla dimensione dell'array:

if 2 < arr.count {
    ...
} else {
    ...
}

3
Cosa succede se la dimensione dell'array è sconosciuta?
Nathan McKaskle,

8
@NathanMcKaskle L'array sa sempre quanti elementi contiene, quindi la dimensione non può essere sconosciuta
Antonio

16
Mi dispiace non stavo pensando lì. Il caffè del mattino stava ancora entrando nel flusso sanguigno.
Nathan McKaskle,

4
@NathanMcKaskle non preoccuparti ... a volte mi capita, anche dopo diversi caffè ;-)
Antonio,

In un certo senso, questa è la risposta migliore, perché viene eseguita in O (1) anziché in O (n).
ScottyBlades,

12

Aggiungi un po 'di zucchero di estensione:

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"

Migliora la leggibilità quando si esegue la if letcodifica di stile in combinazione con le matrici


2
onestamente, questo è il modo più veloce di farlo con la massima leggibilità e chiarezza
barndog

1
@barndog Lo adoro anche io. Di solito aggiungo questo come Sugar in qualsiasi progetto che inizio. Aggiunto anche esempio di guardia. Ringraziamo la rapida e rilassata comunità per aver inventato questo.
eonista,

buona soluzione ... ma Output stamperà "d" nell'esempio che stai dando ...
jayant rawat il

Questo dovrebbe far parte del linguaggio Swift. Il problema dell'indice fuori intervallo non è meno problematico dei valori zero. Suggerirei anche di usare una sintassi simile: forse qualcosa del tipo: myArray [? 3]. Se myArray è facoltativo, otterresti myArray? [? 3]
Andy Weinstein,

@Andy Weinstein Dovresti proporlo a Apple 💪
eonista

7

È possibile riscriverlo in un modo più sicuro per verificare le dimensioni dell'array e utilizzare un condizionale ternario:

if let str2 = (arr.count > 2 ? arr[2] : nil) as String?

1
Ciò che Antonio suggerisce è molto più trasparente (ed efficiente) dove il ternario sarebbe appropriato se tu avessi un valore prontamente disponibile da usare ed eliminare il se del tutto, lasciando solo un'istruzione let.
David Berry,

1
@David Quello che Antonio suggerisce richiede due ifaffermazioni invece di una ifistruzione nel codice originale. Il mio codice sostituisce il secondo ifcon un operatore condizionale, permettendoti di mantenere un singolo elseinvece di forzare due elseblocchi separati .
dasblinkenlight,

1
Non vedo due istruzioni if ​​nel suo codice, metti let str2 = arr [1] in per i suoi puntini di sospensione ed è fatta. Se si sostituisce l'istruzione if if let con l'istruzione if di antonio e si sposta il compito all'interno (o no, poiché l'unico uso di str2 è di stamparlo, non è necessario, basta inserire la dereference in linea nel println. che l'operatore ternario è solo un oscuro (per alcuni :)) se / altrimenti. Se arr. count> 2, quindi arr [2] non può mai essere nullo, quindi perché mapparlo su String? solo così puoi applicare ancora un'altra istruzione if.
David Berry,

@David L'intera ifdomanda dell'OP finirà nel ramo "allora" della risposta di Antonio, quindi ci sarebbero due ifs nidificati . Sto vedendo il codice OP come un piccolo esempio, quindi presumo che vorrebbe ancora un if. Concordo con te sul fatto che nel suo esempio ifnon è necessario. Ma ancora una volta, l'intera istruzione è inutile, perché OP sa che l'array non ha una lunghezza sufficiente e che nessuno dei suoi elementi lo è nil, quindi potrebbe rimuovere ife mantenere solo il suo elseblocco.
dasblinkenlight,

No non lo farebbe. Antonio's if sostituisce la dichiarazione if del PO. Poiché il tipo di array è [String], sai che non può mai contenere uno zero, quindi non è necessario controllare nulla oltre la lunghezza.
David Berry,

7

Estensione Swift 4:

Per me preferisco il metodo simile.

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

Grazie a @Benno Kress


1

Asserire se esiste un indice di array:

Questa metodologia è ottima se non si desidera aggiungere l'estensione di zucchero:

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

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
    }

}

Per me è compito gestire indexOutOfBounds .


Cosa succede se l'indice è negativo? Probabilmente dovresti verificare anche quello.
Frankie Simon,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.