Gamme inverse dans Swift


105

Existe-t-il un moyen de travailler avec des plages inversées dans Swift?

Par exemple:

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

est une boucle infinie.

Dans les nouvelles versions de Swift, ce code se compile, mais au moment de l'exécution donne l'erreur:

Erreur fatale: impossible de former une plage avec UpperBound <lowerBound

Je sais que je peux utiliser à la 1..5place, calculer j = 6 - iet utiliser jcomme index. Je me demandais juste s'il y avait quelque chose de plus lisible?


Voir ma réponse à une question similaire qui donne jusqu'à 8 façons de résoudre votre problème avec Swift 3.
Imanou Petit

Réponses:


184

Mise à jour pour le dernier Swift 3 (fonctionne toujours dans Swift 4)

Vous pouvez utiliser la reversed()méthode sur une plage

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

Ou stride(from:through:by:)méthode

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

stide(from:to:by:) est similaire mais exclut la dernière valeur

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

Mise à jour pour le dernier Swift 2

Tout d'abord, les extensions de protocole modifient leur reverseutilisation:

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

Stride a été retravaillé dans Xcode 7 Beta 6. La nouvelle utilisation est:

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

Cela fonctionne également pour Doubles:

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

Méfiez-vous de la virgule flottante compare ici pour les limites.

Edition antérieure pour Swift 1.2 : à partir de Xcode 6 Beta 4, par et ReverseRange n'existent plus: [

Si vous cherchez simplement à inverser une plage, la fonction d' inversion est tout ce dont vous avez besoin:

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

Tel que publié par 0x7fffffff, il existe une nouvelle construction de pas qui peut être utilisée pour itérer et incrémenter par des entiers arbitraires. Apple a également déclaré que le support en virgule flottante était à venir.

Tiré de sa réponse:

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 a changé depuis la bêta 6
DogCoffee

1
il devrait l'être (1...5).reverse()(comme appel de fonction), veuillez mettre à jour votre réponse. (Comme il ne s'agit que de deux caractères, je n'ai pas pu modifier votre message.)
Behdad

Dans Swift 3.0-PREVIEW-4 (et peut-être plus tôt), cela devrait être (1...5).reversed(). De plus, l'exemple de .stride()ne fonctionne pas et a besoin de la fonction gratuite à la stride(from:to:by:)place.
davidA

2
semble que le stridecode de swift 3 est légèrement incorrect. pour inclure le numéro 1, vous devez utiliser throughopposé à tocomme ceci:for i in stride(from:5,through:1,by:-1) { print(i) }
262Hz

4
Il convient de souligner qu'il reverseda une complexité de O (1) car il enveloppe simplement la collection d'origine et ne nécessite pas d'itération pour la construire.
devios1

21

Il y a quelque chose de troublant dans l'asymétrie de ceci:

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

... par opposition à ceci:

