Kleiner als oder größer als in der Swift-Switch-Anweisung

145

Ich bin mit switchAnweisungen in Swift vertraut , frage mich aber, wie ich diesen Code durch Folgendes ersetzen kann switch:

if someVar < 0 {
    // do something
} else if someVar == 0 {
    // do something else
} else if someVar > 0 {
    // etc
}
Pieter
quelle
Obwohl dies eine interessante Frage ist, denke ich, dass der Code mit switch weit weniger lesbar ist als die if-Anweisungen. Nur weil du kannst, heißt das nicht, dass du es solltest.
Rog

Antworten:

241

Hier ist ein Ansatz. Angenommen, es someVarhandelt sich um eine Intoder eine andere Comparable, können Sie den Operanden optional einer neuen Variablen zuweisen. Auf diese Weise können Sie den Umfang mit dem whereSchlüsselwort festlegen, wie Sie möchten :

var someVar = 3

switch someVar {
case let x where x < 0:
    print("x is \(x)")
case let x where x == 0:
    print("x is \(x)")
case let x where x > 0:
    print("x is \(x)")
default:
    print("this is impossible")
}

Dies kann etwas vereinfacht werden:

switch someVar {
case _ where someVar < 0:
    print("someVar is \(someVar)")
case 0:
    print("someVar is 0")
case _ where someVar > 0:
    print("someVar is \(someVar)")
default:
    print("this is impossible")
}

Sie können das whereSchlüsselwort auch mit der Bereichsübereinstimmung vollständig vermeiden :

switch someVar {
case Int.min..<0:
    print("someVar is \(someVar)")
case 0:
    print("someVar is 0")
default:
    print("someVar is \(someVar)")
}
Aaron Brager
quelle
9
Ich empfehle default: fatalError(), mögliche Logikfehler frühzeitig zu erkennen.
Martin R
1
Vielen Dank! Diese Beispiele sind sehr hilfreich und lösen mein Problem! (andere Beispiele waren auch gut, aber Ihre waren für mich am hilfreichsten)
Pieter
1
@MartinR assertionFailurescheint eine sicherere Option zu sein, insbesondere wenn Sie in einem Team arbeiten.
Michael Voline
119

Mit Swift 5 können Sie einen der folgenden Schalter auswählen, um Ihre if-Anweisung zu ersetzen.


# 1 Schalter mit PartialRangeFromund verwendenPartialRangeUpTo

let value = 1

switch value {
case 1...:
    print("greater than zero")
case 0:
    print("zero")
case ..<0:
    print("less than zero")
default:
    fatalError()
}

# 2 Schalter mit ClosedRangeund verwendenRange

let value = 1

switch value {
case 1 ... Int.max:
    print("greater than zero")
case Int.min ..< 0:
    print("less than zero")
case 0:
    print("zero")
default:
    fatalError()
}

# 3 Verwenden von switch mit where-Klausel

let value = 1

switch value {
case let val where val > 0:
    print("\(val) is greater than zero")
case let val where val == 0:
    print("\(val) is zero")
case let val where val < 0:
    print("\(val) is less than zero")
default:
    fatalError()
}

# 4 Verwenden von switch mit where-Klausel und Zuweisung an _

let value = 1

switch value {
case _ where value > 0:
    print("greater than zero")
case _ where value == 0:
    print("zero")
case _ where value < 0:
    print("less than zero")
default:
    fatalError()
}

# 5 Verwenden des Switches mit RangeExpressiondem ~=(_:_:)Operator des Protokolls

let value = 1

switch true {
case 1... ~= value:
    print("greater than zero")
case ..<0 ~= value:
    print("less than zero")
default:
    print("zero")
}

# 6 Verwenden des Switches mit Equatabledem ~=(_:_:)Operator des Protokolls

let value = 1

switch true {
case value > 0:
    print("greater than zero")
case value < 0:
    print("less than zero")
case 0 ~= value:
    print("zero")
default:
    fatalError()
}

