Menor ou maior que na instrução Swift switch

145

Eu estou familiarizado com as switchinstruções no Swift, mas me pergunto como substituir esse trecho de código por um switch:

if someVar < 0 {
    // do something
} else if someVar == 0 {
    // do something else
} else if someVar > 0 {
    // etc
}
Pieter
fonte
Embora essa seja uma pergunta interessante, acho que o código que usa switch é muito menos legível do que as instruções if. Só porque você pode, não significa que você deveria.
Rog

Respostas:

241

Aqui está uma abordagem. Supondo que someVarseja um Intou outro Comparable, você pode opcionalmente atribuir o operando a uma nova variável. Isso permite que você faça o escopo da maneira que desejar, usando a wherepalavra-chave:

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

Isso pode ser um pouco simplificado:

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

Você também pode evitar a wherepalavra - chave inteiramente com a correspondência de intervalos:

switch someVar {
case Int.min..<0:
    print("someVar is \(someVar)")
case 0:
    print("someVar is 0")
default:
    print("someVar is \(someVar)")
}
Aaron Brager
fonte
9
Eu recomendo default: fatalError()detectar possíveis erros de lógica cedo.
Martin R
1
Obrigado! Esses exemplos são muito úteis e resolvem meu problema! (outros exemplos foram muito bons também, mas o seu foi muito útil para mim)
Pieter
1
O @MartinR assertionFailureparece ser uma opção mais segura, especialmente quando se trabalha em equipe.
Michael Voline
119

Com o Swift 5, você pode escolher uma das seguintes opções para substituir sua instrução if.


# 1 Usando a chave com PartialRangeFromePartialRangeUpTo

let value = 1

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

# 2 Usando a chave com ClosedRangeeRange

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 Usando switch com a cláusula 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 Usando switch com a cláusula where e a atribuição a _

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 Usando o switch com RangeExpressiono ~=(_:_:)operador do protocolo

let value = 1

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

# 6 Usando o switch com Equatableo ~=(_:_:)operador do protocolo

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 Utilizando switch com PartialRangeFrom, PartialRangeUpToe RangeExpression's contains(_:)método

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
fonte
1
Por que o caso de inadimplência é necessário no item 2? parece que, se o rannge for de Int.min a Int.max, o que resta?
μολὼν.λαβέ
Uau, boa lista de opções. É bom saber que existem várias maneiras de fazer isso.
Christopher Pickslay
2
Boa visão geral, mas falha porque os números entre 0 e 1 não são contabilizados. 0.1gera um erro fatal porque 1...cobre apenas números de 1. Portanto, esta solução funciona apenas se valuefor um, Intmas isso é perigoso porque se o tipo de variável for alterado, a funcionalidade será interrompida sem nenhum erro do compilador.
Manuel
1
Sua solução não funciona corretamente para o tipo Double. caso 1 ...: print ( "maior que zero") não é maior que 0 é maior ou igual a 1.
Vlad
20

A switchdeclaração, sob o capô, usa o ~=operador. Então, é isso:

let x = 2

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

Desugars para isso:

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

Se você olhar para a referência da biblioteca padrão, ela poderá dizer exatamente o que a ~=sobrecarga deve fazer : incluída é a correspondência de intervalo e a equivalência para coisas equitativas. (Não incluído é a correspondência enum-case, que é um recurso de idioma, e não uma função na lib std)

Você verá que ele não corresponde a um booleano direto no lado esquerdo. Para esse tipo de comparação, você precisa adicionar uma declaração where.

A menos que você sobrecarregue o ~=operador você mesmo. (Isso geralmente não é recomendado) Uma possibilidade seria algo como isto:

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

Portanto, isso corresponde a uma função que retorna um booleano à esquerda para seu parâmetro à direita. Aqui está o tipo de coisa para a qual você poderia usá-lo:

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

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

Para o seu caso, você pode ter uma declaração parecida com esta:

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

Mas agora você precisa definir novas isNegativee isPositivefunções. A menos que você sobrecarregue mais alguns operadores ...

Você pode sobrecarregar operadores de infixo normais para serem operadores de prefixo ou pós-fixado. Aqui está um exemplo:

postfix operator < {}

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

Isso funcionaria assim:

let isGreaterThanFive = 5<

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

Combine isso com a função anterior, e sua instrução switch pode ser assim:

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

Agora, você provavelmente não deveria usar esse tipo de coisa na prática: é um pouco desonesto. Você (provavelmente) está melhor aderindo à wheredeclaração. Dito isto, o padrão de instrução switch de

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

ou

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

Parece bastante comum para que valha a pena considerar.

oisdk
fonte
1
onde está sua resposta para a pergunta? Não consigo encontrar.
Honey
1
case 3 .. <5: print (3 .. <5) - Literalmente no primeiro parágrafo. Esta resposta é subestimada. Me salva tanto código.
Karim
14

Você pode:

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

Como alguém já postou case let x where x < 0:aqui, é uma alternativa para onde someVarestá um Int.

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

E aqui está uma alternativa para onde someVarestá um Double:

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

É assim que se parece com os intervalos

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
fonte
3

A <0expressão não funciona (mais?), Então acabei com isso:

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
fonte
1
Em rápida 3.0, X_MAXfoi substituída por .greatestFiniteMagnitude, ou seja Double.greatestFiniteMagnitude, CGFloat.greatestFiniteMagnitudeetc. Por isso, normalmente, você pode apenas fazer case 0..< .greatestFiniteMagnitudeuma vez que o tipo de someVarjá se sabe
Guig
@Dorian Roy var timeLeft = 100 switch timeLeft {case 0...<=7200: print("ok") default:print("nothing") }Por que o <=operador não é reconhecido? Se eu escrever sem o igual, funciona. Graças
bibscy
@bibscy Você deseja usar o operador de faixa fechada: case 0...7200:O operador <=é um operador de comparação. Em um switch, você só pode usar operadores de intervalo (consulte a documentação)
Dorian Roy
Isso foi ótimo. Eu estava recebendo este erro padrão de expressão do tipo 'Faixa <Double>' não pode coincidir com valores do tipo 'int' porque o meu someVarera um Inte eu tinha que fazer Double(someVar) `para que ele funcione ...
Mel
2

Que bom que o Swift 4 resolve o problema:

Como solução alternativa em 3, fiz:

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

Funciona, mas não é o ideal

Jeremy Andrews
fonte