How to Compare Enum With Associated Values by Ignoring Its Associated Value in Swift

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

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.

How to Compare Enum Ignoring Associated Values?

Use the below if case statement instead:

enum Example {
case one(value: String)
case two(otherValue: Int)
}

var test = Example.one(value: "A String")

if case Example.one(value: _) = test { // Works
// Do Something
}

Comparing two enum variables regardless of their associated values

Updated approach:

I think there's no native support for this. But you can achieve it by defining a custom operator (preferrably by using a protocol, but you can do it directly as well). Something like this:

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

extension DataType: EnumTypeEquatable {
static func ~=(lhs: DataType, rhs: DataType) -> Bool {
switch (lhs, rhs) {
case (.one, .one),
(.two, .two):
return true
default:
return false
}
}
}

And then use it like:

let isTypeEqual = DataType.One(value: 1) ~= DataType.One(value: 2)
print (isTypeEqual) // true




Old approach:

protocol EnumTypeEquatable {
var enumCaseIdentifier: String { get }
}

extension DataType: EnumTypeEquatable {
var enumCaseIdentifier: String {
switch self {
case .one: return "ONE"
case .two: return "TWO"
}
}
}

func ~=<T>(lhs: T, rhs: T) -> Bool where T: EnumTypeEquatable {
return lhs.enumCaseIdentifier == rhs.enumCaseIdentifier
}

The older version depends on Runtime and might be provided with default enumCaseIdentifier implementation depending on String(describing: self) which is not recommended. (since String(describing: self) is working with CustromStringConvertible protocol and can be altered)

How do I know the type of an enum with associated value in a generic way?

You just need to make your enumeration conform to Equatable and compare the associated values for equality:

enum MyEnum: Equatable {
case one
case two
case three(user: String)
}


var array: [MyEnum] = []
func add(_ value: MyEnum) {
if array.contains(value) { return }
array.append(value)
}


add(.one)
add(.one)
array
add(.three(user: "a"))
add(.three(user: "b"))
add(.three(user: "a"))

array[0] // one
array[1] // three(user: "a")
array[2] // three(user: "b")

To check if the last element is of case .three:

if case .three = array.last {
print(true)
}

Swift - How to compare enum with associated values?

You can use case to access the associated values of an enum.

switch (lhs, rhs) {
case (.perfectMatch(let a, let b), .perfectMatch(let c, let d):
// check equality of associated values
return a == c && b == d
// other cases...
}

You can also access associated values like this using an if statement:

if case .perfectMatch(let a, let b) = handler.matchType {
// do something with a and b
}

How to do an if-else comparison on enums with arguments

The trick is to not actually check with == but rather use the case keyword in conjunction with a single = in your if statement. This is a little counter intuitive in the beginning but just like if let, you get used to it pretty fast:

enum Normal {
case one
case two, three
}

enum NormalRaw: Int {
case one = 1
case two, three
}

enum NormalArg {
case one(Int)
case two, three
}


let normalOne = Normal.one
let normalRawOne = NormalRaw.one
let normalArgOne = NormalArg.one(1)

if case .one = normalOne {
print("A normal one") //prints "A normal one"
}

if case .one = normalRawOne {
print("A normal \(normalRawOne.rawValue)") //prints "A normal 1"
}

if case .one(let value) = normalArgOne {
print("A normal \(value)") //prints "A normal 1"
}

The point is that in Swift you only get equation of enums for free if your enum uses a raw type or if you have no associated values (try it out, you can't have both at the same time). Swift however does not know how to compare cases with associated values - I mean how could it? Let's look at this example:

Normal.one == .one //true
Normal.one == .two //false

NormalRaw.one == .one //true
NormalRaw.one == .two //false

NormalArg.one(1) == .one(1) //Well...?
NormalArg.one(2) == .one(1) //Well...?
NormalArg.one(1) == .two //Well...?

Maybe this makes it clearer why this cannot work out of the box:

class Special {
var name: String?
var special: Special?
}

enum SpecialEnum {
case one(Special)
case two
}

var special1 = Special()
special1.name = "Hello"

var special2 = Special()
special2.name = "World"
special2.special = special1

SpecialEnum.one(special1) == SpecialEnum.one(special2) //Well...?

So if you want enums with associated values, you'll have to implement Equatable protocol in your enum by yourself:

enum NormalArg: Equatable {
case one(Int)
case two

static func ==(lhs: NormalArg, rhs: NormalArg) -> Bool {
switch (lhs, rhs) {
case (let .one(a1), let .one(a2)):
return a1 == a2
case (.two,.two):
return true
default:
return false
}
}
}

How to test equality of Swift enums with associated values

Swift 4.1+

As @jedwidz has helpfully pointed out, from Swift 4.1 (due to SE-0185, Swift also supports synthesizing Equatable and Hashable for enums with associated values.

So if you're on Swift 4.1 or newer, the following will automatically synthesize the necessary methods such that XCTAssert(t1 == t2) works. The key is to add the Equatable protocol to your enum.

enum SimpleToken: Equatable {
case Name(String)
case Number(Int)
}
let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)

Before Swift 4.1

As others have noted, Swift doesn't synthesize the necessary equality operators automatically. Let me propose a cleaner (IMHO) implementation, though:

enum SimpleToken: Equatable {
case Name(String)
case Number(Int)
}

public func ==(lhs: SimpleToken, rhs: SimpleToken) -> Bool {
switch (lhs, rhs) {
case let (.Name(a), .Name(b)),
let (.Number(a), .Number(b)):
return a == b
default:
return false
}
}

It's far from ideal — there's a lot of repetition — but at least you don't need to do nested switches with if-statements inside.



Related Topics



Leave a reply



Submit