1

Here's a simple Swift class with 3 fields:

public class Cabbage: Comparable {
    public let someString: String
    public let someInt: Int
    public let someDouble: Double

    public init(_ someString: String, _ someInt: Int, _ someDouble: Double) {
        self.someString = someString
        self.someInt = someInt
        self.someDouble = someDouble
    }

    public static func ==(lhs: Cabbage, rhs: Cabbage) -> Bool {
        if lhs.someString == rhs.someString {
            if lhs.someInt == rhs.someInt {
                if lhs.someDouble == rhs.someDouble {
                    return true
                }
            }
        }
        return false
    }

    public static func <(lhs: Cabbage, rhs: Cabbage) -> Bool {
        if lhs.someString < rhs.someString {
            if lhs.someInt < rhs.someInt {
                if lhs.someDouble < rhs.someDouble {
                    return true
                }
            }
        }
        return false
    }
}

I think my first function, func ==(), is correct. We return true if and only if all the fields are equal.

But I do not think my logic is correct for func <().

For example if lhs.someString == rhs.someString, should I then be comparing lhs.someInt and rhs.someInt?

And if these two are equal, should I also be comparing lhs.someDouble and rhs.someDouble?

Any ideas or recommendations would be much appreciated.

gepree
  • 617
  • 7
  • 9
  • That's entirely up to you. What are you implementing it for, presumably for sorting a collection of the objects? How do you want the objects to be sorted? – Connor Neville Nov 01 '18 at 18:28
  • Use “tuple comparison,” see “Conforming to Comparable” in https://stackoverflow.com/a/37612765/1187415 – Martin R Nov 01 '18 at 18:30
  • FYI - your `==` can be written more easily as `return lhs.someString == rhs.someString && lhs.someInt == rhs.someInt && lhs.someDouble == rhs.someDouble` – rmaddy Nov 01 '18 at 19:10
  • 1
    @rmaddy: Or `return (lhs.someString, lhs.someInt, lhs.someDouble) == (rhs.someString, rhs.someInt, rhs.someDouble)` (as in the referenced answer) – Martin R Nov 01 '18 at 19:17

1 Answers1

4

Yes, it's not sufficient to just check whether lhs.someString < rhs.someString. You also need to determine if lhs.someString > rhs.someString (or if lhs.someString == rhs.someString).

There are many ways to write it correctly. Here's one:

public static func <(lhs: Cabbage, rhs: Cabbage) -> Bool {
    if lhs.someString < rhs.someString { return true }
    if rhs.someString < lhs.someString { return false }
    if lhs.someInt < rhs.someInt { return true }
    if rhs.someInt < lhs.someInt { return false }
    return lhs.someDouble < rhs.someDouble
}

UPDATE

Since Swift 2.2, the standard library has provided comparison operators for tuples of up to six elements (if all the elements are Comparable).

Furthermore, thanks to SE-0283, all tuples will soon (probably in whatever comes after Swift 5.3) conform to Comparable (if their elements all conform to Comparable).

Here's how SE-0283 defines tuple comparison:

Comparing a tuple to a tuple works elementwise:

Look at the first element, if they are equal move to the second element. Repeat until we find elements that are not equal and compare them.

This definition also applies to the existing tuple comparison operators added in Swift 2.2. It's also called lexicographic order.

So we can use tuple comparison to write a shorter version of < for Cabbage:

extension Cabbage: Comparable {
    public static func <(lhs: Cabbage, rhs: Cabbage) -> Bool {
        return (lhs.someString, lhs.someInt, lhs.someDouble)
            < (rhs.someString, rhs.someInt, rhs.someDouble)
    }
}

We can reduce code duplication by extracting a computed property for the comparison key. Then we can also use it to define the == operator. Here's what I mean:

extension Cabbage: Equatable, Comparable {
    var comparisonKey: (String, Int, Double) {
        return (someString, someInt, someDouble)
    }

    public static func ==(lhs: Cabbage, rhs: Cabbage) -> Bool {
        return lhs.comparisonKey == rhs.comparisonKey 
    }

    public static func <(lhs: Cabbage, rhs: Cabbage) -> Bool {
       return lhs.comparisonKey < rhs.comparisonKey
    }
}
rob mayoff
  • 375,296
  • 67
  • 796
  • 848