文章翻譯自 Avoiding near-duplicates in sets, 做者Paul Hudson @twostraws是一名優秀的Swifter。
這是我第一次翻譯,可能有翻譯不到位的地方,若是有任何問題,歡迎反饋。學習學習再學習,加油💪!算法
Julian Schiavo寫道:我想用Set集合來保證個人Array中元素是惟一的,可是Set集合中每一個元素都包含一個Date類型的變量,當兩個不一樣元素僅僅是Date變量不一樣的時候,實際上Set中能夠同時保存這兩個元素,這就出現了重複元素。這種問題該怎麼解決呢?swift
這是個好問題,實際上Swift的協議給咱們提供了很聰明的解決方案。api
首先,咱們先看下下面示例代碼。結構體NewsStory
有三個屬性:id、title、date:數組
struct NewsStory { var id: Int var title: String var date = Date() }
如上代碼所示,結構體實例初始化時候會自動將當前時間賦值給date屬性。函數
咱們能夠用上面的結構體建立三個對象,以下代碼所示:學習
let story1 = NewsStory(id: 1, title: "What's new in Swift 5.1?") let story2 = NewsStory(id: 2, title: "What's new in Swift 6.0?") let story3 = NewsStory(id: 3, title: "What's new in Swift 6.1?")
Julian想要保存這些新的對象到一個Set集合而不是數組中,這是一個很明智的選擇。所以咱們寫下以下的代碼:翻譯
var stories = Set<NewsStory>() stories.insert(story1) stories.insert(story2) stories.insert(story3) print(stories)
如上代碼所示,建立一個保存故事對象的Set,而後將咱們建立的對象添加到Set集合中,而後打印這個Set集合。然而上面的代碼沒法經過編譯:爲了每一個元素在Set中都有惟一的標識,咱們須要讓NewStory
對象遵照Hashable
協議,Hashable協議可以產生惟一的hash值來標識惟一的一個對象。code
Swift語言這點作得很是好,咱們只須要讓一個包含Hashable屬性的類型遵照Hashable
協議便可,Hashable協議會自動幫咱們計算這個對象的哈希值。所以咱們須要更新NewStory
結構體以下:對象
struct NewsStory: Hashable { var id: Int var title: String var date = Date() }
到如今,咱們的代碼終於可以正常的跑起來啦!blog
而後,Julian遇到的問題並無解決,以下代碼因此:
let story4 = NewsStory(id: 1, title: "What's new in Swift 5.1?") stories.insert(story4) print(stories)
當咱們建立一個和已存在對象相同ID和title的NewStory對象,並添加到set集合中,而後打印集合的內容,你會發現如今集合中包含4個對象,而且其中有一個是重複的。
就像前面寫的那樣,當一個類型遵照Hashable協議而且其屬性也都遵照Hashable協議的時候,Swift會幫咱們自動計算這個對象的hash值。計算方法是這樣的:獲取對象中全部屬性的hash值並將它們結合在一塊兒。
所以,咱們覺得兩個對象是相同的,由於他們有相同的ID和title,可是在Swift看來他們是不一樣的,由於他們的date並不相同。
咱們須要作的就是給Swift提供一個自定義的hash計算規則,告訴Swift說"若是兩個stories對象的ID和title是相同的,那麼他們就是相同的,請忽略date屬性。"
爲了自定義hash計算規則,咱們須要在NewStory
中實現兩個方法:一個是自定義計算hash值,兩一個是檢查兩個對象的惟一標識看是否相等。
第一個方法只使用ID來計算一個story對象的hash值,以下所示:
func hash(into hasher: inout Hasher) { hasher.combine(id) }
第二個方法使用運算符重載來實現一個自定義的==
方法來比較兩個story對象是否相同。
static func ==(lhs: NewsStory, rhs: NewsStory) -> Bool { return lhs.id == rhs.id }
到此爲止,完美解決問題!咱們實現Hashable版本比Swift自動生成的方法的版本更快,由於咱們的hash函數只計算了ID的hash值,而Swift的版本計算了全部屬性的hash值。
示例中咱們只使用了id這個屬性值,可是你在項目中也可使用更多的屬性來保證你的對象是不一樣的。
最終NewsStory
代碼以下所示:
struct NewsStory: Hashable { var id: Int var title: String var date = Date() func hash(into hasher: inout Hasher) { hasher.combine(id) } static func ==(lhs: NewsStory, rhs: NewsStory) -> Bool { return lhs.id == rhs.id } }
在咱們的文章結束以前,須要提醒一點, 實際上是Rob Napier的提醒:相等意味着可替換——任何兩個相等的對象在代碼中均可以相互替換。若是你只比較了id
,那就意味着"若是兩個對象有相同的id,可是其它屬性是不一樣的,我不關心其它屬性是什麼樣的,算法能夠自由的返回其中的任意一個。"
最後,也是最重要的一點:若是兩個對象相等(由於自定義的==
返回true),那麼Swift會自由選擇。Swift可能老是選擇第一個對象,也可能老是選擇第二個對象,或者每次隨機選擇兩個中的一個——這種表如今將來的Swift版本中可能會發生改變。記住這點,由於咱們告訴Swift兩個對象是相同的,纔會發生這個問題,若是關於對象的選擇對你來講很重要,你須要注意這個問題。