Swift 中實現字典

2021-07-26 13:29:39 字數 4047 閱讀 7001

雖然 swift 原生的字典型別實現的 很複雜 (毫無疑問是為了效能),但是我們可以利用 swift 提供的工具寫出漂亮簡潔的實現。我們從乙個簡單的實現開始,並且逐步新增功能。

我們簡要看一下字典的工作原理:它通過任意型別的關鍵字來設定和獲取值。這些值常常儲存在乙個陣列中,當然也可以儲存在樹型結構中。由於我還不太清楚以樹作為字典儲存結構的工作原理,所以這篇文章中我們主要探索乙個由陣列儲存值的字典。

由於我們字典中的值是由陣列儲存,所以我們需要將給定的關鍵字轉換成整數,然後讓這個整數落在陣列範圍內。這兩個方法乙個是雜湊函式,乙個是取模操作。通過雜湊函式,我們可以將關鍵字(通常是字串,也可以是遵循 hashable 協議的任意型別)轉換成乙個數值,然後根據陣列的長度,通過取模操作我們會得到乙個固定位置(consistent slot)來設定和獲取值。

譯者注: 這裡的 consistent slot 是指,字典的 key,通過這種 hash 和取模運算,每次都會得到相同的數值,從而確保相同的 key 值,每次取得的 value 總是保持一致性。

我從 mike ash 的文章 讓我們構建 nsmutabledictionary 獲取了一些靈感,特別是重新計算陣列容量的規則。

讓我們開始吧。我們知道 key 和 value 都應該支援泛型,而且 key 必須遵循 hashable 協議。遵循 hashable 協議的也一定同時遵循 equatable 協議。(譯者注, hashable 協議繼承自 equatable 協議)

struct dictionary

set }}

複製**

這是我們型別的基本結構。我們知道我們的陣列必須是支援泛型的,但是還不知道具體的值是什麼。由於兩個不同的關鍵字經過雜湊和取模操作後可能會指向陣列的同乙個位置,這樣就會造成衝突,因此每個佔位物件需要支援儲存多個值。現在讓來我們設計 placeholder :

struct placeholder

複製**

這個物件儲存很多經過雜湊和取模操作後指向字典同一位置的鍵和值。如果我們很好的設計了字典,那麼每個 placeholder 包含的鍵值對將不會超過乙個,但是超過乙個的情況也會發生。乙個比較好的實現是用乙個鍊錶來儲存 placeholder 的屬性 values 。我將留作乙個練習給讀者。

現在我們大體知道了 placeholder 是什麼樣的,接下來我們看看我們的儲存器是什麼樣的。

private var storage = array(count: 8, repeatedvalue: placeholder())

複製**

初始時我們給陣列乙個隨機的大小。最好選擇 2 的整數冪,因為對 2 的冪取模要比其他數更快。除非我們實現如何重新計算陣列容量,否則取多大都沒有關係。 array(count:repeatedvalue:) 構造方法使得陣列中的每個位置都有乙個可以新增值的佔位物件。

為了設定字典的值,我們需要對鍵進行雜湊(然後取絕對值,因為雜湊有時會返回負值),然後根據陣列大小進行取模操作。

set

複製**

在真正給字典新增值之前,我們需要

a)確保新值(newvalue)不為 nil

b)找到在 position 位置的佔位物件

c)將鍵和值新增到佔位物件中。

set

let position = abs(key.hashvalue) % storage.count}

複製**

這就是基本滿足我們需求的設定方法的實現。(我忽略掉了一些小的細節,比如當你試著對同乙個鍵設定兩次時會發生什麼。我們很快會處理這種情況。)

取值的過程相當簡單。對鍵進行雜湊,取絕對值,取模操作,然後我們獲取了在那個位置上的佔位物件。我將會把從佔位物件中獲取值的過程交給佔位物件自己去做。

get

複製**

真正神奇的是在下面這個方法。我們需要找到第乙個包含那個鍵的鍵值對。在 swift 3 中我們可以使用 sequencetype 協議中的 first(where:) 方法來實現,但目前我們使用普通的寫法 lazy.filter( /* block */ ).first 來獲取鍵值對。

func firstvalue(matchingkey key: key) -> value? ).first

//...}

複製**

一旦我們拿到表示鍵值對的元組,我們就可以直接呼叫 .1 獲取對應的值。

