Puis-je utiliser l'opérateur de plage avec l'instruction if dans Swift?

196

Est-il possible d'utiliser l'opérateur de plage ...et ..<avec l'instruction if. Maye quelque chose comme ça:

let statusCode = 204
if statusCode in 200 ..< 299 {
  NSLog("Success")
}
Jimmy
la source

Réponses:

425

Vous pouvez utiliser l'opérateur "pattern-match" ~=:

if 200 ... 299 ~= statusCode {
    print("success")
}

Ou une instruction switch avec un modèle d'expression (qui utilise l'opérateur de correspondance de modèle en interne):

switch statusCode {
case 200 ... 299:
    print("success")
default:
    print("failure")
}

Notez que ..<dénote une plage qui omet la valeur supérieure, donc vous voulez probablement 200 ... 299ou 200 ..< 300.

Informations supplémentaires: Lorsque le code ci-dessus est compilé dans Xcode 6.3 avec les optimisations activées, alors pour le test

if 200 ... 299 ~= statusCode

en fait, aucun appel de fonction n'est généré du tout, seulement trois instructions d'assemblage:

addq    $-200, %rdi
cmpq    $99, %rdi
ja  LBB0_1

c'est exactement le même code d'assemblage qui est généré pour

if statusCode >= 200 && statusCode <= 299

Vous pouvez vérifier cela avec

xcrun -sdk macosx swiftc -O -emit-assembly main.swift

À partir de Swift 2, cela peut s'écrire

if case 200 ... 299 = statusCode {
    print("success")
}

en utilisant la mise en correspondance de modèles nouvellement introduite pour les instructions if. Voir aussi Swift 2 - Correspondance de motifs dans "if" .

Martin R
la source
1
Cool, est-ce O (1)? De plus, ce serait bien si Swift avait une main courte pour les instructions switch, comme Scala par exemple. Mais étant donné que vous êtes toujours obligé de gérer toutes les possibilités au moment de la compilation dans Swift, cela n'est peut-être pas vraiment faisable.
Sky
2
@Sky: A partir du code assembleur généré, on peut voir qu'une fonction de bibliothèque func ~= (Range<A>, A) -> Boolest appelée. Je suppose que cette fonction fonctionne avec O (1).
Martin R
4
@Downvoter: Certains commentaires explicatifs seraient bien, afin que je puisse améliorer ou corriger la réponse ...
Martin R
1
@MartinR comment vous savez quelle fonction est appelée par le langage d'assemblage. Hopper? +1 pour une réponse sympa
codester
3
@codester: J'ai compilé le code sur la ligne de commande avec xcrun -sdk macosx swift -emit-assembly main.swiftet inspecté le code assembleur. J'ai ensuite utilisé xcrun swift-demangle ...pour démanteler le nom de la fonction appelée. - Malheureusement, Xcode ne peut pas encore créer de code d'assemblage pour les fichiers Swift, il fonctionnera peut-être dans une version ultérieure.
Martin R
95

Cette version semble être plus lisible que la correspondance de motifs:

if (200 ... 299).contains(statusCode) {
    print("Success")
}
Serhii Yakovenko
la source
2
Exactement ce que je cherchais
Nazim Kerimbekov
J'obtiens cette erreur => Impossible de former une plage avec upperBound <lowerBound
Alfi
9

C'est un vieux fil, mais il me semble que nous y réfléchissons trop. Il me semble que la meilleure réponse est

if statusCode >= 200 && statusCode <= 299

Il n'y a pas

if 200 > statusCode > 299

que je connais, et les autres solutions suggérées font des appels de fonction, qui sont plus difficiles à lire et peuvent être plus lents à exécuter. La méthode de correspondance de modèle est une astuce utile à connaître, mais semble être un mauvais ajustement pour ce problème.

Éditer:

Personnellement, je trouve que l'opérateur de correspondance de motifs est hideux et j'aimerais que le compilateur prenne en charge la if x in 1...100syntaxe. C'est tellement plus intuitif et facile à lire queif 1...100 ~= x

Duncan C
la source
1
Vous avez raison de dire que cette version est meilleure à lire, j'ai juste essayé de répondre à la question explicite "Est-il possible d'utiliser l'opérateur de plage ...?" - Mais Xcode 6.3 beta (en mode optimisé) génère exactement trois instructions d'assemblage pour if 200 ... 299 ~= statusCode, aucun appel de fonction :)
Martin R
13
if 200 ... 299 ~= statusCodeDonne en fait le même code d'assemblage queif statusCode >= 200 && statusCode <= 299
Martin R
6
À moins que ce conditionnel ne se trouve dans une section critique visitée des milliers de fois par seconde, s'inquiéter de la surcharge des appels de fonction est une optimisation prématurée. Même alors, je vous inquiétez plus sur ce qu'un appel de fonction est en train de faire plutôt que le coût de l' appeler. Beau travail @MartinR pour prouver qu'il n'y a aucun coût, cependant.
rickster
1
@rickster, c'est vrai. J'ai tendance à préférer les constructions efficaces aux constructions inefficaces par habitude (en supposant que la lisibilité est similaire). Pas dans la mesure où je gaspille trop de MON temps dessus, mais cela paie quand même de savoir quels sont les coûts des différentes approches.
Duncan C
1
C'est tatillon, mais je ne suis pas d'accord avec votre suggestion selon laquelle votre déclaration if est plus lisible ou compréhensible que la réponse publiée par @SerhiiYakovenko. Simplement sur la base de SEC - vous nommez "statusCode" deux fois. Dans une session de débogage aux yeux larmoyants tard dans la nuit après avoir décidé qu'une variable différente nommée "statusValue" devrait être utilisée ici au lieu de "statusCode", je pourrais peut-être commettre l'erreur de changer l'un des noms de variable et non l'autre .
RenniePet
3

Je voulais vérifier les erreurs 4xx sauf 401. Voici le code:

let i = 401

if 400..<500 ~= i, i != 401 {
    print("yes")
} else {
    print("NO")
}
abhimuralidharan
la source
2

J'ai également préféré l'opérateur Range .contains (), jusqu'à ce que je trouve que son implémentation est inefficace - https://oleb.net/blog/2015/09/swift-ranges-and-intervals/

Nous pouvons représenter la condition x <0 en utilisant une plage: (Int.min .. <0) .contains (x) est exactement équivalent. Mais c'est beaucoup plus lent. L'implémentation par défaut de contains (_ :) parcourt toute la collection, et exécuter une boucle neuf quintillions de fois dans le pire des cas n'est pas bon marché.

Entro
la source