如何在Set集合中避免重複元素

文章翻譯自 Avoiding near-duplicates in sets, 做者Paul Hudson @twostraws是一名優秀的Swifter。
這是我第一次翻譯,可能有翻譯不到位的地方,若是有任何問題,歡迎反饋。學習學習再學習,加油💪!算法

img

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兩個對象是相同的,纔會發生這個問題,若是關於對象的選擇對你來講很重要,你須要注意這個問題。

相關文章
相關標籤/搜索