Skip to content

Commit

Permalink
Merge pull request kodecocodes#216 from glennrfisher/hash-table
Browse files Browse the repository at this point in the history
Update hash table for Swift 3.0
  • Loading branch information
kelvinlauKL authored Sep 17, 2016
2 parents f72b8c7 + 7f94c35 commit 1082749
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 295 deletions.
115 changes: 10 additions & 105 deletions Hash Table/HashTable.playground/Contents.swift
Original file line number Diff line number Diff line change
@@ -1,129 +1,34 @@
//: Playground - noun: a place where people can play

public struct HashTable<Key: Hashable, Value> {
private typealias Element = (key: Key, value: Value)
private typealias Bucket = [Element]

private var buckets: [Bucket]
private(set) var count = 0

public init(capacity: Int) {
assert(capacity > 0)
buckets = .init(count: capacity, repeatedValue: [])
}

public var isEmpty: Bool {
return count == 0
}

private func indexForKey(key: Key) -> Int {
return abs(key.hashValue) % buckets.count
}
}

extension HashTable {
public subscript(key: Key) -> Value? {
get {
return valueForKey(key)
}
set {
if let value = newValue {
updateValue(value, forKey: key)
} else {
removeValueForKey(key)
}
}
}

public func valueForKey(key: Key) -> Value? {
let index = indexForKey(key)

for element in buckets[index] {
if element.key == key {
return element.value
}
}
return nil // key not in hash table
}

public mutating func updateValue(value: Value, forKey key: Key) -> Value? {
let index = indexForKey(key)

// Do we already have this key in the bucket?
for (i, element) in buckets[index].enumerate() {
if element.key == key {
let oldValue = element.value
buckets[index][i].value = value
return oldValue
}
}

// This key isn't in the bucket yet; add it to the chain.
buckets[index].append((key: key, value: value))
count += 1
return nil
}

public mutating func removeValueForKey(key: Key) -> Value? {
let index = indexForKey(key)

// Find the element in the bucket's chain and remove it.
for (i, element) in buckets[index].enumerate() {
if element.key == key {
buckets[index].removeAtIndex(i)
count -= 1
return element.value
}
}
return nil // key not in hash table
}

public mutating func removeAll() {
buckets = .init(count: buckets.count, repeatedValue: [])
count = 0
}
}

extension HashTable: CustomStringConvertible {
public var description: String {
return buckets.flatMap { b in b.map { e in "\(e.key) = \(e.value)" } }.joinWithSeparator(", ")
}
}

extension HashTable: CustomDebugStringConvertible {
public var debugDescription: String {
var s = ""
for (i, bucket) in buckets.enumerate() {
s += "bucket \(i): " + bucket.map { e in "\(e.key) = \(e.value)" }.joinWithSeparator(", ") + "\n"
}
return s
}
}



// Playing with hash values

"firstName".hashValue
abs("firstName".hashValue) % 5

"lastName".hashValue
abs("lastName".hashValue) % 5

"hobbies".hashValue
abs("hobbies".hashValue) % 5



// Playing with the hash table

var hashTable = HashTable<String, String>(capacity: 5)

hashTable["firstName"] = "Steve"
hashTable["lastName"] = "Jobs"
hashTable["hobbies"] = "Programming Swift"

hashTable.description
print(hashTable)
print(hashTable.debugDescription)

let x = hashTable["firstName"]
hashTable["firstName"] = "Tim"

let y = hashTable["firstName"]
hashTable["firstName"] = nil

let z = hashTable["firstName"]

