Skip to content

Commit 35a8ed4

Browse files
committed
Add code for Bloom Filter, tests
1 parent 5721ee7 commit 35a8ed4

File tree

3 files changed

+160
-0
lines changed

3 files changed

+160
-0
lines changed

Bloom Filter/BloomFilter.swift

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import Foundation
2+
3+
public class BloomFilter<T> {
4+
private(set) private var arr: [Bool]
5+
private(set) private var hashFunctions: [T -> Int]
6+
7+
public init(size: Int = 1024, hashFunctions: [T -> Int]) {
8+
self.arr = Array<Bool>(count: size, repeatedValue: false)
9+
self.hashFunctions = hashFunctions
10+
}
11+
12+
private func computeHashes(value: T) -> [Int] {
13+
return hashFunctions.map() { hashFunc in
14+
abs(hashFunc(value) % self.arr.count)
15+
}
16+
}
17+
18+
public func insert(toInsert: T) {
19+
let hashValues: [Int] = self.computeHashes(toInsert)
20+
21+
for hashValue in hashValues {
22+
self.arr[hashValue] = true
23+
}
24+
}
25+
26+
public func insert(values: [T]) {
27+
for value in values {
28+
self.insert(value)
29+
}
30+
}
31+
32+
public func query(value: T) -> Bool {
33+
let hashValues = self.computeHashes(value)
34+
35+
// Map hashes to indices in the Bloom filter
36+
let results = hashValues.map() { hashValue in
37+
self.arr[hashValue]
38+
}
39+
40+
// All values must be 'true' for the query to return true
41+
42+
// This does NOT imply that the value is in the Bloom filter,
43+
// only that it may be. If the query returns false, however,
44+
// you can be certain that the value was not added.
45+
46+
let exists = results.reduce(true, combine: { $0 && $1 })
47+
48+
return exists
49+
}
50+
51+
public func isEmpty() -> Bool {
52+
// Reduce list; as soon as the reduction hits a 'true' value, the && condition will fail
53+
return arr.reduce(true) { prev, next in
54+
prev && !next
55+
}
56+
}
57+
58+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import XCTest
2+
import BloomFilter
3+
4+
/* Two hash functions, adapted from
5+
http://www.cse.yorku.ca/~oz/hash.html */
6+
7+
8+
func djb2(x: String) -> Int {
9+
var hash = 5381
10+
11+
for char in x.characters {
12+
hash = ((hash << 5) &+ hash) &+ char.hashValue
13+
}
14+
15+
return Int(hash)
16+
}
17+
18+
func sdbm(x: String) -> Int {
19+
var hash = 0
20+
21+
for char in x.characters {
22+
hash = char.hashValue &+ (hash << 6) &+ (hash << 16) &- hash;
23+
}
24+
25+
return Int(hash)
26+
}
27+
28+
29+
class BloomFilterTests: XCTestCase {
30+
31+
func testSingleHashFunction() {
32+
let bloom = BloomFilter<String>(hashFunctions: [djb2])
33+
34+
bloom.insert("Hello world!")
35+
36+
let result_good = bloom.query("Hello world!")
37+
let result_bad = bloom.query("Hello world")
38+
39+
XCTAssertTrue(result_good)
40+
XCTAssertFalse(result_bad)
41+
}
42+
43+
func testEmptyFilter() {
44+
let bloom = BloomFilter<String>(hashFunctions: [djb2])
45+
46+
let empty = bloom.isEmpty()
47+
48+
XCTAssertTrue(empty)
49+
}
50+
51+
func testMultipleHashFunctions() {
52+
let bloom = BloomFilter<String>(hashFunctions: [djb2, sdbm])
53+
54+
bloom.insert("Hello world!")
55+
56+
let result_good = bloom.query("Hello world!")
57+
let result_bad = bloom.query("Hello world")
58+
59+
XCTAssertTrue(result_good)
60+
XCTAssertFalse(result_bad)
61+
}
62+
63+
func testFalsePositive() {
64+
let bloom = BloomFilter<String>(size: 5, hashFunctions: [djb2, sdbm])
65+
66+
bloom.insert(["hello", "elloh", "llohe", "lohel", "ohell"])
67+
68+
print("Inserted")
69+
70+
let query = bloom.query("This wasn't inserted!")
71+
72+
// This is true even though we did not insert the value in the Bloom filter;
73+
// the Bloom filter is capable of producing false positives but NOT
74+
// false negatives.
75+
76+
XCTAssertTrue(query)
77+
}
78+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>CFBundleDevelopmentRegion</key>
6+
<string>en</string>
7+
<key>CFBundleExecutable</key>
8+
<string>$(EXECUTABLE_NAME)</string>
9+
<key>CFBundleIdentifier</key>
10+
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
11+
<key>CFBundleInfoDictionaryVersion</key>
12+
<string>6.0</string>
13+
<key>CFBundleName</key>
14+
<string>$(PRODUCT_NAME)</string>
15+
<key>CFBundlePackageType</key>
16+
<string>BNDL</string>
17+
<key>CFBundleShortVersionString</key>
18+
<string>1.0</string>
19+
<key>CFBundleSignature</key>
20+
<string>????</string>
21+
<key>CFBundleVersion</key>
22+
<string>1</string>
23+
</dict>
24+
</plist>

0 commit comments

Comments
 (0)