OrdererSet updating and removing equal objects with different order question
Fabezi opened this issue · comments
I don't fully understand the behaviour below or is this a bug? Updating and removing equal objects with different order fails or succeeds if the comparable object is below or above the other object in set.
Thanks.
import Foundation
import XCTest
class DummyObject: Comparable
{
let name:String
let order:Int
init(name:String, order:Int) {
self.name = name
self.order = order
}
static func ==(a: DummyObject, b: DummyObject) -> Bool { return a.name == b.name }
static func <(a: DummyObject, b: DummyObject) -> Bool { return a.order > b.order }
}
class RandomTests: XCTestCase {
func testSet()
{
var testSet = SortedSet<DummyObject>()
// Creates dummy objects with name and order
let createObject: (Int, Int) -> DummyObject = { index, order in
let newName = "name \(index)"
let newOrder = order
return DummyObject(name: newName, order: newOrder)
}
// Inserts and logs update or replace
let insertObject: (DummyObject) -> Void = { object in
if (testSet.update(with: object) != nil) {
print("updated", object.name, object.order);
} else {
print("inserted", object.name, object.order);
}
}
// Removes and logs remove or not found
let removeObject: (DummyObject) -> Bool = { object in
if (testSet.remove(object) != nil) {
print("removed", object.name, object.order);
return true
} else {
print("could not find \(object.name, object.order) to remove");
return false
}
}
let object0 = createObject(0, 100)
let object1 = createObject(1, 101)
let object2 = createObject(2, 102) // Update test
let object3 = createObject(2, 103) // Update test
insertObject(object0)
insertObject(object1)
insertObject(object2) // Update test
insertObject(object3) // update test
// Update object object2 with comparable object with different order
// -> (FAILS WITH 101 BUT NOT WITH 103, ACTUAL OBJECT IN SET IS 102)
XCTAssertEqual(testSet.count, 3, "object2 should've been replaced by object3 and not inserted")
// Remove object 3 with comparable object with different order
// -> (FAILS WITH 102 BUT NOT WITH 104, ACTUAL OBJECT IN SET IS 103)
let likeObject3 = createObject(2, 104)
XCTAssert(removeObject(likeObject3), "object3 should've been removed")
// Remove object 1 with comparable object with different order (FAILS)
// -> (FAILS)
let likeObject1 = createObject(1, 10)
XCTAssert(removeObject(likeObject1), "object1 should've been removed")
}
}
The bug is in DummyObject
's implementation of Comparable
; in particular, it does not implement a strict total ordering. For example, object2 < object3
and object2 == object3
are both true, which is a clear violation of Comparable
requirements.
Such a broken ordering relation will confuse any algorithm that uses Comparable
. For example, you'll see that Array.sort
will have trouble sorting your values, too.
In a correct implementation of Comparable
, both ==
and <
need to look at the same properties.
For example, this is a valid implementation:
static func ==(a: DummyObject, b: DummyObject) -> Bool { return a.name == b.name }
static func <(a: DummyObject, b: DummyObject) -> Bool { return a.name < b.name }
This is another option:
static func ==(a: DummyObject, b: DummyObject) -> Bool { return a.order == b.order }
static func <(a: DummyObject, b: DummyObject) -> Bool { return a.order > b.order }
And here is a third:
static func ==(a: DummyObject, b: DummyObject) -> Bool { return (a.name, a.order) == (b.name, b.order) }
static func <(a: DummyObject, b: DummyObject) -> Bool { return (a.name, -a.order) < (b.name, -b.order) }
(Of course, there are many more options to define these correctly.) The one to choose depends on the semantics that you're trying to achieve.
You can't mix and match definitions of ==
and <
from these solutions; they are inherently interrelated and can't be separated from each other.
@lorentey Thank you! Of course.