Jak porównać wyliczenie z powiązanymi wartościami, ignorując skojarzoną z nim wartość w języku Swift?

87

Po przeczytaniu Jak testować równość wyliczeń Swift z powiązanymi wartościami zaimplementowałem następujące wyliczenie:

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

Poniższy kod działa:

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

Jednak to się nie kompiluje:

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

... i daje następujący komunikat o błędzie:

Operator binarny „==” nie może być stosowany do operandów typu „CardRank” i „(Int) -> CardRank”

Zakładam, że CardRank.Numberdzieje się tak, ponieważ oczekuje pełnego typu i nie określa całego typu, podczas gdy CardRank.Number(2)tak. Jednak w tym przypadku chcę, aby pasowała do dowolnej liczby; nie tylko konkretny.

Oczywiście mogę użyć instrukcji switch, ale celem wdrożenia ==operatora było uniknięcie tego rozwlekłego rozwiązania:

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

Czy istnieje sposób, aby porównać wyliczenie z powiązanymi wartościami, ignorując skojarzoną z nim wartość?

Uwaga: zdaję sobie sprawę, że mógłbym zmienić wielkość liter w ==metodzie na case (.Number, .Number): return true, ale chociaż zwracałoby to prawdę poprawnie, moje porównanie nadal wyglądałoby tak, jakby było porównywane z określoną liczbą ( number == CardRank.Number(2); gdzie 2 jest wartością fikcyjną), a nie z dowolną liczbą ( number == CardRank.Number).

Rozsądne
źródło
1
Można zmniejszyć Jack, Queen, King, Aceprzypadki, w ==realizacji operatora po prostu:case (let x, let y) where x == y: return true
Alexander - dozbrojenie Monica

Odpowiedzi:

72

Edycja: jak wskazuje Etan, możesz pominąć (_)dopasowanie z symbolem wieloznacznym, aby użyć go bardziej czysto.


Niestety nie wierzę, że istnieje łatwiejszy sposób niż twoje switchpodejście w Swift 1.2.

W Swift 2 możesz jednak użyć nowego if-casedopasowania do wzorca:

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

Jeśli chcesz uniknąć szczegółowości, możesz rozważyć dodanie isNumberobliczonej właściwości do wyliczenia, która implementuje instrukcję switch.

Ronald Martin
źródło
1
Jest to świetne do sterowania przepływem, ale jest do bani, gdy potrzebujesz prostego wyrażenia, np assert(number == .Number). : . Mam tylko nadzieję, że poprawiono to w późniejszych wersjach Swifta. = /
Jeremy
3
Do bani są też takie rzeczy, jak warunki pętli while itp. W języku Swift 3 można usunąć (_), aby kod był bardziej przejrzysty.
Etan
Dzięki @Etan, dodałem to do odpowiedzi. W Swift 2 możesz również pominąć symbol wieloznaczny. Ta odpowiedź została napisana przed udostępnieniem funkcji językowej, więc jeszcze tego nie wiedziałem! :-D
Ronald Martin
Ponadto: jeśli wyliczenie ma pojedynczą wielkość liter ze skojarzonymi wartościami, wzorzec „jeżeli” musi być używany nawet w przypadku przypadków, które nie mają skojarzonych wartości.
Etan,
1
@PeterWarbo, nie możesz wykonać negacji przy użyciu tej składni dopasowywania wzorców. Na razie będziesz musiał wrócić do defaultskrzynki w switchbloku.
Ronald Martin
28

Niestety w Swift 1.x nie ma innego sposobu, więc musisz użyć, switchktóry nie jest tak elegancki jak wersja Swift 2, w której możesz użyć if case:

if case .Number = number {
    //ignore the value
}
if case .Number(let x) = number {
    //without ignoring
}
Qbyte
źródło
3
Niestety, działa to tylko w przypadku ifstwierdzeń, a nie jako wyrażenia.
Raphael
20

W Swift 4.2 Equatablezostaną zsyntetyzowane, jeśli wszystkie powiązane wartości będą zgodne z Equatable. Wszystko, co musisz zrobić, to dodać 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
źródło
18
Chociaż spowoduje to porównanie powiązanych wartości.
Joe
3

Oto prostsze podejście:

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

Nie ma potrzeby przeciążania operatora ==, a sprawdzenie typu karty nie wymaga mylącej składni:

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
źródło
3
Chociaż nie odpowiada na nadrzędne pytanie, IMO jest to bardziej eleganckie rozwiązanie (poza wyliczonymi numerami) w scenariuszach podobnych do OP i nie powinno być odrzucane.
Nathan Hosselton