# 7 Verwendung Schalter mit PartialRangeFrom, PartialRangeUpTound RangeExpression‚s contains(_:)Verfahren

let value = 1

switch true {
case (1...).contains(value):
    print("greater than zero")
case (..<0).contains(value):
    print("less than zero")
default:
    print("zero")
}
Imanou Petit
quelle
1
Warum wird der Standardfall in # 2 benötigt? scheint flakey, dass, wenn die Rannge von Int.min bis Int.max ist, was übrig bleibt?
μολὼν.λαβέ
Wow, schöne Liste von Optionen. Gut zu wissen, dass es dafür eine Reihe von Möglichkeiten gibt.
Christopher Pickslay
2
Gute Übersicht, aber fehlerhaft, da Zahlen zwischen 0 und 1 nicht berücksichtigt werden. 0.1löst einen schwerwiegenden Fehler aus, da 1...nur Zahlen von 1 abgedeckt werden. Diese Lösung funktioniert also nur, wenn dies valueeine Intist. Dies ist jedoch gefährlich, da bei einer Änderung des Variablentyps die Funktionalität ohne Compilerfehler unterbrochen wird.
Manuel
1
Ihre Lösung funktioniert für Double Type nicht richtig. Fall 1 ...: print ("größer als Null") ist NICHT größer als 0, es ist größer oder gleich 1.
Vlad
20

Die switchAnweisung unter der Haube verwendet den ~=Operator. Also das:

let x = 2

switch x {
case 1: print(1)
case 2: print(2)
case 3..<5: print(3..<5)
default: break
}

Desugars dazu:

if 1          ~= x { print(1) }
else if 2     ~= x { print(2) }
else if 3..<5 ~= x { print(3..<5) }
else {  }

Wenn Sie sich die Standardbibliotheksreferenz ansehen, können Sie genau ~=wissen , wozu die überladen ist : Enthalten ist die Bereichsanpassung und das Gleichsetzen für gleichwertige Dinge. (Nicht enthalten ist der Enum-Case-Matching, bei dem es sich eher um eine Sprachfunktion als um eine Funktion in der Standardbibliothek handelt.)

Sie werden sehen, dass es nicht mit einem geraden Booleschen Wert auf der linken Seite übereinstimmt. Für diese Art von Vergleichen müssen Sie eine where-Anweisung hinzufügen.

Es sei denn ... Sie überlasten den ~=Bediener selbst. (Dies wird im Allgemeinen nicht empfohlen.) Eine Möglichkeit wäre etwa diese:

func ~= <T> (lhs: T -> Bool, rhs: T) -> Bool {
  return lhs(rhs)
}

Das entspricht also einer Funktion, die links einen Booleschen Wert an ihren Parameter rechts zurückgibt. Hier ist die Art von Dingen, für die Sie es verwenden könnten:

func isEven(n: Int) -> Bool { return n % 2 == 0 }

switch 2 {
case isEven: print("Even!")
default:     print("Odd!")
}

Für Ihren Fall haben Sie möglicherweise eine Aussage, die folgendermaßen aussieht:

switch someVar {
case isNegative: ...
case 0: ...
case isPositive: ...
}

Aber jetzt müssen Sie neue isNegativeund isPositiveFunktionen definieren . Es sei denn, Sie überlasten einige weitere Operatoren ...

Sie können normale Infix-Operatoren überladen, um Curry-Präfix- oder Postfix-Operatoren zu sein. Hier ist ein Beispiel:

postfix operator < {}

postfix func < <T : Comparable>(lhs: T)(_ rhs: T) -> Bool {
  return lhs < rhs
}

Das würde so funktionieren:

let isGreaterThanFive = 5<

isGreaterThanFive(6) // true
isGreaterThanFive(5) // false

Kombinieren Sie dies mit der früheren Funktion, und Ihre switch-Anweisung kann folgendermaßen aussehen:

