Swift Array: comprueba si existe un índice

140

En Swift, ¿hay alguna forma de verificar si existe un índice en una matriz sin que se genere un error fatal?

Esperaba poder hacer algo como esto:

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
}

Pero consigo

error fatal: índice de matriz fuera de rango

chrishale
fuente

Respuestas:

442

Una manera elegante en Swift:

let isIndexValid = array.indices.contains(index)
Manuel
fuente
10
En la vida real, no debería importar, pero en cuanto a la complejidad del tiempo, ¿no sería mucho mejor usarlo index < array.count?
funct7
2
Sinceramente, creo que el ejemplo que diste es un poco artificial, ya que nunca he experimentado tener un valor de índice negativo, pero si ese fuera el caso, dos comprobaciones verdaderas / falsas serían aún mejores: es decir, en index >= 0 && index < array.countlugar de que el peor de los casos sea n comparaciones
funct7
13
En caso de que se pregunte cuál es la diferencia de velocidad, la medí con las herramientas de Xcode y es insignificante. gist.github.com/masonmark/a79bfa1204c957043687f4bdaef0c2ad
Mason
77
Solo para agregar otro punto sobre por qué esto es correcto: si aplica la misma lógica cuando trabaja en un ArraySlice, el primer índice no será 0, por lo que hacerlo index >= 0no será una verificación lo suficientemente buena. .indicesen cambio funciona en cualquier caso.
DeFrenZ
2
Amo a Swift por esto. Aquí está mi caso de uso: construir una estructura JSON de mierda para el servicio, así que tuve que hacer esto: "attendee3": names.indices.contains (2)? nombres [2]: ""
Lee Probert
64

Tipo de extensión:

extension Collection {

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

}

Con esto, obtiene un valor opcional cuando agrega la palabra clave opcional a su índice, lo que significa que su programa no se bloquea incluso si el índice está fuera de rango. En tu ejemplo:

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
fuente
44
Excelente respuesta 👏 Lo más importante es que se puede leer mientras se usa optionalen el parámetro. ¡Gracias!
Jakub
2
Awesomeness: D me salvó el día.
Codetard
2
Esto es hermoso
JoeGalind
32

Simplemente verifique si el índice es menor que el tamaño de la matriz:

if 2 < arr.count {
    ...
} else {
    ...
}
Antonio
fuente
3
¿Qué pasa si el tamaño de la matriz es desconocido?
Nathan McKaskle
8
@NathanMcKaskle La matriz siempre sabe cuántos elementos contiene, por lo que el tamaño no puede ser desconocido
Antonio
16
Lo siento, no estaba pensando allí. El café de la mañana todavía entraba en el torrente sanguíneo.
Nathan McKaskle
44
@NathanMcKaskle no te preocupes ... a veces eso me pasa, incluso después de varios cafés ;-)
Antonio
De alguna manera, esta es la mejor respuesta, porque se ejecuta en O (1) en lugar de O (n).
ScottyBlades
12

Agregue un poco de azúcar de extensión:

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"

Mejora la legibilidad al hacer if letcodificación de estilo junto con matrices

eonista
fuente
2
honestamente, esta es la forma más rápida de hacerlo con la máxima legibilidad y claridad
barndog
1
@barndog Me encanta también. Por lo general, agrego esto como Sugar en cualquier proyecto que comience. Añadido ejemplo de guardia también. Gracias a la comunidad floja y veloz por haber creado este.
eonista
buena solución ... pero Salida imprimirá "d" en el ejemplo que está dando ...
jayant rawat
Esto debería ser parte del lenguaje Swift. La cuestión del índice fuera de rango no es menos problemática que los valores nulos. Incluso sugeriría usar una sintaxis similar: tal vez algo como: myArray [? 3]. Si myArray es opcional, obtendrías myArray? [? 3]
Andy Weinstein
@Andy Weinstein Deberías proponerlo a Apple 💪
eonista
7

Puede reescribir esto de una manera más segura para verificar el tamaño de la matriz y usar un condicional ternario:

if let str2 = (arr.count > 2 ? arr[2] : nil) as String?
dasblinkenlight
fuente
1
Lo que Antonio sugiere es mucho más transparente (y eficiente) Donde el ternario sería apropiado si tuviera un valor fácilmente disponible para usar y eliminar el if por completo, solo dejando una declaración let.
David Berry
1
@David Lo que Antonio sugiere requiere dos ifdeclaraciones en lugar de una ifdeclaración en el código original. Mi código reemplaza el segundo ifcon un operador condicional, lo que le permite mantener uno solo en elselugar de forzar dos elsebloques separados .
dasblinkenlight
1
No veo dos declaraciones if en su código, ponga let str2 = arr [1] para sus puntos suspensivos y listo. Si reemplaza la declaración OP if let con la declaración if de antonio, y mueve la asignación dentro (o no, ya que el único uso de str2 es imprimirla, no hay necesidad, simplemente coloque la desreferencia en línea en println. Sin mencionar que el operador ternario es solo un oscuro (para algunos :)) si / si no. Si arr. cuenta> 2, luego arr [2] nunca puede ser nulo, entonces ¿por qué asignarlo a String? solo para que puedas aplicar otra declaración if.
David Berry
@David La pregunta completa ifde OP terminará dentro de la rama "entonces" de la respuesta de Antonio, por lo que habría dos ifs anidados . Estoy viendo el código de OP como un pequeño ejemplo, así que supongo que todavía querría un if. Estoy de acuerdo con usted en que en su ejemplo ifno es necesario. Pero, de nuevo, toda la declaración no tiene sentido, porque OP sabe que la matriz no tiene suficiente longitud, y que ninguno de sus elementos lo es nil, por lo que podría eliminar ify mantener solo su elsebloque.
dasblinkenlight
No, no lo haría. Antonio's if reemplaza el OP's if declaración. Como el tipo de matriz es [Cadena], sabe que nunca puede contener un valor nulo, por lo que no necesita verificar nada más allá de la longitud.
David Berry el
7

Extensión Swift 4:

Para mí prefiero como método.

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

Gracias a @Benno Kress

YannSteph
fuente
1

Afirmando si existe un índice de matriz:

Esta metodología es excelente si no desea agregar azúcar de extensión:

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

}

Es trabajo para mí manejar indexOutOfBounds .

Pratik Sodha
fuente
¿Qué pasa si el índice es negativo? Probablemente deberías comprobar eso también.
Frankie Simon