Reputation: 677
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.
Upvotes: 1
Views: 194
Reputation: 385670
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
}
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
}
}
Upvotes: 4