Как сравнить перечисление со связанными значениями, игнорируя связанное с ним значение в Swift?

87

Прочитав Как проверить равенство перечислений Swift со связанными значениями , я реализовал следующее перечисление:

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

Следующий код работает:

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

Однако это не компилируется:

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

... и выдает следующее сообщение об ошибке:

Бинарный оператор '==' не может применяться к операндам типа 'CardRank' и '(Int) -> CardRank'

Я предполагаю, что это связано с тем, что он ожидает полный тип и CardRank.Numberне указывает весь тип, тогда как CardRank.Number(2)это было. Однако в этом случае я хочу, чтобы он совпадал с любым числом; не только конкретный.

Очевидно, я могу использовать оператор switch, но весь смысл реализации ==оператора заключался в том, чтобы избежать этого подробного решения:

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

Is there any way to compare an enum with associated values while ignoring its associated value?

Note: I realize that I could change the case in the == method to case (.Number, .Number): return true, but, although it would return true correctly, my comparison would still look like its being compared to a specific number (number == CardRank.Number(2); where 2 is a dummy value) rather than any number (number == CardRank.Number).

Senseful
источник
1
You can reduce the Jack, Queen, King, Ace cases in the == operator implementation to just: case (let x, let y) where x == y: return true
Alexander - Reinstate Monica

Ответы:

72

Edit: As Etan points out, you can omit the (_) wildcard match to use this more cleanly.


Unfortunately, I don't believe that there's an easier way than your switch approach in Swift 1.2.

In Swift 2, however, you can use the new if-case pattern match:

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

If you're looking to avoid verbosity, you might consider adding an isNumber computed property to your enum that implements your switch statement.

Ronald Martin
источник
1
This is great for control flow, but it kinda sucks when you just want a simple expression, eg: assert(number == .Number). I can only hope this is improved in later versions of Swift. =/
Jeremy
3
Also sucks for things like while loop conditions etc. In Swift 3 you can remove the (_) for a cleaner code.
Etan
Thanks @Etan, I've added that to the answer. You can actually omit the wildcard in Swift 2 too. This answer was written before the language feature was released, so I didn't know that yet! :-D
Ronald Martin
Also: If an enum has a single case with associated values, the if-case pattern needs to be used even for those cases that do not have associated values.
Etan
1
@PeterWarbo, you cannot do negation with this pattern-matching syntax. You'll have to fall back to a default case in a switch block for now.
Ronald Martin
28

Unfortunately in Swift 1.x there isn't another way so you have to use switch which isn't as elegant as Swift 2's version where you can use if case:

if case .Number = number {
    //ignore the value
}
if case .Number(let x) = number {
    //without ignoring
}
Qbyte
источник
3
Sadly, this only works in if statements, not as an expression.
Raphael
3

Here's a simpler approach:

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

There's no need to overload the == operator, and checking for card type does not require confusing syntax:

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
источник
3
Though it doesn't answer the overarching question, IMO this is a more elegant solution (enumerated Numbers aside) in scenarios similar to OP's and shouldn't be downvoted.
Nathan Hosselton