Gamma inversa in Swift


105

C'è un modo per lavorare con gli intervalli inversi in Swift?

Per esempio:

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

è un ciclo infinito.

Nelle versioni più recenti di Swift tale codice viene compilato, ma in fase di esecuzione restituisce l'errore:

Errore irreversibile: impossibile formare un intervallo con upperBound <lowerBound

So che posso 1..5invece usare , calcolare j = 6 - ie usare jcome indice. Mi stavo solo chiedendo se ci fosse qualcosa di più leggibile?


Vedi la mia risposta per una domanda simile che offre fino a 8 modi per risolvere il tuo problema con Swift 3.
Imanou Petit

Risposte:


184

Aggiorna per l'ultimo Swift 3 (funziona ancora in Swift 4)

Puoi usare il reversed()metodo su un intervallo

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

O stride(from:through:by:)metodo

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

stide(from:to:by:) è simile ma esclude l'ultimo valore

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

Aggiorna per l'ultima versione di Swift 2

Prima di tutto, le estensioni del protocollo cambiano il modo in cui reversevengono utilizzate:

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

Stride è stato rielaborato in Xcode 7 Beta 6. Il nuovo utilizzo è:

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

Funziona anche per Doubles:

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

Diffidare dei confronti in virgola mobile qui per i limiti.

Modifica precedente per Swift 1.2 : a partire da Xcode 6 Beta 4, di e ReverseRange non esistono più: [

Se stai solo cercando di invertire un intervallo, la funzione di inversione è tutto ciò di cui hai bisogno:

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

Come pubblicato da 0x7fffffff, c'è un nuovo costrutto stride che può essere usato per iterare e incrementare di interi arbitrari. Apple ha anche affermato che il supporto in virgola mobile è in arrivo.

Tratto dalla sua risposta:

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
}

Cordiali saluti, Stride è cambiato dalla beta 6
DogCoffee

1
dovrebbe essere (1...5).reverse()(come una chiamata di funzione), aggiorna la tua risposta. (Dato che sono solo due caratteri, non ho potuto modificare il tuo post.)
Behdad,

In Swift 3.0-PREVIEW-4 (e forse anche prima), dovrebbe essere (1...5).reversed(). Inoltre, l'esempio di .stride()non funziona e necessita stride(from:to:by:)invece della funzione gratuita .
davidA

2
sembra che il stridecodice per swift 3 sia leggermente errato. per includere il numero 1, dovresti usare il throughcontrario di tocome questo:for i in stride(from:5,through:1,by:-1) { print(i) }
262Hz

4
Vale la pena sottolineare che reversedha una complessità di O (1) perché avvolge semplicemente la raccolta originale e non richiede un'iterazione per costruirla.
devios1

21

C'è qualcosa di preoccupante nell'asimmetria di questo:

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

... al contrario di questo:

for i in 1..<5 {

Significa che ogni volta che voglio fare un intervallo inverso, devo ricordarmi di mettere le parentesi, in più devo scriverlo .reverse()alla fine, sporgendo come un pollice dolorante. Questo è davvero brutto rispetto ai cicli for in stile C, che sono simmetrici conteggio in avanti e conto alla rovescia. Quindi ho preferito usare lo stile C per i loop. Ma in Swift 2.2, i cicli for in stile C non saranno più disponibili! Quindi ho dovuto affrettarmi a sostituire tutto il mio decremento di cicli C in stile con questo brutto .reverse()costrutto, chiedendomi per tutto il tempo, perché mai non c'è un operatore di intervallo inverso?

Ma aspetta! Questo è Swift: siamo autorizzati a definire i nostri operatori !! Eccoci qui:

infix operator >>> {
    associativity none
    precedence 135
}

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

Quindi ora posso dire:

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

Questo copre solo il caso più comune che si verifica nel mio codice, ma è di gran lunga il caso più comune, quindi è tutto ciò di cui ho bisogno al momento.

Ho avuto una specie di crisi interna in arrivo con l'operatore. Mi sarebbe piaciuto usare >.., come se fosse il contrario di ..<, ma non è legale: non puoi usare un punto dopo un non-punto, a quanto pare. Ho considerato, ..>ma ho deciso che era troppo difficile distinguerlo ..<. La cosa bella >>>è che ti urla: " fino a!" (Ovviamente sei libero di trovare un altro operatore. Ma il mio consiglio è: per la super simmetria, definisci <<<cosa fare..< fa, e ora hai <<<e >>>quali sono simmetrici e facili da digitare.)


Versione 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()
}

