本文是對 Swift Algorithm Club 翻譯的一篇文章。 Swift Algorithm Club是 raywenderlich.com網站出品的用Swift實現算法和數據結構的開源項目,目前在GitHub上有18000+⭐️,我初略統計了一下,大概有一百左右個的算法和數據結構,基本上常見的都包含了,是iOSer學習算法和數據結構不錯的資源。 🐙andyRon/swift-algorithm-club-cn是我對Swift Algorithm Club,邊學習邊翻譯的項目。因爲能力有限,如發現錯誤或翻譯不妥,請指正,歡迎pull request。也歡迎有興趣、有時間的小夥伴一塊兒參與翻譯和學習🤓。固然也歡迎加⭐️,🤩🤩🤩🤨🤪。 本文的翻譯原文和代碼能夠查看🐙swift-algorithm-club-cn/Bloom Filtergit
布隆過濾器(Bloom Filter)github
布隆過濾器是一種節省空間的數據結構,能夠告訴您元素是否存在於集合中。算法
這是一個機率數據結構:對布隆過濾器的查詢返回false
,意味着該元素確定不在集合中,或者是true
,這意味着元素可能在集合中。swift
誤報的可能性很小,即便查詢返回true
,元素實際上也可能不在集合中。 可是永遠不會有任何漏報:若是查詢返回false
,你能夠保證,那麼元素確實不在集合中。數組
因此布隆過濾器告訴你,「絕對不是」或「多是的」。緩存
起初,這彷佛不太有用。 可是,它在緩存過濾和數據同步等應用程序中很重要。數據結構
布隆過濾器優於哈希表的一個優勢是前者保持恆定的內存使用和恆定時間插入和搜索。 對於具備大量元素的集合,哈希表和布隆過濾器之間的性能差別很大,若是您不須要保證不存在誤報,則它是可行的選項。函數
**注意:**與哈希表不一樣,布隆過濾器不存儲實際對象。 它只會記住你看過的對象(有必定程度的不肯定性)以及你沒有看過的對象。性能
布隆過濾器本質上是一個固定長度的位向量,一個位數組。 當咱們插入對象時,咱們將其中一些位設置爲1
,當咱們查詢對象時,咱們檢查某些位是0
仍是1
。 兩個操做都使用哈希函數。學習
要在過濾器中插入元素,可使用多個不一樣的哈希函數對元素進行哈希。 每一個哈希函數返回一個咱們映射到數組中索引的值。 而後,咱們將這些索引處的位設置爲1
或true
。
例如,假設這是咱們的位數組。 咱們有17位,最初它們都是0
或false
:
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
複製代碼
如今咱們要在布隆過濾器中插入字符串"Hello world!"
。 咱們對此字符串應用兩個哈希函數。第一個給出值1999532104120917762。咱們經過取數組長度的模數將此哈希值映射到數組的索引:1999532104120917762 % 17 = 4
。 這意味着咱們將索引4處的位設置爲1
或者true
:
[ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
複製代碼
而後咱們再次散列原始字符串,但此次使用不一樣的散列函數。 它給出哈希值9211818684948223801。取17的模數爲12,咱們也將索引12處的位設置爲1
:
[ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 ]
複製代碼
這兩個1位足以告訴布隆過濾器它如今包含字符串 "Hello world!"
。 固然,它不包含實際的字符串,因此你不能要求布隆過濾器,「給我一個你包含的全部對象的列表」。 全部它都是一堆1和0。
相似於插入,查詢是經過首先對指望值進行哈希來實現的,該指望值給出幾個數組索引,而後檢查這些索引處的全部位是否爲1
。 若是其中一個位不是1
,則沒法插入該元素,而且查詢返回false
。 若是全部位都是1
,則查詢返回true
。
例如,若是咱們查詢字符串"Hello WORLD"
,那麼第一個哈希函數返回5383892684077141175,其中取17的模是12。該位是1
。可是第二個哈希函數給出5625257205398334446,它映射到數組索引9。該位爲0
。 這意味着字符串"Hello WORLD"
不在過濾器中,查詢返回false
。
第一個哈希函數映射到1
位的事實是巧合(它與兩個字符串以"Hello "
開頭的事實無關)。 太多這樣的巧合可能致使「碰撞」。 若是存在衝突,即便未插入元素,查詢也可能錯誤地返回true
- 致使前面提到的誤報問題。
假設咱們插入了一些其餘元素,"Bloom Filterz"
,它設置了第7位和第9位。如今數組看起來像這樣:
[ 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0 ]
複製代碼
若是再次查詢"Hello WORLD"
,則過濾器會看到第12位爲true,第9位如今也爲true。 它報告說"Hello WORLD"
確實出如今集合中,即便它不是......由於咱們從未插入過那個特定的字符串。這是誤報。這個例子說明了爲何布隆過濾器永遠不會說「絕對是」,只有「多是」。
您能夠經過使用具備更多位的數組並使用其餘哈希函數來解決此類問題。 固然,使用的哈希函數越多,布隆過濾器就越慢。 因此你必須取得平衡。
使用布隆過濾器沒法刪除,由於任何一個位均可能屬於多個元素。 一旦你添加了一個元素,它就在那裏。
布隆過濾器的性能是O(k),其中 k是哈希函數的數量。
代碼很是簡單。 內部位數組在初始化時設置爲固定長度,初始化後不能進行突變。
public init(size: Int = 1024, hashFunctions: [(T) -> Int]) {
self.array = [Bool](repeating: false, count: size)
self.hashFunctions = hashFunctions
}
複製代碼
應在初始化時指定幾個哈希函數。 您使用哪些哈希函數將取決於您將添加到集合的元素的數據類型。 你能夠在playground測試中看到一些例子 - 字符串的djb2
和sdbm
哈希函數。
插入只是將所需的位翻轉爲true
:
public func insert(_ element: T) {
for hashValue in computeHashes(element) {
array[hashValue] = true
}
}
複製代碼
這使用computeHashes()
函數,它循環遍歷指定的hashFunctions
並返回索引數組:
private func computeHashes(_ value: T) -> [Int] {
return hashFunctions.map() { hashFunc in abs(hashFunc(value) % array.count) }
}
複製代碼
並查詢檢查以確保哈希值處的位爲true
:
public func query(_ value: T) -> Bool {
let hashValues = computeHashes(value)
let results = hashValues.map() { hashValue in array[hashValue] }
let exists = results.reduce(true, { $0 && $1 })
return exists
}
複製代碼
若是你來自另外一種命令式語言,你可能會注意到exists
賦值中的不尋常語法。 當Swift使代碼更加簡潔和可讀時,Swift使用函數範例,在這種狀況下,reduce
是一種更加簡潔的方法來檢查全部必需的位是否爲true
而不是用for
循環。