for i in 1..<5 {

Cela signifie que chaque fois que je veux faire une gamme inversée, je dois me rappeler de mettre les parenthèses, plus je dois écrire cela .reverse()à la fin, en sortant comme un pouce endolori. C'est vraiment moche par rapport aux boucles for de style C, qui sont symétriques comptant et décomptant. J'ai donc tendance à utiliser plutôt le style C pour les boucles. Mais dans Swift 2.2, les boucles for de style C disparaissent! J'ai donc dû me dépêcher de remplacer toutes mes boucles for décrémentantes de style C par cette conception laide .reverse()- en me demandant tout le temps, pourquoi diable n'y a-t-il pas d'opérateur de plage inversée?

Mais attendez! C'est Swift - nous sommes autorisés à définir nos propres opérateurs !! Et c'est parti:

infix operator >>> {
    associativity none
    precedence 135
}

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

Alors maintenant, j'ai le droit de dire:

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

Ce ne couvre que le plus de cas commun qui se produit dans mon code, mais il est loin le cas le plus commun, il est donc tout ce que je besoin à l' heure actuelle.

J'ai eu une sorte de crise interne avec l'opérateur. J'aurais aimé utiliser >.., comme étant l'inverse de ..<, mais ce n'est pas légal: vous ne pouvez pas utiliser un point après un non-point, il apparaît. J'ai réfléchi, ..>mais j'ai décidé que c'était trop difficile à distinguer ..<. Ce >>>qui est bien, c'est qu'il vous crie dessus: " jusqu'à !" (Bien sûr, vous êtes libre de trouver un autre opérateur. Mais mon conseil est le suivant: pour la super symétrie, définissez <<<pour faire ce qui ..<fait, et maintenant vous avez <<<et >>>qui sont symétriques et faciles à taper.)


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

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

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

Version 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' >>>opérateur est vraiment cool! J'espère que les designers de Swift prêtent attention à des choses comme ça, parce que rester reverse()à la fin des expressions entre parenthèses est bizarre. Il n'y a aucune raison pour laquelle ils ne pourraient pas traiter 5>..0automatiquement les plages, car le ForwardIndexTypeprotocole fournit un distanceTo()opérateur parfaitement raisonnable qui peut être utilisé pour déterminer si la foulée doit être positive ou négative.
dasblinkenlight

1
Merci @dasblinkenlight - Cela m'est venu parce que dans l'une de mes applications j'avais beaucoup de boucles C for (en Objective-C) qui ont migré vers Swift C-style for loops et ont maintenant dû être converties parce que C-style for loops s'en vont. C'était tout simplement terrifiant, car ces boucles représentaient le cœur de la logique de mon application et la réécriture de tout risquait de se casser. Je voulais donc une notation qui rendrait la correspondance avec la notation de style C (commentée) aussi claire que possible.
mat

soigné! J'aime votre obsession pour le code propre!
Yohst

10

Il semble que les réponses à cette question ont un peu changé au fur et à mesure que nous progressons dans les bêtas. Depuis la version bêta 4, la by()fonction et le ReversedRangetype ont été supprimés du langage. Si vous cherchez à créer une plage inversée, vos options sont désormais les suivantes:

1: Créez une plage avant, puis utilisez la reverse()fonction pour l'inverser.

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: Utilisez les nouvelles stride()fonctions qui ont été ajoutées dans la version bêta 4, qui comprend des fonctions pour spécifier les index de début et de fin, ainsi que la quantité d'itération.

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
}

Notez que j'ai également inclus le nouvel opérateur de plage exclusif dans cet article. ..a été remplacé par ..<.

Edit: À partir des notes de version de Xcode 6 beta 5 , Apple a ajouté la suggestion suivante pour gérer cela:

ReverseRange a été supprimé; utiliser lazy (x ..

Voici un exemple.

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

+1 Je cherche une référence lazy(0....5).reverse()et je ne vois pas les notes de version bêta 5. Connaissez-vous d'autres discussions sur ce sujet? De plus, pour info, les nombres à l'intérieur de cette forboucle sont inversés dans votre exemple.
Rob

1
@Rob Hmm J'aurais dû penser que les notes de publication d'une version bêta finiraient par être supprimées. Quand j'ai écrit ceci, c'était le seul endroit où j'avais vu une référence à lazy (), mais je serai heureux de chercher un peu plus et de mettre à jour / corriger cela quand je rentrerai à la maison plus tard. Merci de l'avoir signalé.
Mick MacCallum

1
@ 0x7fffffff Dans votre dernier exemple, le commentaire dit // 0, 1, 2, 3, 4, 5mais il devrait en fait être inversé. Le commentaire est trompeur.
Pang

5

Xcode 7, bêta 2:

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

3

Swift 3, 4+ : vous pouvez le faire comme ceci:

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

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

résultat : 10, 9, 8 ... 0

Vous pouvez le personnaliser comme vous le souhaitez. Pour plus d'informations, lisez la func sequence<T>référence


1

Cela pourrait être une autre façon de procéder.

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

-2

La fonction Reverse () est utilisée pour le nombre inverse.

Var n: Int // Entrez le nombre

Pour i en 1 ... n.reverse () {Print (i)}

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.