Меньше или больше, чем в операторе Swift switch

145

Я знаком с switchутверждениями в Swift, но мне интересно, как заменить этот кусок кода на switch:

if someVar < 0 {
    // do something
} else if someVar == 0 {
    // do something else
} else if someVar > 0 {
    // etc
}
Pieter
источник
Хотя это интересный вопрос, я думаю, что код с использованием switch гораздо менее читабелен, чем операторы 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 Использование switch с предложением 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 Использование switch с предложением where и присваиванием _

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и RangeExpression«ы contains(_:)метод

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
источник
1
почему регистр по умолчанию необходим в # 2? кажется странным, что если диапазон от Int.min до Int.max, что остается?
μολὼν.λαβέ
Вау, хороший список вариантов. Приятно знать, что есть несколько способов сделать это.
Кристофер Пикслей
2
Хороший обзор, но с недостатками, потому что числа от 0 до 1 не учитываются. 0.1генерирует фатальную ошибку, потому что 1...охватывает только числа от 1. Таким образом, это решение работает, только если valueесть, Intно это опасно, потому что, если тип переменной изменяется, функциональные сбои без какой-либо ошибки компилятора.
Мануэль
1
Ваше решение не работает правильно для типа Double. case 1 ...: print («больше нуля») НЕ больше 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
}

Desugars к этому:

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

Если вы посмотрите на ссылку на стандартную библиотеку, она может сказать вам, что ~=делать перегружено версией: включается соответствие по диапазону и уравнивание для уравниваемых вещей. (Не включено сопоставление с перечислением, которое является языковой функцией, а не функцией из библиотеки std)

Вы увидите, что он не соответствует прямому логическому значению на левой стороне. Для таких сравнений вам нужно добавить оператор where.

Если только вы ~=сами не перегружаете оператора. (Как правило, это не рекомендуется) Одна из возможностей будет выглядеть примерно так:

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 может выглядеть так:

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

Кажется достаточно распространенным, чтобы об этом стоило задуматься.

oisdk
источник
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"
}
GOrozco58
источник
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
В Свифт 3.0, X_MAXбыл заменен .greatestFiniteMagnitude, то есть Double.greatestFiniteMagnitude, и CGFloat.greatestFiniteMagnitudeт.д. Таким образом , как правило, вы можете просто сделать , case 0..< .greatestFiniteMagnitudeтак как тип someVarуже известен
Guig
@ Дориан Рой var timeLeft = 100 switch timeLeft {case 0...<=7200: print("ok") default:print("nothing") }Почему <=оператор не распознается? Если я пишу это без равных, это работает. Спасибо
bibscy
@bibscy Вы хотите использовать оператор закрытого диапазона: case 0...7200:оператор <=является оператором сравнения. В коммутаторе вы можете использовать только операторы дальности (см. Документы)
Дориан Рой,
Это было здорово. Я получаю этот шаблон выражения ошибки типа 'Range <Double>', который не может соответствовать значениям типа 'Int', потому что my someVarбыл an, Intи я должен был сделать Double(someVar) `, чтобы это сработало ...
Honey
2

Рад, что Swift 4 решает проблему:

В качестве обходного пути в 3 я сделал:

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

Работает но не идеально

Джереми Эндрюс
источник