Senseful
Senseful

Reputation: 91891

How to compare Swift enum with associated values by ignoring its associated value?

After reading How to test equality of Swift enums with associated values, I implemented the following 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
    }
}

The following code works:

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

However, this doesn't compile:

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

... and it gives the following error message:

Binary operator '==' cannot be applied to operands of type 'CardRank' and '(Int) -> CardRank'

I'm assuming this is because it's expecting a full type and CardRank.Number does not specify an entire type, whereas CardRank.Number(2) did. However, in this case, I want it to match any number; not just a specific one.

Obviously I can use a switch statement, but the whole point of implementing the == operator was to avoid this verbose solution:

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

Upvotes: 136

Views: 54478

Answers (11)

Eric_WVGG
Eric_WVGG

Reputation: 2947

Building on the if-case pattern, if you need to pluck out the associated object from the enum…

enum Fruit {
  case apple (AppleStruct)
  case banana (BananaStruct)
}

struct AppleStruct {
  func commitOriginalSin()
}

struct BananaStruct {
  func slipAndFall()
}

if case .banana(let fruit) = someFruit {
  fruit.slipAndFall()
}

… and in SwiftUI…

if case .banana(let fruit) = someFruit {
  BananaView(fruit)
}
if case .apple(let fruit) = someFruit {
  AppleView(fruit)
}

Upvotes: -1

Ronald Martin
Ronald Martin

Reputation: 4478

In Swift 2+ you can use the 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.

Upvotes: 125

Junaid Khan
Junaid Khan

Reputation: 1

let cardRank = CardRank.Number(10)    
guard let case .Number(someNumber) = cardRank else {
      return
}
print(someNum)

Upvotes: -2

SmileBot
SmileBot

Reputation: 19662

extension CardRank {
    func isSameCaseAs(_ other: CardRank) -> Bool {
        switch (self, other) {
        case (.Number, .Number),
            (.Jack, .Jack),
            (.Queen, .Queen),
            (.King, .King),
            (.Ace, .Ace):
            return true
        default:
            return false
        }
    }
}

let number = CardRank.Number(1)
let otherNumber = CardRank.Number(2)
number.isSameCaseAs(otherNumber) // true

Just create an extension and ignore the associated types.

Upvotes: 3

lochiwei
lochiwei

Reputation: 1358

From Swift 5.3, you can use the Comparable Enums feature:

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

let cards: [CardRank] = [
    .Queen, .Number(8), .Ace, .Number(3), .King
]

print(cards.sorted())
// [.Number(3), .Number(8), .Queen, .King, .Ace]

Upvotes: -1

Rouger
Rouger

Reputation: 641

What I usually do to compare if two enum cases "match" no matter their associated value is:

I have a protocol Matchable:

protocol Matchable {
  static func ~= (lhs: Self, rhs: Self) -> Bool
}

Then I make enums conform to it:

extension CardRank: Matchable {
  static func ~= (lhs: Self, rhs: Self) -> Bool {
    switch (lhs, rhs) {
      case
        (.number, .number),
        (.jack, .jack),
        (.queen, .queen),
        (.king, .king),
        (.ace, .ace):
        return true
        
      default:
        return false
    }
  }
}

let card1: CardRank = .number(1)
let card2: CardRank = .number(2)
let card3: CardRank = .jack

print(card1 ~= card2) // true
print(card1 ~= card3) // false

Upvotes: 18

Martin
Martin

Reputation: 4765

I didn't want to conform Equatable (it didn't help me either) and I wanted to filter for other cases than a specific one, so instead of simply writing card != .Number I had to write the following. (I adjusted my code to this question.)

enum CardRank {
    ...
    var isNumber: Bool {
       if case .Number = self { return true }
       return false
    }
}

So I can write not a number in a complex condition:

if something && !card.isNumber { ... }

I wish I could just write card != .Number, but the compiler was always complaining with Type of expression is ambiguous without more context. Maybe in an upcoming swift version!

Upvotes: 5

Edward Brey
Edward Brey

Reputation: 41718

You don't need func == or Equatable. Just use an enumeration case pattern.

let rank = CardRank.Ace
if case .Ace = rank { print("Snoopy") }

Upvotes: 2

nambatee
nambatee

Reputation: 1568

In Swift 4.2 Equatable will be synthesized if all your associated values conform to Equatable. All you need to do is add Equatable.

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

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

Upvotes: 31

Mike Taverne
Mike Taverne

Reputation: 9362

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

Upvotes: 5

Qbyte
Qbyte

Reputation: 13263

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
}

Upvotes: 39

Related Questions