Менше або більше, ніж у заяві Swift switch


145

Мені знайомі switchзаяви у Swift, але цікаво, як замінити цей фрагмент коду на switch:

if someVar < 0 {
    // do something
} else if someVar == 0 {
    // do something else
} else if someVar > 0 {
    // etc
}

Хоча це цікаве питання, я думаю, що код за допомогою перемикача набагато менш читабельний, ніж твердження if. Тільки тому, що ти можеш, не означає, що ти повинен.
Рог

Відповіді:


241

Ось один підхід. Якщо припустити someVar, що це те Intчи інше Comparable, ви можете необов'язково призначити операнда новій змінній. Це дозволяє вам охоплювати його, однак ви хочете використовувати whereключове слово:

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

Це можна трохи спростити:

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

Ви також можете whereповністю уникнути ключового слова повністю з відповідності діапазону:

switch someVar {
case Int.min..<0:
    print("someVar is \(someVar)")
case 0:
    print("someVar is 0")
default:
    print("someVar is \(someVar)")
}

9
Я рекомендую default: fatalError()виявити можливі логічні помилки на ранньому етапі.
Мартін Р

1
Дякую! Ці приклади дуже корисні, і вони вирішують мою проблему! (інші приклади теж були хороші, але мені найбільше допомогли твої)
Пітер

1
@MartinR assertionFailureздається більш безпечним варіантом, особливо під час роботи в команді.
Майкл Волін

119

За допомогою Swift 5 ви можете вибрати один із наведених нижче перемикачів, щоб замінити свою операцію if.


№1 Використання перемикача з PartialRangeFromіPartialRangeUpTo

let value = 1

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

№2 Використання перемикача з ClosedRangeіRange

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 Використання перемикача з пунктом де

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 Використання перемикача з пунктом та призначенням де _

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 Використання комутатора з оператором RangeExpressionпротоколу~=(_:_:)

let value = 1

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

# 6 Використання комутатора з оператором Equatableпротоколу~=(_:_:)

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 Використання перемикача з PartialRangeFrom, PartialRangeUpToі RangeExpressionscontains(_:) метод

let value = 1

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

1
чому потрібен випадок за замовчуванням у №2? здається, що лускав, що якщо рядок знаходиться від Int.min до Int.max, що залишилося?
μολὼν.λαβέ

Нічого собі, приємний список варіантів. Добре знати, що існує ряд способів зробити це.
Крістофер Пікслі

2
Хороший огляд, але хибний, оскільки числа між 0 і 1 не враховуються. 0.1видає фатальну помилку, оскільки 1...охоплює лише числа від 1. Отже, це рішення працює лише якщо valueє, Intале це небезпечно, оскільки якщо тип змінної зміни функціональність перерветься без помилок компілятора.
Мануель

1
Ваше рішення не працює належним чином для типу Double. випадок 1 ...: друк ("більший за нуль") НЕ більший ніж 0, він більший або дорівнює 1.
Влад

20

switchЗаява, під капотом, використовує ~=оператор. Отже це:

let x = 2

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

Десугари до цього:

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

Якщо ви подивитесь на стандартну посилання на бібліотеку, вона може точно сказати, що саме ~=робити : включено відповідність діапазону та прирівняне до рівних речей. (Не входить відповідність enum-case, яка є мовною особливістю, а не функцією в lib-файлі std)

Ви побачите, що він не відповідає прямому булевому з лівого боку. Для таких порівнянь вам потрібно додати оператор.

Якщо тільки ... ви перевантажуєте ~=оператора самостійно. (Це, як правило, не рекомендується) Однією з можливостей буде щось подібне:

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

Таким чином, це відповідає функції, яка повертає булевий зліва до його параметра праворуч. Ось те, що ви могли використовувати для:

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

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

Для вашого випадку у вас може бути вислів, який виглядає приблизно так:

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

Але тепер ви повинні визначити нове isNegativeіisPositive функції. Якщо ви не перевантажите ще декількох операторів ...

Ви можете перевантажувати звичайні оператори інфіксованих файлів, щоб бути викривленими префіксами або операторами постфікса. Ось приклад:

postfix operator < {}

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

Це діятиме так:

let isGreaterThanFive = 5<

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

Поєднайте це з попередньою функцією, і ваш вимикач може виглядати так:

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

Тепер ви, мабуть, не повинні використовувати подібні речі на практиці: це трохи хитро. Вам (напевно) краще дотримуватися whereзаяви. Це сказало, схема заяви заяви

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

або

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

Здається, досить поширеним, щоб його варто було б розглянути.


1
де ваша відповідь на запитання? Я не можу його знайти.
Мед

1
справа 3 .. <5: друк (3 .. <5) - Буквально в першому абзаці. Ця відповідь занижена. Врятує мені стільки код.
Карим

14

Ти можеш:

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

6

Оскільки хтось уже опублікував case let x where x < 0:тут, це альтернатива тому, де someVarє Int.

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

І ось альтернатива, де someVarє Double:

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

6

Так виглядає з діапазонами

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

3

<0Вираз не працює (більше?) , Так що я в кінцевому підсумку з цим:

Swift 3.0:

switch someVar {
    case 0:
        // it's zero
    case 0 ..< .greatestFiniteMagnitude:
        // it's greater than zero
    default:
        // it's less than zero
    }

1
У swift 3.0 X_MAXзамінено на .greatestFiniteMagnitude, тобто Double.greatestFiniteMagnitude, CGFloat.greatestFiniteMagnitudeі т. Д. Так зазвичай це можна зробити, case 0..< .greatestFiniteMagnitudeоскільки тип someVarуже відомий
Guig

@Dorian Roy var timeLeft = 100 switch timeLeft {case 0...<=7200: print("ok") default:print("nothing") }Чому <=оператор не розпізнається? Якщо я напишу його без рівних, це працює. Спасибі
химерність

@bibscy Ви хочете використовувати оператор із закритим діапазоном: case 0...7200:Оператор <=є оператором порівняння. У комутаторі ви можете використовувати лише операторів діапазону (див. Документи)
Доріан Рой

Це було чудово. Я отримував цю схему виразів помилок типу "Діапазон <Подвійний>" не може відповідати значенням типу "Int", тому що мій someVarбув Intі мені довелося виконати Double(деякий вар) `, щоб він працював ...
Honey

2

Радий, що Swift 4 вирішує проблему:

Як вирішення у 3 році я зробив:

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

Працює, але не ідеально

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.