Як порівняти перерахування із пов’язаними значеннями, ігноруючи пов’язане з ним значення у 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
}

Чи є спосіб порівняти перелік із пов'язаними значеннями, ігноруючи пов'язане з ним значення?

Примітка: Я усвідомлюю, що міг би змінити регістр ==методу на case (.Number, .Number): return true, але, хоча це поверне істину правильно, моє порівняння все одно буде виглядати як його порівняння з певним числом ( number == CardRank.Number(2); де 2 - фіктивне значення), а не з будь-яким числом ( number == CardRank.Number).


1
Ви можете зменшити Jack, Queen, King, Aceвипадки в ==реалізації оператора просто:case (let x, let y) where x == y: return true
Олександр - відновить Моніку

Відповіді:


72

Редагувати: Як зазначає Ітан, ви можете пропустити (_)збіг підстановки, щоб використовувати це більш чисто.


На жаль, я не вірю, що існує простіший спосіб, ніж ваш switchпідхід у Swift 1.2.

Однак у Swift 2 ви можете використовувати новий if-caseшаблон збігу:

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

Якщо ви хочете уникнути багатослівності, ви можете подумати про додавання isNumberобчислюваної властивості до вашого переліку, що реалізує ваш оператор switch.


1
Це дуже зручно для управління потоком, але це свого роду смокче , коли ви просто хочете вираз простий, наприклад: assert(number == .Number). Я можу лише сподіватися, що це покращено в пізніших версіях Swift. = /
Джеремі

3
Також відстій для таких речей, як умови циклу while тощо. У Swift 3 ви можете видалити (_) для більш чистого коду.
Етан

Дякую @Etan, я додав це до відповіді. Насправді ви також можете пропустити підстановочний знак у Swift 2. Ця відповідь була написана ще до виходу мовної функції, тому я ще цього не знав! :-D
Рональд Мартін

Також: Якщо перерахування має єдиний регістр із пов’язаними значеннями, шаблон if-case потрібно використовувати навіть для тих випадків, які не мають пов’язаних значень.
Етан

1
@PeterWarbo, ти не можеш заперечити цей синтаксис, що відповідає шаблону. Поки що вам доведеться повернутися до defaultсправи у switchблоці.
Рональд Мартін

28

На жаль, у Swift 1.x немає іншого способу, тому вам доведеться використовувати такий, switchякий не такий елегантний, як версія Swift 2, де ви можете використовувати if case:

if case .Number = number {
    //ignore the value
}
if case .Number(let x) = number {
    //without ignoring
}

3
На жаль, це працює лише у ifвисловлюваннях, а не як вираз.
Рафаель

20

У Swift 4.2 Equatableбуде синтезовано, якщо всі відповідні значення відповідають Equatable. Все, що вам потрібно зробити, це додати Equatable.

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

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


18
Хоча це дозволить порівняти пов'язані значення.
Джо

3

Ось простіший підхід:

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

Не потрібно перевантажувати оператор ==, а перевірка типу картки не вимагає заплутаного синтаксису:

let card = CardRank.Jack

if card == CardRank.Jack {
    print("You played a jack")
} else if !card.isFaceCard {
    print("You must play a face card!")
}

3
Незважаючи на те, що воно не відповідає на всебічне питання, IMO є більш елегантним рішенням (перераховуючи цифри в сторону) у сценаріях, подібних до OP, і не повинно бути проти.
Nathan Hosselton

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.