Como comparar enum com valores associados, ignorando seu valor associado em Swift?

87

Depois de ler Como testar a igualdade de enums do Swift com valores associados , implementei o seguinte enum:

enum CardRank {
    case Number(Int)
    case Jack
    case Queen
    case King
    case Ace
}

func ==(a: CardRank, b: CardRank) -> Bool {
    switch (a, b) {
    case (.Number(let a), .Number(let b))   where a == b: return true
    case (.Jack, .Jack): return true
    case (.Queen, .Queen): return true
    case (.King, .King): return true
    case (.Ace, .Ace): return true
    default: return false
    }
}

O seguinte código funciona:

let card: CardRank = CardRank.Jack
if card == CardRank.Jack {
    print("You played a jack!")
} else if card == CardRank.Number(2) {
    print("A two cannot be played at this time.")
}

No entanto, isso não compila:

let number = CardRank.Number(5)
if number == CardRank.Number {
    print("You must play a face card!")
}

... e dá a seguinte mensagem de erro:

O operador binário '==' não pode ser aplicado a operandos do tipo 'CardRank' e '(Int) -> CardRank'

Estou supondo que isso ocorre porque ele está esperando um tipo completo e CardRank.Numbernão especifica um tipo inteiro, enquanto o CardRank.Number(2)fazia. No entanto, neste caso, quero que corresponda a qualquer número; não apenas um específico.

Obviamente, posso usar uma instrução switch, mas o objetivo principal da implementação do ==operador era evitar esta solução detalhada:

switch number {
case .Number:
    print("You must play a face card!")
default:
    break
}

Existe alguma maneira de comparar um enum com valores associados enquanto ignora seu valor associado?

Observação: eu percebo que poderia alterar o caso no ==método para case (.Number, .Number): return true, mas, embora retornasse verdadeiro corretamente, minha comparação ainda pareceria estar sendo comparada a um número específico ( number == CardRank.Number(2); onde 2 é um valor fictício) em vez de qualquer número ( number == CardRank.Number).

Sensível
fonte
1
Você pode reduzir os Jack, Queen, King, Acecasos na ==implementação operador apenas:case (let x, let y) where x == y: return true
Alexander - Reintegrar Monica

Respostas:

72

Edit: Como Etan aponta, você pode omitir a (_)correspondência curinga para usar isso de forma mais limpa.


Infelizmente, não acredito que haja uma maneira mais fácil do que sua switchabordagem no Swift 1.2.

No Swift 2, no entanto, você pode usar a nova if-casecorrespondência de padrão:

let number = CardRank.Number(5)
if case .Number(_) = number {
    // Is a number
} else {
    // Something else
}

Se você está procurando evitar o detalhamento, pode considerar adicionar uma isNumberpropriedade computada ao seu enum que implemente sua instrução switch.

Ronald Martin
fonte
1
Isso é ótimo para controle de fluxo, mas é meio chato quando você quer apenas uma expressão simples, por exemplo: assert(number == .Number). Só posso esperar que isso seja melhorado nas versões posteriores do Swift. = /
Jeremy
3
Também é péssimo para coisas como condições de loop while etc. No Swift 3 você pode remover o (_) para um código mais limpo.
Etan
Obrigado @Etan, adicionei isso à resposta. Você também pode omitir o curinga no Swift 2. Esta resposta foi escrita antes do lançamento do recurso de linguagem, então eu não sabia disso ainda! :-D
Ronald Martin
Além disso: Se um enum tiver um único caso com valores associados, o padrão if-case precisa ser usado mesmo para os casos que não têm valores associados.
Etan
1
@PeterWarbo, você não pode negar com esta sintaxe de correspondência de padrões. Você terá que defaultrecorrer a um caso em um switchbloco por enquanto.
Ronald Martin
28

Infelizmente, no Swift 1.x não há outra maneira de usar switchque não seja tão elegante quanto a versão do Swift 2, onde você pode usar if case:

if case .Number = number {
    //ignore the value
}
if case .Number(let x) = number {
    //without ignoring
}
Qbyte
fonte
3
Infelizmente, isso só funciona em ifdeclarações, não como uma expressão.
Raphael
20

No Swift 4.2 Equatableserá sintetizado se todos os seus valores associados estiverem em conformidade Equatable. Tudo que você precisa fazer é adicionar Equatable.

enum CardRank: Equatable {
    case Number(Int)
    case Jack
    case Queen
    case King
    case Ace
}

https://developer.apple.com/documentation/swift/equatable?changes=_3

Nambatee
fonte
18
Embora isso compare os valores associados.
Joe
3

Esta é uma abordagem mais simples:

enum CardRank {
    case Two
    case Three
    case Four
    case Five
    case Six
    case Seven
    case Eight
    case Nine
    case Ten
    case Jack
    case Queen
    case King
    case Ace

    var isFaceCard: Bool {
        return (self == Jack) || (self == Queen) || (self == King)
    }
}

Não há necessidade de sobrecarregar o operador == e a verificação do tipo de cartão não requer sintaxe confusa:

let card = CardRank.Jack

if card == CardRank.Jack {
    print("You played a jack")
} else if !card.isFaceCard {
    print("You must play a face card!")
}
Mike Taverne
fonte
3
Embora não responda à questão geral, IMO esta é uma solução mais elegante (números enumerados à parte) em cenários semelhantes aos OPs e não deve ser rejeitada.
Nathan Hosselton