Mniejsze lub większe niż w instrukcji przełącznika Swift

145

Znam switchinstrukcje w Swift, ale zastanawiam się, jak zamienić ten fragment kodu na switch:

if someVar < 0 {
    // do something
} else if someVar == 0 {
    // do something else
} else if someVar > 0 {
    // etc
}
Pieter
źródło
Chociaż jest to interesujące pytanie, myślę, że kod wykorzystujący przełącznik jest znacznie mniej czytelny niż instrukcje if. To, że możesz, nie oznacza, że ​​powinieneś.
Rog

Odpowiedzi:

241

Oto jedno podejście. Zakładając, że someVarjest to Intlub inny Comparable, możesz opcjonalnie przypisać operand do nowej zmiennej. Dzięki temu możesz określić zakres, jak chcesz, używając wheresłowa kluczowego:

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")
}

Można to nieco uprościć:

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")
}

Możesz również wherecałkowicie uniknąć słowa kluczowego dzięki dopasowaniu zakresu:

switch someVar {
case Int.min..<0:
    print("someVar is \(someVar)")
case 0:
    print("someVar is 0")
default:
    print("someVar is \(someVar)")
}
Aaron Brager
źródło
9
Zalecam default: fatalError()wczesne wykrywanie możliwych błędów logicznych.
Martin R
1
Dzięki! Te przykłady są bardzo pomocne i rozwiązują mój problem! (inne przykłady też były dobre, ale twoje były dla mnie najbardziej pomocne)
Pieter
1
@MartinR assertionFailurewydaje się być bezpieczniejszą opcją, szczególnie podczas pracy w zespole.
Michael Voline
119

Dzięki Swift 5 możesz wybrać jeden z poniższych przełączników, aby zastąpić instrukcję if.


# 1 Używanie przełącznika z PartialRangeFromiPartialRangeUpTo

let value = 1

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

# 2 Korzystanie z przełącznika z ClosedRangeiRange

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 Używanie przełącznika z klauzulą ​​WHERE

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 Używanie przełącznika z klauzulą ​​WHERE i przypisaniem do _

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 Używanie przełącznika z operatorem RangeExpressionprotokołu~=(_:_:)

let value = 1

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

# 6 Używanie przełącznika z operatorem Equatableprotokołu~=(_:_:)

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 Korzystanie z przełącznika PartialRangeFrom, PartialRangeUpToa RangeExpressionjest contains(_:)metoda

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
źródło
1
dlaczego w # 2 potrzebny jest przypadek domyślny? wydaje się kruchy, że jeśli rannge jest od Int.min do Int.max, co pozostaje?
μολὼν.λαβέ
Wow, niezła lista opcji. Warto wiedzieć, że można to zrobić na kilka sposobów.
Christopher Pickslay
2
Dobry przegląd, ale błędny, ponieważ liczby od 0 do 1 nie są uwzględniane. 0.1zgłasza błąd krytyczny, ponieważ 1...obejmuje tylko liczby od 1. Więc to rozwiązanie działa tylko wtedy, gdy valuejest, Intale jest to niebezpieczne, ponieważ jeśli typ zmiennej zostanie zmieniony, funkcjonalność zostanie przerwana bez żadnego błędu kompilatora.
Manuel
1
Twoje rozwiązanie nie działa poprawnie dla typu Double. przypadek 1 ...: print ("większe niż zero") NIE jest większe niż 0, jest większe lub równe 1.
Vlad
20

switchOświadczenie, pod maską, używa ~=operatora. Więc to:

let x = 2

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

Cukry do tego:

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

Jeśli spojrzysz na odniesienie do biblioteki standardowej, może ci dokładnie powiedzieć, do czego ~=jest przeciążona : dołączone jest dopasowywanie zakresu i zrównanie dla równych rzeczy. (Nie uwzględniono dopasowania wyliczenia wielkości liter, co jest funkcją języka, a nie funkcją w standardowej bibliotece)

Zobaczysz, że nie pasuje do prostej wartości logicznej po lewej stronie. W przypadku tego rodzaju porównań należy dodać instrukcję gdzie.

Chyba że ... sam przeciążasz ~=operatora. (Generalnie nie jest to zalecane) Jedną z możliwości byłoby coś takiego:

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

Więc to dopasowuje funkcję, która zwraca wartość logiczną po lewej stronie do parametru po prawej stronie. Oto rodzaj rzeczy, do których możesz go użyć:

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

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

W twoim przypadku możesz mieć oświadczenie, które wygląda następująco:

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

Ale teraz musisz zdefiniować nowe isNegativei isPositivefunkcje. Chyba że przeładujesz więcej operatorów ...

Możesz przeciążać zwykłe operatory wrostkowe, aby były one operatorami prefiksowymi lub postfiksowymi. Oto przykład:

postfix operator < {}

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

To działałoby tak:

let isGreaterThanFive = 5<

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

Połącz to z wcześniejszą funkcją, a instrukcja switch może wyglądać następująco:

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

Prawdopodobnie nie powinieneś używać tego typu rzeczy w praktyce: jest to trochę podejrzane. (Prawdopodobnie) lepiej trzymać się tego wherestwierdzenia. To powiedziawszy, wzorzec instrukcji przełącznika

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

lub

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

Wydaje się na tyle powszechne, że warto się nad tym zastanowić.

oisdk
źródło
1
gdzie jest twoja odpowiedź na pytanie? Nie mogę tego znaleźć.
Kochanie
1
przypadek 3 .. <5: print (3 .. <5) - Dosłownie w pierwszym akapicie. Ta odpowiedź jest niedoceniana. Oszczędza mi tyle kodu.
Karim
14

Możesz:

switch true {
case someVar < 0:
    print("less than zero")
case someVar == 0:
    print("eq 0")
default:
    print("otherwise")
}
rintaro
źródło
6

Ponieważ ktoś już opublikował case let x where x < 0:tutaj, jest alternatywą dla gdzie someVarjest Int.

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

A oto alternatywa dla gdzie someVarjest Double:

case -(Double.infinity)...0: // do something
// etc
Simons
źródło
6

Tak to wygląda z zakresami

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
źródło
3

<0Wyrażenie nie działa (już?), Więc skończyło się w ten sposób:

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
źródło
1
W swift 3.0, X_MAXzostała zastąpiona .greatestFiniteMagnitude, tj Double.greatestFiniteMagnitude, CGFloat.greatestFiniteMagnitudeitd. Więc zwykle, można po prostu zrobić case 0..< .greatestFiniteMagnitudeod rodzaju someVarjest już znany
Guig
@Dorian Roy var timeLeft = 100 switch timeLeft {case 0...<=7200: print("ok") default:print("nothing") }Dlaczego <=operator nie jest rozpoznawany? Jeśli napiszę to bez równości, to działa. Dzięki
bibscy
@bibscy Chcesz użyć operatora zamkniętego zakresu: case 0...7200:Operator <=jest operatorem porównania. W przełączniku możesz używać tylko operatorów zasięgu (patrz dokumentacja)
Dorian Roy
To było świetne. Otrzymałem ten wzorzec wyrażenia błędu typu `` Zakres <Double> '' nie może pasować do wartości typu `` Int '', ponieważ mój someVarbył Inti musiałem zrobić Double(someVar) `, aby działał ...
Honey
2

Cieszę się, że Swift 4 rozwiązuje problem:

Jako obejście w 3 zrobiłem:

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

Działa, ale nie jest idealny

Jeremy Andrews
źródło