Rango inverso en Swift


105

¿Hay alguna forma de trabajar con rangos inversos en Swift?

Por ejemplo:

for i in 5...1 {
  // do something
}

es un bucle infinito.

En las versiones más recientes de Swift, ese código se compila, pero en tiempo de ejecución da el error:

Error fatal: no se puede formar el rango con upperBound <lowerBound

Sé que puedo usar 1..5en su lugar, calcular j = 6 - iy usar jcomo mi índice. Me preguntaba si había algo más legible.


Vea mi respuesta para una pregunta similar que ofrece hasta 8 formas de resolver su problema con Swift 3.
Imanou Petit

Respuestas:


184

Actualización para el último Swift 3 (todavía funciona en Swift 4)

Puedes usar el reversed()método en un rango

for i in (1...5).reversed() { print(i) } // 5 4 3 2 1

O stride(from:through:by:)método

for i in stride(from:5,through:1,by:-1) { print(i) } // 5 4 3 2 1

stide(from:to:by:) es similar pero excluye el último valor

for i in stride(from:5,to:0,by:-1) { print(i) } // 5 4 3 2 1

Actualización para el último Swift 2

En primer lugar, las extensiones de protocolo cambian cómo reversese usa:

for i in (1...5).reverse() { print(i) } // 5 4 3 2 1

Stride ha sido reelaborado en Xcode 7 Beta 6. El nuevo uso es:

for i in 0.stride(to: -8, by: -2) { print(i) } // 0 -2 -4 -6
for i in 0.stride(through: -8, by: -2) { print(i) } // 0 -2 -4 -6 -8

También funciona para Doubles:

for i in 0.5.stride(to:-0.1, by: -0.1) { print(i) }

Tenga cuidado con las comparaciones de punto flotante aquí para los límites.