Versione 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()
}

Versione 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()
}

Versione 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
Wow, l' >>>operatore è davvero fantastico! Spero che i designer di Swift prestino attenzione a cose del genere, perché attaccarsi reverse()alla fine delle espressioni tra parentesi sembra strano. Non c'è motivo per cui non possano gestire gli 5>..0intervalli automaticamente, perché il ForwardIndexTypeprotocollo fornisce un distanceTo()operatore perfettamente ragionevole che può essere utilizzato per capire se il passo deve essere positivo o negativo.
dasblinkenlight

1
Grazie @dasblinkenlight - Questo mi è venuto in mente perché in una delle mie app avevo molti cicli C per (in Objective-C) che sono migrati allo stile C di Swift per i cicli e ora dovevano essere convertiti perché lo stile C per i cicli stanno andando via. Questo era semplicemente terrificante, perché questi loop rappresentavano il nucleo della logica della mia app e riscrivere tutto rischiava di rompersi. Quindi volevo una notazione che rendesse la corrispondenza con la notazione in stile C (commentata) il più chiara possibile.
matt

! pulito Mi piace la tua ossessione per il codice dall'aspetto pulito!
Yohst

10

Sembra che le risposte a questa domanda siano leggermente cambiate man mano che procediamo attraverso i beta. A partire dalla beta 4, sia la by()funzione che ilReversedRange tipo sono stati rimossi dalla lingua. Se stai cercando di creare un intervallo invertito, le tue opzioni sono ora le seguenti:

1: crea un intervallo in avanti, quindi utilizza la reverse()funzione per invertirlo.

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: utilizza le nuove stride()funzioni aggiunte nella beta 4, che includono funzioni per specificare gli indici di inizio e di fine, nonché la quantità di cui eseguire l'iterazione.

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
}

Nota che ho incluso anche il nuovo operatore di range esclusivo in questo post. ..è stato sostituito con ..<.

Modifica: dalle note di rilascio di Xcode 6 beta 5 , Apple ha aggiunto il seguente suggerimento per gestirlo:

ReverseRange è stato rimosso; usa pigro (x ..

Ecco un esempio.

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

+1 Sto cercando un riferimento a lazy(0....5).reverse()e non vedo le note sulla versione beta 5. Conosci altre discussioni su questo argomento? Inoltre, FYI, i numeri all'interno di quel forciclo sono all'indietro nel tuo esempio.
Rob

1
@ Rob Hmm avrei dovuto immaginare che le note di rilascio per una beta alla fine sarebbero state rimosse. Quando ho scritto questo, quello era l'unico posto in cui avevo visto un riferimento a lazy (), ma cercherò volentieri ancora un po 'e aggiornerò / correggerò questo quando torno a casa più tardi. Grazie per averlo fatto notare.
Mick MacCallum

1
@ 0x7fffffff Nel tuo ultimo esempio, il commento dice // 0, 1, 2, 3, 4, 5ma in realtà dovrebbe essere invertito. Il commento è fuorviante.
Pang

5

Xcode 7, beta 2:

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

3

Swift 3, 4+ : puoi farlo in questo modo:

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

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

risultato : 10, 9, 8 ... 0

Puoi personalizzarlo come preferisci. Per maggiori informazioni leggi il func sequence<T>riferimento


1

Questo potrebbe essere un altro modo per farlo.

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

-2

La funzione Reverse () viene utilizzata per il numero inverso.

Var n: Int // Immettere il numero

For i in 1 ... n.reverse () {Print (i)}

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.