Skip to content

Commit 1068934

Browse files
committed
Add hash table
1 parent ad1d46d commit 1068934

File tree

6 files changed

+563
-1
lines changed

6 files changed

+563
-1
lines changed
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
//: Playground - noun: a place where people can play
2+
3+
public struct HashTable<Key: Hashable, Value> {
4+
private typealias Element = (key: Key, value: Value)
5+
private typealias Bucket = [Element]
6+
7+
private var buckets: [Bucket]
8+
private(set) var count = 0
9+
10+
public init(capacity: Int) {
11+
assert(capacity > 0)
12+
buckets = .init(count: capacity, repeatedValue: [])
13+
}
14+
15+
public var isEmpty: Bool {
16+
return count == 0
17+
}
18+
19+
private func indexForKey(key: Key) -> Int {
20+
return abs(key.hashValue) % buckets.count
21+
}
22+
}
23+
24+
extension HashTable {
25+
public subscript(key: Key) -> Value? {
26+
get {
27+
return valueForKey(key)
28+
}
29+
set {
30+
if let value = newValue {
31+
updateValue(value, forKey: key)
32+
} else {
33+
removeValueForKey(key)
34+
}
35+
}
36+
}
37+
38+
public func valueForKey(key: Key) -> Value? {
39+
let index = indexForKey(key)
40+
41+
for element in buckets[index] {
42+
if element.key == key {
43+
return element.value
44+
}
45+
}
46+
return nil // key not in hash table
47+
}
48+
49+
public mutating func updateValue(value: Value, forKey key: Key) -> Value? {
50+
let index = indexForKey(key)
51+
52+
// Do we already have this key in the bucket?
53+
for (i, element) in buckets[index].enumerate() {
54+
if element.key == key {
55+
let oldValue = element.value
56+
buckets[index][i].value = value
57+
return oldValue
58+
}
59+
}
60+
61+
// This key isn't in the bucket yet; add it to the chain.
62+
buckets[index].append((key: key, value: value))
63+
count += 1
64+
return nil
65+
}
66+
67+
public mutating func removeValueForKey(key: Key) -> Value? {
68+
let index = indexForKey(key)
69+
70+
// Find the element in the bucket's chain and remove it.
71+
for (i, element) in buckets[index].enumerate() {
72+
if element.key == key {
73+
buckets[index].removeAtIndex(i)
74+
count -= 1
75+
return element.value
76+
}
77+
}
78+
return nil // key not in hash table
79+
}
80+
81+
public mutating func removeAll() {
82+
buckets = .init(count: buckets.count, repeatedValue: [])
83+
count = 0
84+
}
85+
}
86+
87+
extension HashTable: CustomStringConvertible {
88+
public var description: String {
89+
return buckets.flatMap { b in b.map { e in "\(e.key) = \(e.value)" } }.joinWithSeparator(", ")
90+
}
91+
}
92+
93+
extension HashTable: CustomDebugStringConvertible {
94+
public var debugDescription: String {
95+
var s = ""
96+
for (i, bucket) in buckets.enumerate() {
97+
s += "bucket \(i): " + bucket.map { e in "\(e.key) = \(e.value)" }.joinWithSeparator(", ") + "\n"
98+
}
99+
return s
100+
}
101+
}
102+
103+
104+
105+
// Playing with hash values
106+
"firstName".hashValue
107+
abs("firstName".hashValue) % 5
108+
"lastName".hashValue
109+
abs("lastName".hashValue) % 5
110+
"hobbies".hashValue
111+
abs("hobbies".hashValue) % 5
112+
113+
114+
115+
// Playing with the hash table
116+
var hashTable = HashTable<String, String>(capacity: 5)
117+
118+
hashTable["firstName"] = "Steve"
119+
hashTable["lastName"] = "Jobs"
120+
hashTable["hobbies"] = "Programming Swift"
121+
122+
hashTable.description
123+
print(hashTable.debugDescription)
124+
125+
let x = hashTable["firstName"]
126+
hashTable["firstName"] = "Tim"
127+
let y = hashTable["firstName"]
128+
hashTable["firstName"] = nil
129+
let z = hashTable["firstName"]
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<playground version='5.0' target-platform='osx'>
3+
<timeline fileName='timeline.xctimeline'/>
4+
</playground>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Timeline
3+
version = "3.0">
4+
<TimelineItems>
5+
</TimelineItems>
6+
</Timeline>