Edición anterior para Swift 1.2 : A partir de Xcode 6 Beta 4, by y ReverseRange ya no existen: [

Si solo está buscando invertir un rango, la función inversa es todo lo que necesita:

for i in reverse(1...5) { println(i) } // prints 5,4,3,2,1

Según lo publicado por 0x7fffffff, hay una nueva construcción de zancada que se puede usar para iterar e incrementar en números enteros arbitrarios. Apple también declaró que llegará el soporte de punto flotante.

Procedente de su respuesta:

for x in stride(from: 0, through: -8, by: -2) {
    println(x) // 0, -2, -4, -6, -8
}

for x in stride(from: 6, to: -2, by: -4) {
    println(x) // 6, 2
}

FYI Stride ha cambiado desde la beta 6
DogCoffee

1
debería ser (1...5).reverse()(como una llamada de función), actualice su respuesta. (Como solo tiene dos caracteres, no pude editar tu publicación).
Behdad

En Swift 3.0-PREVIEW-4 (y posiblemente antes), debería ser (1...5).reversed(). Además, el ejemplo de .stride()no funciona y necesita la función gratuita en su stride(from:to:by:)lugar.
davidA

2
parece que el stridecódigo para swift 3 es ligeramente incorrecto. para incluir el número 1, necesitaría usar throughopuesto a toesto:for i in stride(from:5,through:1,by:-1) { print(i) }
262Hz

4
Vale la pena señalar que reversedtiene una complejidad de O (1) porque simplemente envuelve la colección original y no requiere una iteración para construirla.
devios1

21

Hay algo preocupante sobre la asimetría de esto:

for i in (1..<5).reverse()

... en contraposición a esto:

for i in 1..<5 {

Significa que cada vez que quiero hacer un rango inverso, tengo que recordar poner el paréntesis, además tengo que escribir eso .reverse()al final, sobresaliendo como un pulgar dolorido. Esto es realmente feo en comparación con los bucles for de estilo C, que son simétricos contando hacia arriba y hacia abajo. Así que tendía a usar el estilo C para bucles. ¡Pero en Swift 2.2, los bucles for estilo C desaparecerán! Así que tuve que apresurarme reemplazando todos mis bucles for decrecientes de estilo C con esta fea .reverse()construcción, preguntándome todo el tiempo, ¿por qué diablos no hay un operador de rango inverso?

¡Pero espera! Esto es Swift, ¡¡tenemos permitido definir nuestros propios operadores !! Aquí vamos:

infix operator >>> {
    associativity none
    precedence 135
}

func >>> <Pos : ForwardIndexType where Pos : Comparable>(end:Pos, start:Pos)
    -> ReverseRandomAccessCollection<(Range<Pos>)> {
        return (start..<end).reverse()
}

Así que ahora puedo decir:

for i in 5>>>1 {print(i)} // 4, 3, 2, 1

Esto cubre solo el caso más común que ocurre en mi código, pero es de lejos el caso más común, por lo que es todo lo que necesito en este momento.

Tuve una especie de crisis interna con el operador. Me hubiera gustado usar>.. , como lo contrario de ..<, pero eso no es legal: no se puede usar un punto después de un no punto, parece. Lo consideré, ..>pero decidí que era demasiado difícil distinguirlo ..<. Lo bueno de >>>es que te grita: " abajo a!" (Por supuesto, puede pensar en otro operador. Pero mi consejo es: para la super simetría, defina <<<para hacer lo que ..<hace, y ahora tiene <<<y >>>cuáles son simétricos y fáciles de escribir).


Versión Swift 3 (Xcode 8 seed 6):

infix operator >>> : RangeFormationPrecedence
func >>><Bound>(maximum: Bound, minimum: Bound) ->
    ReversedRandomAccessCollection<CountableRange<Bound>> 
    where Bound : Comparable, Bound.Stride : Integer {
        return (minimum..<maximum).reversed()
}

Versión Swift 4 (Xcode 9 beta 3):

infix operator >>> : RangeFormationPrecedence
func >>><Bound>(maximum: Bound, minimum: Bound)
    -> ReversedRandomAccessCollection<CountableRange<Bound>>
    where Bound : Comparable & Strideable { 
        return (minimum..<maximum).reversed()
}

Versión Swift 4.2 (Xcode 10 beta 1):

infix operator >>> : RangeFormationPrecedence
func >>><Bound>(maximum: Bound, minimum: Bound)
    -> ReversedRandomAccessCollection<Range<Bound>>
    where Bound : Strideable { 
        return (minimum..<maximum).reversed()
}

Versión Swift 5 (Xcode 10.2.1):

infix operator >>> : RangeFormationPrecedence
func >>><Bound>(maximum: Bound, minimum: Bound)
    -> ReversedCollection<Range<Bound>>
    where Bound : Strideable {
        return (minimum..<maximum).reversed()
}

1
¡Vaya, el >>>operador es realmente genial! Espero que los diseñadores de Swift presten atención a cosas como esa, porque quedarse reverse()al final de las expresiones entre paréntesis se siente raro. No hay ninguna razón por la que no puedan manejar los 5>..0rangos automáticamente, porque el ForwardIndexTypeprotocolo proporciona un distanceTo()operador perfectamente razonable que puede usarse para determinar si el paso debe ser positivo o negativo.
dasblinkenlight

1
Gracias @dasblinkenlight: esto se me ocurrió porque en una de mis aplicaciones tenía muchos bucles C for (en Objective-C) que migraron a bucles for Swift de estilo C y ahora tuve que convertirlos porque bucles for estilo C se van. Esto fue simplemente aterrador, porque estos bucles representaban el núcleo de la lógica de mi aplicación, y reescribir todo corría el riesgo de romperse. Por lo tanto, quería una notación que hiciera la correspondencia con la notación de estilo C (comentada) lo más clara posible.
Matt

¡ordenado! ¡Me gusta tu obsesión por el código limpio!
2018

10

Parece que las respuestas a esta pregunta han cambiado un poco a medida que avanzamos en las betas. A partir de la beta 4, tanto la by()función como el ReversedRangetipo se han eliminado del lenguaje. Si está buscando hacer un rango inverso, sus opciones ahora son las siguientes:

1: Cree un rango de avance y luego use la reverse()función para revertirlo.

for x in reverse(0 ... 4) {
    println(x) // 4, 3, 2, 1, 0
}

for x in reverse(0 ..< 4) {
    println(x) // 3, 2, 1, 0
}

2: Utilice las nuevas stride()funciones que se agregaron en la versión beta 4, que incluye funciones para especificar los índices de inicio y finalización, así como la cantidad de iteración.

for x in stride(from: 0, through: -8, by: -2) {
    println(x) // 0, -2, -4, -6, -8
}

for x in stride(from: 6, to: -2, by: -4) {
    println(x) // 6, 2
}

Tenga en cuenta que también he incluido el nuevo operador de rango exclusivo en esta publicación. ..fue reemplazado por ..<.

Editar: De las notas de la versión de Xcode 6 beta 5 , Apple agregó la siguiente sugerencia para manejar esto:

Se ha eliminado ReverseRange; usar lazy (x ..

He aquí un ejemplo.

for i in lazy(0...5).reverse() {
    // 0, 1, 2, 3, 4, 5
}

+1 Estoy buscando una referencia lazy(0....5).reverse()y no veo las notas de la versión beta 5. ¿Conoce otras discusiones sobre este tema? Además, para su información, los números dentro de ese forbucle están al revés en su ejemplo.
Rob

1
@Rob Hmm Debería haberme imaginado que las notas de la versión beta eventualmente serían eliminadas. Cuando escribí esto, ese era el único lugar en el que había visto una referencia a lazy (), pero con mucho gusto buscaré más y actualizaré / corregiré esto cuando llegue a casa más tarde. Gracias por señalar esto.
Mick MacCallum

1
@ 0x7fffffff En su último ejemplo, el comentario dice // 0, 1, 2, 3, 4, 5pero en realidad debería invertirse. El comentario es engañoso.
Pang

5

Xcode 7, beta 2:

for i in (1...5).reverse() {
  // do something
}

3

Swift 3, 4+ : puedes hacerlo así:

for i in sequence(first: 10, next: {$0 - 1}) {

    guard i >= 0 else {
        break
    }
    print(i)
}

resultado : 10, 9, 8 ... 0

Puedes personalizarlo como quieras. Para más información lea la func sequence<T>referencia


1

Esta podría ser otra forma de hacer esto.

(1...5).reversed().forEach { print($0) }

-2

La función Reverse () se utiliza para el número inverso.

Var n: Int // Ingrese el número

Para i en 1 ... n.reverse () {Imprimir (i)}

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.