switch someVar {
case 0< : print("Bigger than 0")
case 0  : print("0")
default : print("Less than 0")
}

Jetzt sollten Sie so etwas in der Praxis wahrscheinlich nicht verwenden: Es ist ein bisschen zwielichtig. Sie sind (wahrscheinlich) besser dran, sich an die whereAussage zu halten. Das heißt, das switch-Anweisungsmuster von

switch x {
case negative:
case 0:
case positive:
}

oder

switch x {
case lessThan(someNumber):
case someNumber:
case greaterThan(someNumber):
}

Scheint häufig genug zu sein, um eine Überlegung wert zu sein.

oisdk
quelle
1
Wo ist deine Antwort auf die Frage? Ich kann es nicht finden.
Honey
1
Fall 3 .. <5: print (3 .. <5) - Wörtlich im ersten Absatz. Diese Antwort wird unterschätzt. Spart mir so viel Code.
Karim
14

Sie können:

switch true {
case someVar < 0:
    print("less than zero")
case someVar == 0:
    print("eq 0")
default:
    print("otherwise")
}
Rintaro
quelle
6

Da jemand hier schon gepostet case let x where x < 0:hat ist eine Alternative für wo someVarist ein Int.

switch someVar{
case Int.min...0: // do something
case 0: // do something
default: // do something
}

Und hier ist eine Alternative für wo someVarist ein Double:

case -(Double.infinity)...0: // do something
// etc
simons
quelle
6

So sieht es mit Bereichen aus

switch average {
case 0..<40: //greater or equal than 0 and less than 40
    return "T"
case 40..<55: //greater or equal than 40 and less than 55
    return "D"
case 55..<70: //greater or equal than 55 and less than 70
    return "P"
case 70..<80: //greater or equal than 70 and less than 80
    return "A"
case 80..<90: //greater or equal than 80 and less than 90
    return "E"
case 90...100: //greater or equal than 90 and less or equal than 100
    return "O"
default:
    return "Z"
}
GOrozco58
quelle
3

Der <0Ausdruck funktioniert nicht mehr (mehr?), Also habe ich folgendes gefunden:

Swift 3.0:

switch someVar {
    case 0:
        // it's zero
    case 0 ..< .greatestFiniteMagnitude:
        // it's greater than zero
    default:
        // it's less than zero
    }
Dorian Roy
quelle
1
In swift 3.0 X_MAXwird ersetzt durch .greatestFiniteMagnitude, das heißt Double.greatestFiniteMagnitude, CGFloat.greatestFiniteMagnitudeetc. Also in der Regel können Sie nur tun , case 0..< .greatestFiniteMagnitudeda die Art der someVarbereits bekannt
Guig
@ Dorian Roy var timeLeft = 100 switch timeLeft {case 0...<=7200: print("ok") default:print("nothing") }Warum wird der <=Betreiber nicht erkannt? Wenn ich es ohne das Gleiche schreibe, funktioniert es. Danke
bibscy
@bibscy Sie möchten den Operator für geschlossene Bereiche verwenden: case 0...7200:Der Operator <=ist ein Vergleichsoperator. In einem Schalter können Sie nur Bereichsoperatoren verwenden (siehe Dokumente)
Dorian Roy
Das war großartig. Ich habe dieses Fehlerausdruckmuster vom Typ 'Range <Double>' erhalten, das nicht mit Werten vom Typ 'Int' übereinstimmen kann, da mein someVarein war Intund ich Double(someVar) `ausführen musste, damit es funktioniert ...
Honey
2

Ich bin froh, dass Swift 4 das Problem angeht:

Als Workaround in 3 habe ich:

switch translation.x  {
case  0..<200:
    print(translation.x, slideLimit)
case  -200..<0:
    print(translation.x, slideLimit)
default:
    break
}

Funktioniert aber nicht ideal

Jeremy Andrews
quelle