Reverse Range in Swift


105

Gibt es eine Möglichkeit, in Swift mit umgekehrten Bereichen zu arbeiten?

Beispielsweise:

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

ist eine Endlosschleife.

In neueren Versionen von Swift wird dieser Code kompiliert, aber zur Laufzeit wird der Fehler ausgegeben:

Schwerwiegender Fehler: Bereich kann nicht mit UpperBound <LowerBound gebildet werden

Ich weiß, dass ich 1..5stattdessen verwenden, berechnen j = 6 - iund jals meinen Index verwenden kann. Ich habe mich nur gefragt, ob es etwas Lesbareres gibt.


Siehe meine Antwort für eine ähnliche Frage, die bis zu 8 Möglichkeiten bietet, um Ihr Problem mit Swift 3 zu lösen.
Imanou Petit

Antworten:


184

Update Für das neueste Swift 3 (funktioniert immer noch in Swift 4)

Sie können die reversed()Methode für einen Bereich verwenden

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

Oder stride(from:through:by:)Methode

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

stide(from:to:by:) ist ähnlich, schließt jedoch den letzten Wert aus

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

Update Für den neuesten Swift 2

Zunächst ändern Protokollerweiterungen die reverseVerwendung:

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

Stride wurde in Xcode 7 Beta 6 überarbeitet. Die neue Verwendung lautet:

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

Es funktioniert auch für Doubles:

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

Seien Sie vorsichtig bei Gleitkomma-Vergleichen hier für die Grenzen.