func firstvalue(matchingkey key: key) -> value? ).first?.1}

複製**

這幾乎是 dictionary 的基本實現了。23 行 swift **。所有**在這個 gist 中。

還有幾個有意思的事情還沒有實現。首先,需要有惰性求值的 generate() ,這樣的話 dictionary 就遵循 sequencetype 協議了。

譯者注: 遵循 sequencetype 協議之後,就可以使用 for (key, value) in 的方式來遍歷字典了

extension dictionary: sequencetype ).generate()}}

複製**

接下來是刪除鍵。首先,從佔位物件中刪除:

extension placeholder )}}

複製**

然後從字典中刪除

extension dictionary }

複製**

在設定新值時先呼叫 remove(key:) 。這樣確保同乙個鍵不會對映到不同的值上。

最後,我們來看看如何重新計算陣列容量。當字典包含非常多物件時,它的實際儲存結構需要調整自身大小,可以把「非常多物件」看成乙個大於2/3或者3/4的負載係數(物件數量除以陣列長度)。我選擇 0.7。

extension dictionary

var count: int ).count

}var currentloadfactor: double }

複製**

( count 的實現還是懶求值。理想情況下, dictionary 會記錄有多少物件被新增和被刪除,但實際上很難記錄)

mutating func resizeifneeded() }

複製**

這就是 swift 的值語法非常奇怪的地方。 mike ash 在 構建nsmutabledictionary 文章中,建立了乙個固定大小的字典,並且把它封裝成乙個可變大小的字典。當需要調整大小時,他建立乙個新的固定大小的字典(大小加倍),然後手動的把所有元素拷貝到新的字典當中。

在 swift 中我們不必如此。swift 中的結構體物件賦值給乙個變數會進行一次完整拷貝。

let olddictionary = self

//...

複製**

一旦我們拷貝完字典,我們可以重置 storage 變數為原先陣列大小的兩倍。(大小加倍確保陣列大小仍然是2的冪。)

//...

storage = array>(count: size*2, repeatedvalue: placeholder())

//...

複製**

一旦設定完成,字典會變成空的,我們必須把 olddictionary 的所有值拷貝到當前字典中:

//...

for (key, value) in olddictionary

複製**

這是完整的 resizeifneeded 函式:

mutating func resizeifneeded() }}

複製**

在 swift 的結構體中, self 可以訪問當前型別的值和函式,但是它也是可變的。你可以對它設定新值,拷貝它,或者就把它當成另乙個變數的引用。

兩周前 我開玩笑說 swift 是比 objective-c ,ruby 或者 python 更動態的語言。因為你可以改變它的錯誤行為,但是這裡有另一種情況,即你可以在 swift 中修改一些在 objective-c 中不能修改的東西: self 本身的引用。我們本可以在該方法中這樣寫 self = dictionary(size: size*2) ,這在 swift 是絕對有效的。對於那些覺得物件標識是最重要的物件導向開發者而言,這非常奇怪。

包含排序,刪除,調整陣列大小的完整的實現可以在 gist 中找到。除了 count/generate() 懶求值的實現,我非常喜歡這個小工程的表達方式。

在 Swift 中實現字典

雖然 swift 原生的字典型別實現的很複雜 毫無疑問是為了效能 但是我們可以利用 swift 提供的工具寫出漂亮簡潔的實現。我們從乙個簡單的實現開始,並且逐步新增功能。我們簡要看一下字典的工作原理 它通過任意型別的關鍵字來設定和獲取值。這些值常常儲存在乙個陣列中,當然也可以儲存在樹型結構中。由於我...

Swift中的陣列和字典

swift對陣列和字典的定義和使用語法,體現了現代語言的特色。熟悉後會感覺更加簡練和易用。定義和宣告 var a array 等同於 var a string let animals giraffe cow doggie let animal animals 4 crash 陣列越界 for ani...

Swift字典集合

字典表示一種非常複雜的集合,允許按照某個鍵來訪問元素。字典是由兩部分集合構成的,乙個是鍵 key 集合,乙個是值 value 集合。鍵集合是不能有重複元素的,而值集合是可以重複的,鍵和值是成對出現的。如下圖所示是字典結構的 學號與學生 集合,學號是鍵集合,不能重複,學生是值集合,可以重複。提示 字典...