本文是对 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
循环。