print(hashTable)
print(hashTable.debugDescription)
153 changes: 153 additions & 0 deletions Hash Table/HashTable.playground/Sources/HashTable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
Hash Table: A symbol table of generic key-value pairs.
The key must be `Hashable`, which means it can be transformed into a fairly
unique integer value. The more unique the hash value, the better.
Hash tables use an internal array of buckets to store key-value pairs. The
hash table's capacity is determined by the number of buckets. This
implementation has a fixed capacity--it does not resize the array as more
key-value pairs are inserted.
To insert or locate a particular key-value pair, a hash function transforms the
key into an array index. An ideal hash function would guarantee that different
keys map to different indices. In practice, however, this is difficult to
achieve.
Since different keys can map to the same array index, all hash tables implement
a collision resolution strategy. This implementation uses a strategy called
separate chaining, where key-value pairs that hash to the same index are
"chained together" in a list. For good performance, the capacity of the hash
table should be sufficiently large so that the lists are small.
A well-sized hash table provides very good average performance. In the
worst-case, however, all keys map to the same bucket, resulting in a list that
that requires O(n) time to traverse.
Average Worst-Case
Space: O(n) O(n)
Search: O(1) O(n)
Insert: O(1) O(n)
Delete: O(1) O(n)
*/
public struct HashTable<Key: Hashable, Value>: CustomStringConvertible {
private typealias Element = (key: Key, value: Value)
private typealias Bucket = [Element]
private var buckets: [Bucket]

/// The number of key-value pairs in the hash table.
private(set) public var count = 0

/// A Boolean value that indicates whether the hash table is empty.
public var isEmpty: Bool { return count == 0 }

/// A string that represents the contents of the hash table.
public var description: String {
let pairs = buckets.flatMap { b in b.map { e in "\(e.key) = \(e.value)" } }
return pairs.joined(separator: ", ")
}

/// A string that represents the contents of
/// the hash table, suitable for debugging.
public var debugDescription: String {
var str = ""
for (i, bucket) in buckets.enumerated() {
let pairs = bucket.map { e in "\(e.key) = \(e.value)" }
str += "bucket \(i): " + pairs.joined(separator: ", ") + "\n"
}
return str
}

/**
Create a hash table with the given capacity.
*/
public init(capacity: Int) {
assert(capacity > 0)
buckets = Array<Bucket>(repeatElement([], count: capacity))
}

/**
Accesses the value associated with
the given key for reading and writing.
*/
public subscript(key: Key) -> Value? {
get {
return value(forKey: key)
}
set {
if let value = newValue {
updateValue(value, forKey: key)
} else {
removeValue(forKey: key)
}
}
}

/**
Returns the value for the given key.
*/
public func value(forKey key: Key) -> Value? {
let index = self.index(forKey: key)
for element in buckets[index] {
if element.key == key {
return element.value
}
}
return nil // key not in hash table
}

/**
Updates the value stored in the hash table for the given key,
or adds a new key-value pair if the key does not exist.
*/
@discardableResult public mutating func updateValue(_ value: Value, forKey key: Key) -> Value? {
let index = self.index(forKey: key)

// Do we already have this key in the bucket?
for (i, element) in buckets[index].enumerated() {
if element.key == key {
let oldValue = element.value
buckets[index][i].value = value
return oldValue
}
}

// This key isn't in the bucket yet; add it to the chain.
buckets[index].append((key: key, value: value))
count += 1
return nil
}

/**
Removes the given key and its
associated value from the hash table.
*/
@discardableResult public mutating func removeValue(forKey key: Key) -> Value? {
let index = self.index(forKey: key)

// Find the element in the bucket's chain and remove it.
for (i, element) in buckets[index].enumerated() {
if element.key == key {
buckets[index].remove(at: i)
count -= 1
return element.value
}
}
return nil // key not in hash table
}

/**
Removes all key-value pairs from the hash table.
*/
public mutating func removeAll() {
buckets = Array<Bucket>(repeatElement([], count: buckets.count))
count = 0
}

/**
Returns the given key's array index.
*/
private func index(forKey key: Key) -> Int {
return abs(key.hashValue) % buckets.count
}
}
6 changes: 0 additions & 6 deletions Hash Table/HashTable.playground/timeline.xctimeline

This file was deleted.

Loading

0 comments on commit 1082749

Please sign in to comment.