Frühere Bearbeitung für Swift 1.2 : Ab Xcode 6 Beta 4 existieren by und ReverseRange nicht mehr: [

Wenn Sie nur einen Bereich umkehren möchten, benötigen Sie lediglich die Umkehrfunktion:

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

Wie gepostet 0x7fffffff gibt es ein neues Stride - Konstrukt , das zu wiederholen und Schritt durch beliebige ganze Zahlen verwendet werden kann. Apple gab auch an, dass Gleitkomma-Unterstützung kommt.

Aus seiner Antwort entnommen:

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 hat sich seit Beta 6
DogCoffee

1
es sollte sein (1...5).reverse()(als Funktionsaufruf), bitte aktualisieren Sie Ihre Antwort. (Da es nur zwei Zeichen sind, konnte ich Ihren Beitrag nicht bearbeiten.)
Behdad

In Swift 3.0-PREVIEW-4 (und möglicherweise früher) sollte dies der Fall sein (1...5).reversed(). Außerdem .stride()funktioniert das Beispiel für nicht und benötigt stride(from:to:by:)stattdessen die freie Funktion .
DavidA

2
Es sieht so aus, als ob der strideCode für Swift 3 etwas falsch ist. throughtofor i in stride(from:5,through:1,by:-1) { print(i) }
Um

4
Es ist erwähnenswert, dass reversedO (1) eine Komplexität aufweist, da es lediglich die ursprüngliche Sammlung umschließt und keine Iteration erfordert, um sie zu erstellen.
Devios1

21

Die Asymmetrie hat etwas Beunruhigendes:

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

... im Gegensatz dazu:

for i in 1..<5 {

Das bedeutet, dass ich jedes Mal, wenn ich einen umgekehrten Bereich machen möchte, daran denken muss, die Klammern zu setzen, und dass ich das .reverse()am Ende schreiben muss, das wie ein schmerzender Daumen hervorsteht. Dies ist wirklich hässlich im Vergleich zum C-Stil für Schleifen, die symmetrisch hoch- und runterzählen. Daher habe ich stattdessen C-Style für Loops verwendet. Aber in Swift 2.2 verschwinden die C-Style-for-Loops! Also musste ich herumhasten, um all meinen dekrementierenden C-Stil für Schleifen durch dieses hässliche .reverse()Konstrukt zu ersetzen - und mich die ganze Zeit fragen, warum um alles in der Welt gibt es keinen Reverse-Range-Operator?

Aber warte! Das ist Swift - wir dürfen unsere eigenen Operatoren definieren !! Auf geht's:

infix operator >>> {
    associativity none
    precedence 135
}

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

Jetzt darf ich also sagen:

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

Dies deckt nur den häufigsten Fall ab, der in meinem Code auftritt, aber es ist bei weitem der häufigste Fall, also ist es alles, was ich derzeit brauche.

Ich hatte eine Art interne Krise mit dem Betreiber. Ich hätte es gerne >..als Umkehrung verwendet ..<, aber das ist nicht legal: Sie können keinen Punkt nach einem Nicht-Punkt verwenden, wie es scheint. Ich überlegte ..>, entschied aber, dass es zu schwer war, mich davon zu unterscheiden ..<. Das Schöne daran >>>ist, dass es dich anschreit: " runter zu!" (Natürlich können Sie sich einen anderen Operator einfallen lassen. Aber mein Rat lautet: Definieren Sie <<<für Supersymmetrie, was zu tun ..<ist, und jetzt haben Sie <<<und >>>welche sind symmetrisch und einfach zu tippen.)


Swift 3-Version (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()
}

Swift 4-Version (Xcode 9 Beta 3):

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

Swift 4.2 Version (Xcode 10 Beta 1):

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

Swift 5-Version (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, der >>>Operator ist echt cool! Ich hoffe, dass Swift-Designer auf solche Dinge achten, denn reverse()es fühlt sich komisch an, am Ende von Ausdrücken in Klammern zu bleiben . Es gibt keinen Grund, warum sie nicht 5>..0automatisch mit Bereichen umgehen könnten , da das ForwardIndexTypeProtokoll einen absolut vernünftigen distanceTo()Operator bietet , mit dem herausgefunden werden kann, ob der Schritt positiv oder negativ sein sollte.
Dasblinkenlight

1
Danke @dasblinkenlight - Dies kam für mich, weil ich in einer meiner Apps viel C für Schleifen (in Objective-C) hatte, die für Schleifen in den Swift C-Stil migriert wurden und jetzt konvertiert werden mussten, weil für Schleifen C-Stil gehen weg. Das war einfach furchterregend, weil diese Schleifen den Kern der Logik meiner App darstellten und das Umschreiben alles riskierte, was zu einem Bruch führte. Daher wollte ich eine Notation, die die Entsprechung mit der (auskommentierten) C-Notation so klar wie möglich macht.
Matt

ordentlich! Ich mag deine Besessenheit mit sauber aussehendem Code!
Yohst

10

Es scheint, dass sich die Antworten auf diese Frage im Laufe der Betas etwas geändert haben. Ab Beta 4 wurden sowohl die by()Funktion als auch der ReversedRangeTyp aus der Sprache entfernt. Wenn Sie einen umgekehrten Bereich erstellen möchten, haben Sie jetzt folgende Möglichkeiten:

1: Erstellen Sie einen Vorwärtsbereich und verwenden Sie die reverse()Funktion, um ihn umzukehren.

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: Verwenden Sie die neuen stride()Funktionen, die in Beta 4 hinzugefügt wurden. Dazu gehören Funktionen zum Angeben des Start- und Endindex sowie des Betrags, um den iteriert werden soll.

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
}

Beachten Sie, dass ich auch den neuen exklusiven Bereichsoperator in diesen Beitrag aufgenommen habe. ..wurde ersetzt durch ..<.

Bearbeiten: In den Versionshinweisen zu Xcode 6 Beta 5 hat Apple den folgenden Vorschlag hinzugefügt, um damit umzugehen:

ReverseRange wurde entfernt; benutze faul (x ..

Hier ist ein Beispiel.

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

+1 Ich suche nach einem Verweis auf lazy(0....5).reverse()und sehe die Beta 5-Versionshinweise nicht. Kennen Sie andere Diskussionen zu diesem Thema? Außerdem sind die Zahlen in dieser forSchleife in Ihrem Beispiel rückwärts.
Rob

1
@ Rob Hmm Ich hätte mir vorstellen sollen, dass die Versionshinweise für eine Beta irgendwann entfernt werden. Als ich das schrieb, war dies der einzige Ort, an dem ich einen Verweis auf faul () gesehen hatte, aber ich werde mich gerne nach weiteren umsehen und dies aktualisieren / korrigieren, wenn ich später nach Hause komme. Vielen Dank für den Hinweis.
Mick MacCallum

1
@ 0x7fffffff In Ihrem letzten Beispiel heißt // 0, 1, 2, 3, 4, 5es im Kommentar, aber es sollte eigentlich umgekehrt werden. Der Kommentar ist irreführend.
Pang

5

Xcode 7, Beta 2:

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

3

Swift 3, 4+ : Sie können es so machen:

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

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

Ergebnis : 10, 9, 8 ... 0

Sie können es beliebig anpassen. Weitere Informationen finden Sie in der func sequence<T>Referenz


1

Dies könnte ein anderer Weg sein, dies zu tun.

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

-2

Die Funktion Reverse () wird für die umgekehrte Nummer verwendet.

Var n: Int // Nummer eingeben

Für i in 1 ... n.reverse () {Print (i)}

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.