Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Hash Tables

### https://github.com/LambdaSchool/cs-module-project-hash-tables/pull/173
## Day 1

Task: Implement a basic hash table without collision resolution.
Expand Down Expand Up @@ -73,10 +73,10 @@ For these, you can use either the built-in `dict` type, or the hashtable
you built. (Some of these are easier with `dict` since it's more
full-featured.)

* [Lookup Table](applications/lookup_table/)
* [Expensive Sequence](applications/expensive_seq/)
* [Word Count](applications/word_count/)
* [No Duplicates](applications/no_dups/)
* XXXXX[Lookup Table](applications/lookup_table/)
* XXXXX[Expensive Sequence](applications/expensive_seq/)
* XXXXX[Word Count](applications/word_count/)
* XXXXX[No Duplicates](applications/no_dups/)
* [Markov Chains](applications/markov/)
* [Histogram](applications/histo/)
* [Cracking Caesar Ciphers](applications/crack_caesar/)
Expand Down
13 changes: 12 additions & 1 deletion applications/expensive_seq/expensive_seq.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
# Your code here


table = {}

def expensive_seq(x, y, z):
# Your code here
if x <= 0:
return y + z

if (x, y, z) not in table:
table[(x, y, z)] = (
expensive_seq(x - 1, y + 1, z)
+ expensive_seq(x - 2, y + 2, z * 2)
+ expensive_seq(x - 3, y + 3, z * 3)
)
return table[(x, y, z)]



Expand Down
19 changes: 16 additions & 3 deletions applications/lookup_table/lookup_table.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Your code here

import math
import random

def slowfun_too_slow(x, y):
v = math.pow(x, y)
Expand All @@ -9,18 +10,30 @@ def slowfun_too_slow(x, y):

return v

def build_lookup_table():
d = {}

for x in range(2, 14):
for y in range(3, 6):
d[(x, y)] = slowfun_too_slow(x, y)

return d

# this ensures that the lookup table is ALREADY BUILT before we call slowfun() in our for loop!
table = build_lookup_table()

def slowfun(x, y):
"""
Rewrite slowfun_too_slow() in here so that the program produces the same
output, but completes quickly instead of taking ages to run.
"""
# Your code here

return table[(x, y)]


# Do not modify below this line!

for i in range(50000):
x = random.randrange(2, 14)
y = random.randrange(3, 6)
print(f'{i}: {x},{y}: {slowfun(x, y)}')
print(f'{i}: {x},{y}: {slowfun(x, y)}')
9 changes: 9 additions & 0 deletions applications/no_dups/no_dups.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
def no_dups(s):
# Your code here
d = {}
arr = s.split()
output = []

for word in arr:
if word not in d:
d[word] = 1
output.append(word)

return " ".join(output)


if __name__ == "__main__":
Expand Down
22 changes: 18 additions & 4 deletions applications/word_count/word_count.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
def word_count(s):
# Your code here

def word_count(string):
separators = '":;,.-+=/\|[]{}()*^&'

words = string.lower().split()
counts = {}

for word in words:
word = word.strip(separators)
if not word:
break
if word in counts:
counts[word] += 1
else:
counts[word] = 1

return counts


# Driver program
if __name__ == "__main__":
print(word_count(""))
print(word_count("Hello"))
print(word_count('Hello, my cat. And my cat doesn\'t say "hello" back.'))
print(word_count('This is a test of the emergency broadcast network. This is only a test.'))
print(word_count('This is a test of the emergency broadcast network. This is only a test.'))
121 changes: 106 additions & 15 deletions hashtable/hashtable.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,58 @@ def __init__(self, key, value):
self.next = None


class LinkedList:
def __init__(self):
self.head = None

def __repr__(self):
current_str = ""
current = self.head
while current is not None:
current_str += f"{str(current.value)} -> "
current = current.next
return current_str

def add_to_head(self, node):
node.next = self.head
self.head = node

def add_to_head_or_overwrite(self, node):
existing_node = self.find(node.key)
if existing_node is not None:
existing_node.key = node.key
else:
self.add_to_head(node)

def find(self, key):
current = self.head
while current is not None:
if current.key == key:
return current.value
current = current.next
return None

def delete(self, key):
current = self.head

if current.key == key:
self.head = current.next

prev = current
current = current.next

while current is not None:
if current.key == key:
prev.next = current.next
current.next = None
return current.value
else:
prev = current
current = current.next

return None


# Hash table can't have fewer than this many slots
MIN_CAPACITY = 8

Expand All @@ -16,53 +68,80 @@ class HashTable:
"""
A hash table that with `capacity` buckets
that accepts string keys

Implement this.
"""

def __init__(self, capacity):
# Your code here
self.capacity = capacity
self.table = [None] * capacity

for i in range(0, len(self.table)):
self.table[i] = LinkedList()


def get_num_slots(self):
"""
Return the length of the list you're using to hold the hash
table data. (Not the number of items stored in the hash table,
but the number of slots in the main list.)

One of the tests relies on this.


One of the tests relies on this. if froggy do tonight 🤣

Implement this.
"""
# Your code here
return len(self.table)


def get_load_factor(self):
"""
Return the load factor for this hash table.

Implement this.
"""
# Your code here
count = 0

for i in range(0, len(self.table)):
current = self.table[i].head
while current is not None:
count += 1
current = current.next

load_factor = count / self.get_num_slots()

if load_factor > 0.7:
self.resize(self.capacity * 2)
elif load_factor < 0.2:
if self.capacity // 2 < 8:
self.resize(8)
else:
self.resize(self.capacity // 2)

return load_factor


def fnv1(self, key):
"""
FNV-1 Hash, 64-bit

Implement this, and/or DJB2.
"""

# Your code here
pass


def djb2(self, key):
"""
DJB2 hash, 32-bit

Implement this, and/or FNV-1.
"""
# Your code here
hash = 5381
for c in key:
hash = (hash * 33) + ord(c)
return hash


def hash_index(self, key):
Expand All @@ -73,48 +152,60 @@ def hash_index(self, key):
#return self.fnv1(key) % self.capacity
return self.djb2(key) % self.capacity


def put(self, key, value):
"""
Store the value with the given key.

Hash collisions should be handled with Linked List Chaining.

Implement this.
"""
# Your code here
new_entry = HashTableEntry(key, value)
self.table[self.hash_index(key)].add_to_head(new_entry)


def delete(self, key):
"""
Remove the value stored with the given key.

Print a warning if the key is not found.

Implement this.
"""
# Your code here
return self.table[self.hash_index(key)].delete(key)


def get(self, key):
"""
Retrieve the value stored with the given key.

Returns None if the key is not found.

Implement this.
"""
# Your code here
if self.table[self.hash_index(key)].head is not None:
return self.table[self.hash_index(key)].find(key)
else:
return None


def resize(self, new_capacity):
"""
Changes the capacity of the hash table and
rehashes all key/value pairs.

Implement this.
"""
# Your code here
old_capacity = self.capacity
self.capacity = new_capacity

for i in range(old_capacity, new_capacity):
self.table.append(None)
self.table[i] = LinkedList()

for i in range(0, old_capacity):
current_node = self.table[i].head
while current_node is not None:
self.put(current_node.key, current_node.value)
current_node = current_node.next


if __name__ == "__main__":
Expand Down Expand Up @@ -146,8 +237,8 @@ def resize(self, new_capacity):

print(f"\nResized from {old_capacity} to {new_capacity}.\n")

# Test if data intact after resizing
# # Test if data intact after resizing
for i in range(1, 13):
print(ht.get(f"line_{i}"))

print("")
print("")