25

I've had trouble finding/understanding documentation on how to compare enums in Swift by their order of definition. Specifically when I create an enumeration such as

enum EnumType {
    case First,  Second, Third
}

Swift does not allow me to directly compare enums by order, such as

let type1 = EnumType.First
let type2 = EnumType.Second
if type1 < type2 {println("good")} // error

it generates the compile error "cannot invoke '<' with argument list of of type {EnumType, EnumType}. So the only solution I've found is to write my own comparison operators as overloads, such as

enum EnumType : Int {
    case First = 0, Second, Third
}

func <(a: EnumType, b: EnumType) -> Bool {
    return a.rawValue < b.rawValue
}

let type1 = EnumType.First
let type2 = EnumType.Second
if type1 < type2 {println("good")} // Returns "good"

This is all well and good for "heavy weight" enums that have a lot of use and value in my application, but overloading all the operators I might want to use seems excessively burdensome for 'lightweight" enums that I might define on the fly to bring order to some constants for a single small module.

Is there way to do this without writing lots of boilerplate overloading code for every enum type I define in my project? Even better, is there something I'm missing to make Swift automatically provide comparison operators for simple enums that don't have associated types, ie. that are untyped or typed as Int? Swift knows how to compare Ints, so why can't it compare enum Ints?

SafeFastExpressive
  • 3,637
  • 2
  • 32
  • 39
  • 1
    You can use the `hashValue` property, as described in [this answer](http://stackoverflow.com/a/27094973/148357). Be sure to read the last statement :) – Antonio Jan 09 '15 at 21:28

4 Answers4

48

So long as you give your enum an underlying type, it’ll conform to the protocol RawRepresentable.

This means you can write a generic comparison operator for any type that is raw representable, and has a raw type that is comparable, like so:

func <<T: RawRepresentable where T.RawValue: Comparable>(a: T, b: T) -> Bool {
    return a.rawValue < b.rawValue
}

which will mean your enum will automatically have a < operator:

enum E: Int {  // this would work with Double and String also
    // btw, no need to give a seed value of 0,
    // that happens automatically for Ints
    case A, B, C, D, E
}

E.A < E.C  // returns true

The only bit of boilerplate you’ll still have to do is tag your enum as Comparable in case you want to use it with generic algorithms that require that:

extension E: Comparable { }
// (no need for anything else - requirements are already fulfilled)

let a: [E] = [.C, .E, .A]
let b = sorted(a)
// b will now be [.A, .C, .E]

Making it conform to Comparable will also give it <=, >, and >= operators automatically (supplied by the standard library).

Airspeed Velocity
  • 40,491
  • 8
  • 113
  • 118
  • 12
    Or you could just write the enum as enum E : Int, Comparable { .. } No sense in having a random extension line hanging out there, IMO. – Hobbes the Tige May 03 '16 at 17:35
  • Does this approach return correct order for string enum (ex. case "Z", case "A")? I suggest, comparison operator will return "Z" > "A", what is incorrect for our case. – brigadir Jul 22 '16 at 13:36
  • While this was a good solution at the time the question was asked, it's much neater to do this with conditional conformance as in my answer [here](https://stackoverflow.com/a/55409389/600871) – David Monagle Nov 27 '19 at 04:46
15

This is to some extent the same answer as the OP proposed himself. It does involve a bit of boilerplate code for every enum that you want to be comparable, but I prefer this than having some external magic function that provides comparable to all enums. That can cause problems if you do a quick copy-and-paste from one program to another, and then the enum doesn't work and you can't remember why.

public enum LogLevel: Int, Comparable {
    case verbose
    case debug
    case info
    case warning
    case error
    case severe

    // Implement Comparable
    public static func < (a: LogLevel, b: LogLevel) -> Bool {
        return a.rawValue < b.rawValue
    }
}

EDIT:

This is in response to a comment by @JasonMoore.

Comparable does not require ==. That is required by Equatable, and the Swift standard library automatically provides Equatable for most kinds of enums.

http://www.jessesquires.com/blog/swift-enumerations-and-equatable/

As for >, <= and >=, the Apple documentation says that they are required by Comparable, but that a default implementation is provided (based on use of == and <, I assume).

https://developer.apple.com/documentation/swift/comparable

Here's a bit of code that I ran in the IBM Swift Sandbox - it compiles and runs fine with the above definition.

let a : LogLevel = LogLevel.verbose
let b : LogLevel = LogLevel.verbose
let c : LogLevel = LogLevel.warning

print(a == b)  // prints true
print(a > c)  // prints false
print(a <= c)  // prints true
RenniePet
  • 11,420
  • 7
  • 80
  • 106
  • 2
    You need to add the methods `<=`, `>=` and `>` to conform to Comparable. It's an annoying amount of boilerplate. – Jason Moore May 10 '17 at 19:05
  • 3
    You're right. `Equatable` is automatically added automatically to enums (unless you have an enum case with a variable). And you can conform to Comparable by only implementing `<`. Thanks for clarifying that. (Should I delete my comment above?) – Jason Moore Jun 21 '17 at 13:21
  • 1
    @JasonMoore Why not leave your comment for the sake of posterity? :-) – RenniePet Jun 21 '17 at 13:31
  • It helped me compare my string based enum. Worked great. Just needed to add one implementation function :) – Hasaan Ali Dec 28 '17 at 13:13
7

In newer versions of Swift you can create a protocol to achieve this, without the need for generic globals. This also gives you the ability to choose which enums this affects.

/// Allows a raw enum type to be compared by the underlying comparable RawValue
public protocol RawComparable : Comparable where Self : RawRepresentable, RawValue: Comparable {
}

extension RawComparable {
    public static func < (lhs: Self, rhs: Self) -> Bool {
        return lhs.rawValue < rhs.rawValue
    }
}

To use this it's as simple as adding the RawComparable protocol to the enum type:

enum EnumType : Int, RawComparable {
    case First = 0, Second, Third
}
David Monagle
  • 1,701
  • 16
  • 19
6

Comparing enums as the OP wanted will be possible from Swift 5.3 onwards.

It works as below (from proposal):

enum Brightness: Comparable {
    case low
    case medium
    case high
}

let expectedBrightness = Brightness.low
let actualBrightness = Brightness.high

if actualBrightness > expectedBrightness {
    // Do something
}

More info and examples here.

shemsu
  • 1,066
  • 9
  • 17