Hash Table/HashTable.swift

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/*
2+
Hash table
3+
4+
Allows you to store and retrieve objects by a "key".
5+
6+
The key must be Hashable, which means it can be converted into a fairly
7+
unique Int that represents the key as a number. The more unique the hash
8+
value, the better.
9+
10+
The hashed version of the key is used to calculate an index into the array
11+
of "buckets". The capacity of the hash table is how many of these buckets
12+
it has. Ideally, the number of buckets is the same as the maximum number of
13+
items you'll be storing in the HashTable, and each hash value maps to its
14+
own bucket. In practice that is hard to achieve.
15+
16+
When there is more than one hash value that maps to the same bucket index
17+
-- a "collision" -- they are chained together, using an array. (Note that
18+
more clever ways exist for dealing with the chaining issue.)
19+
20+
While there are free buckets, inserting/retrieving/removing are O(1).
21+
But when the buckets are full and the chains are long, using the hash table
22+
becomes an O(n) operation.
23+
24+
Counting the size of the hash table is O(1) because we're caching the count.
25+
26+
The order of the elements in the hash table is undefined.
27+
28+
This implementation has a fixed capacity; it does not resize when the table
29+
gets too full. (To do that, you'd allocate a larger array of buckets and then
30+
insert each of the elements into that new array; this is necessary because
31+
the hash values will now map to different bucket indexes.)
32+
*/
33+
public struct HashTable<Key: Hashable, Value> {
34+
private typealias Element = (key: Key, value: Value)
35+
private typealias Bucket = [Element]
36+
37+
private var buckets: [Bucket]
38+
private(set) var count = 0
39+
40+
public init(capacity: Int) {
41+
assert(capacity > 0)
42+
buckets = .init(count: capacity, repeatedValue: [])
43+
}
44+
45+
public var isEmpty: Bool {
46+
return count == 0
47+
}
48+
49+
private func indexForKey(key: Key) -> Int {
50+
return abs(key.hashValue) % buckets.count
51+
}
52+
}
53+
54+
// MARK: - Basic operations
55+
56+
extension HashTable {
57+
public subscript(key: Key) -> Value? {
58+
get {
59+
return valueForKey(key)
60+
}
61+
set {
62+
if let value = newValue {
63+
updateValue(value, forKey: key)
64+
} else {
65+
removeValueForKey(key)
66+
}
67+
}
68+
}
69+
70+
public func valueForKey(key: Key) -> Value? {
71+
let index = indexForKey(key)
72+
73+
for element in buckets[index] {
74+
if element.key == key {
75+
return element.value
76+
}
77+
}
78+
return nil // key not in hash table
79+
}
80+
81+
public mutating func updateValue(value: Value, forKey key: Key) -> Value? {
82+
let index = indexForKey(key)
83+
84+
// Do we already have this key in the bucket?
85+
for (i, element) in buckets[index].enumerate() {
86+
if element.key == key {
87+
let oldValue = element.value
88+
buckets[index][i].value = value
89+
return oldValue
90+
}
91+
}
92+
93+
// This key isn't in the bucket yet; add it to the chain.
94+
buckets[index].append((key: key, value: value))
95+
count += 1
96+
return nil
97+
}
98+
99+
public mutating func removeValueForKey(key: Key) -> Value? {
100+
let index = indexForKey(key)
101+
102+
// Find the element in the bucket's chain and remove it.
103+
for (i, element) in buckets[index].enumerate() {
104+
if element.key == key {
105+
buckets[index].removeAtIndex(i)
106+
count -= 1
107+
return element.value
108+
}
109+
}
110+
return nil // key not in hash table
111+
}
112+
113+
public mutating func removeAll() {
114+
buckets = .init(count: buckets.count, repeatedValue: [])
115+
count = 0
116+
}
117+
}
118+
119+
// MARK: - Helper methods for inspecting the hash table
120+
121+
extension HashTable {
122+
public var keys: [Key] {
123+
var a = [Key]()
124+
for bucket in buckets {
125+
for element in bucket {
126+
a.append(element.key)
127+
}
128+
}
129+
return a
130+
}
131+
132+
public var values: [Value] {
133+
var a = [Value]()
134+
for bucket in buckets {
135+
for element in bucket {
136+
a.append(element.value)
137+
}
138+
}
139+
return a
140+
}
141+
}
142+
143+
// MARK: - For debugging
144+
145+
extension HashTable: CustomStringConvertible {
146+
public var description: String {
147+
return buckets.flatMap { b in b.map { e in "\(e.key) = \(e.value)" } }.joinWithSeparator(", ")
148+
}
149+
}
150+
151+
extension HashTable: CustomDebugStringConvertible {
152+
public var debugDescription: String {
153+
var s = ""
154+
for (i, bucket) in buckets.enumerate() {
155+
s += "bucket \(i): " + bucket.map { e in "\(e.key) = \(e.value)" }.joinWithSeparator(", ") + "\n"
156+
}
157+
return s
158+
}
159+
}

0 commit comments

Comments
 (0)