0

I'd like to build a hashable value to be used for my dictionary's key. It should consist of a structure with two strings and an NSDate. I'm not sure I built my hashValue getter correctly below:

// MARK: comparison function for conforming to Equatable protocol
func ==(lhs: ReminderNotificationValue, rhs: ReminderNotificationValue) -> Bool {
    return lhs.hashValue == rhs.hashValue
}
struct ReminderNotificationValue : Hashable {
    var notifiedReminderName: String
    var notifiedCalendarTitle: String
    var notifiedReminderDueDate: NSDate

var hashValue : Int {
    get {
        return notifiedReminderName.hashValue &+ notifiedCalendarTitle.hashValue &+ notifiedReminderDueDate.hashValue
    }
}

init(notifiedReminderName: String, notifiedCalendarTitle: String, notifiedReminderDueDate: NSDate) {
    self.notifiedReminderName = notifiedReminderName
    self.notifiedCalendarTitle = notifiedCalendarTitle
    self.notifiedReminderDueDate = notifiedReminderDueDate
}
}


var notifications: [ReminderNotificationValue : String] = [ : ]

let val1 = ReminderNotificationValue(notifiedReminderName: "name1", notifiedCalendarTitle: "title1", notifiedReminderDueDate: NSDate())
let val2 = ReminderNotificationValue(notifiedReminderName: "name1", notifiedCalendarTitle: "title1", notifiedReminderDueDate: NSDate())

notifications[val1] = "bla1"
notifications[val2] = "bla2"

notifications[val2]   // returns "bla2". 
notifications[val1]   // returns "bla1". But I'd like the dictionary to overwrite the value for this to "bla2" since val1 and val2 should be of equal value.
Daniel
  • 3,758
  • 3
  • 22
  • 43

1 Answers1

1

The problem is not your hashValue implementation, but the == function. Generally, x == y implies x.hashValue == y.hashValue, but not the other way around. Different objects can have the same hash value. Even

var hashValue : Int { return 1234 }

would be an ineffective, but valid hash method.

Therefore in ==, you have to compare the two objects for exact equality:

func ==(lhs: ReminderNotificationValue, rhs: ReminderNotificationValue) -> Bool {
    return lhs.notifiedReminderName == rhs.notifiedReminderName
    && lhs.notifiedCalendarTitle == rhs.notifiedCalendarTitle
    && lhs.notifiedReminderDueDate.compare(rhs.notifiedReminderDueDate) == .OrderedSame
}

Another problem in your code is that the two invocations of NSDate() create different dates, as NSDate is an absolute point in time, represented as a floating point number with sub-second precision.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Thanks. Actually, it seems that if I pass the same date then the expected behaviour seems to work. Even with my == function. Why is that? – Daniel Jun 19 '16 at 09:52
  • @Daniel: It works by chance. The hash value of a string is a 64-bit number, so not all strings can have the same hash. But it may be difficult to find an actual example of two strings having the same hash. – Note that for example, `NSArray` uses just the number of elements as a hash. – Martin R Jun 19 '16 at 09:55
  • No matter how much I try it the expected behaviour works perfectly with my original implementation. I accept your solution and thanks for it. But I just want to understand why something that shouldn't work seems to work no matter how often I try it with different test cases. – Daniel Jun 19 '16 at 09:58
  • @Daniel: Hashes are usually created in a way such that it is "unlikely" that two different strings have the same hash. But there are more than 2^64 strings, so it is not impossible (but difficult to find a concrete example). – For a simpler example demonstrating the problem, try `print([1,2,3].hashValue == [4,5,6].hashValue)`. – Martin R Jun 19 '16 at 10:05
  • I understand that for arrays it obviously doesn't work. It's weird though that print("bla1".hashValue == "bla2".hashValue) works. Why wouldn't all strings have a unique hashValue in Swift out of the box? That doesn't seem to be impossible to achieve mathematically. Anyway, thanks. – Daniel Jun 19 '16 at 10:12
  • 1
    @Daniel: There are more than 2^64 different strings possible, but only 2^64 different hash values. The "pigeon-hole principle" shows that there must be different strings with the same hash value. – Martin R Jun 19 '16 at 10:15