Skip to content

Commit

Permalink
Merge pull request #1 from WebWallen/daniel-wallen
Browse files Browse the repository at this point in the history
Hash Tables Application, Block Chain Mining, and Data Structures Interview Q's
  • Loading branch information
RaineBeauDubs committed Feb 15, 2020
2 parents 05f9eb6 + d3707de commit e37ede6
Show file tree
Hide file tree
Showing 7 changed files with 197 additions and 45 deletions.
101 changes: 101 additions & 0 deletions InterviewQuestions
@@ -0,0 +1,101 @@
Explain in detail the workings of a dynamic array:

* What is the four step process to creating an array?
1. Determine how big it needs to be
2. Request a block of memory that fits those needs
3. Receive the memory address of the reserved block
4. Write your values into the array

* How much memory does an array require?
It depends how many integers there are.
Each integer requires four bytes of memory.
Thus, we multiply four by the array's length.

* How do we access the index of an array element?
index * sizeof(type) + start_address

*What is the runtime complexity of the search operation?
O(1) -- arrays are the most time and space efficient operation

*What is the difference between arrays and Python lists?
Python lists are just arrays with less complexity and more built-in functionality

*What is the process for adding an element to the end of an array?
1. Take size of current array and increase it by one.
2. Request a block of memory fitting the new size.
3. Copy each element from old to new space (happens one element at a time).
4. Free the memory from the old array.

*What is the runtime complexity of the insert operation?
O(n)

*How does the insert process differ on Python?
1. It allocates a few empty spaces to the end every time an array grows.
2. Each time it grows, the amount of extra spaces will increase as well.
3. Time complexity is usually O(1) but can be O(n).

*How is this different from the way Python pushes elements to the front?
1. Check if there's an empty space at the end.
2. If not: allocate a new array, place first element, copy the rest, and free memory from old array.
3. If so: move each element to the right (starting from the end) and place new element in front

*What is the runtime complexity of the append operation?
O(n^2)

*Why is it so much faster to add an array to the back of array?
In a best case scenario, you can simply add it to the end without operating on any other elements. This is typically 600x faster than adding to front.

* What is the worse case scenario if you try to extend the storage size of a dynamic array?
If the dynamic array doesn't have any room for a new item, this can cause a temporary slow down. Or O(n) time complexity.

* What are some examples of hash tables?
1. Associative arrays and dictionaries
2. Objects (Python, Javascript, Ruby)
3. Caches (Memcached)
4. Dynamic programming, memorization

* What is a hash table anyway?
1. Devide for key/value storage and retrieval
2. Contigious block of memory (next to each other)
3. Key converted to an integer via a hash function
4. Hashed key converted to array index via modulo function

*What is a hash function?
It achieves one-way mapping from arbitrary data to fixed data size and type

*What are some different types of hash functions?
1. Deterministic (same input always results in same output)
2. Uniform distribution (on average very far away from original input)
3. Non-invertible (can't use the output to reverse engineer the input used)

*What is the process for creating a hash table?
1. Declare an empty array (power of 2).
2. Hash the key.
3. Assign value to hash index.

*What is the storage device for hash table elements?
Linked lists. To retrieve a value, you traverse down the LL until you find matching key.

*What is the time complexity of a linked list?
O(n) for insertion, deletion, and retrieval. Ditto for space.

*How does hash table resizing work?
Use a load factor (number of entries / hash table capacity)
When load factor passes a certain threshold, resizing occurs
-Create a new hash table with double capacity
-Copy elements from old to new one-at-a-time
-Resizing is O(n) but occurs at O(logn) frequency

*Explain the purpose and meaning of blockchain.
1. Blockchain is a data structure and support system holding a public ledger of transactions.
2. It is not owned by any person or entity -- anyone can make a copy.
3. Everyone has access to source code, but it's still secure due to cryptography.

*What are the blocks, what is the chain? How is the data organized?
Blocks hold index, time stamp, list of transactions, proof used to mine block, and hash of previous block.
Blocks are listed in a chain and must be linked by matching hashes, which prevents people from stealing funds.
Longest chain = what gets trusted. Thus, you'd have to change every single hash since they are all connected.

*Explain how proof of work functions. How does it operate. How does this protect the chain from attack. What kind of attack is possible?
Proof of work uses several complicated hashing mechanisms to make the task of guessing any identifying information near impossible.
It "proves" that work happened by solving an arbitrary and complicated computational math problem. Risk = gains over 51% computational power.
2 changes: 1 addition & 1 deletion Pipfile
Expand Up @@ -11,4 +11,4 @@ requests = "==2.18.4"
flask = "==0.12.2"

[requires]
python_version = "3.7"
python_version = "3.8"
35 changes: 20 additions & 15 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 15 additions & 6 deletions blockchain/miner.py
Expand Up @@ -9,7 +9,6 @@

import random


def proof_of_work(last_proof):
"""
Multi-Ouroboros of Work Algorithm
Expand All @@ -23,9 +22,16 @@ def proof_of_work(last_proof):
start = timer()

print("Searching for next proof")
# Intialize proof and assign zero so we can increment the variable later
proof = 0
# TODO: Your code here

# Convert last proof to a string with encode method and assign to variable
last_proof_string = f"{last_proof}".encode()
# Hash the string with sha256 hash library and use hexdigest to complete conversion
last_proof_hash = hashlib.sha256(last_proof_string).hexdigest()
# While the valid proof hasn't yet been completed on the hashified proof...
while valid_proof(last_proof_hash, proof) is False:
# Increment until it has
proof += 1
print("Proof found: " + str(proof) + " in " + str(timer() - start))
return proof

Expand All @@ -38,9 +44,12 @@ def valid_proof(last_hash, proof):
IE: last_hash: ...AE9123456, new hash 123456E88...
"""

# TODO: Your code here!
pass
# Convert proof to a string with encode method and assign to guess variable
guess_string = f"{proof}".encode()
# Hash the guess string with sha256 hash library and use hexdigest to complete conversion
guess_hash = hashlib.sha256(guess_string).hexdigest()
# Convert last hash to a string and return a value where its last 6 numbers equal first 6 numbers of the new hash
return str(last_hash)[-6:] == guess_hash[:6] # use negative int to access last 6


if __name__ == '__main__':
Expand Down
2 changes: 1 addition & 1 deletion blockchain/my_id.txt
@@ -1 +1 @@
NONAME
daniel-wallen
39 changes: 26 additions & 13 deletions hashtables/ex1/ex1.py
@@ -1,23 +1,36 @@
# Hint: You may not need all of these. Remove the unused functions.
from hashtables import (HashTable,
hash_table_insert,
hash_table_remove,
hash_table_retrieve,
hash_table_resize)
hash_table_retrieve)


def get_indices_of_item_weights(weights, length, limit):
# Initialize hashtable and set its capacity to 16
ht = HashTable(16)

"""
YOUR CODE HERE
"""

# For the first weight in the range from 0 to length...
for weight_1 in range(0, length):
# Insert the hashtable, [weight key], and weight value
hash_table_insert(ht, weights[weight_1], weight_1)
# For the second weight in the range from 0 to length...
for weight_2 in range(0, length):
# Subtract [weight value] from limit and assign result to matching_weight
matching_weight = limit - weights[weight_2]
# Retrieve the matching weight from hash table and assign to found_weight
found_weight = hash_table_retrieve(ht, matching_weight)
# If we found a weight...
if found_weight:
# Return the found weight and its partner weight_2
return (found_weight, weight_2)
# Return None (means no matching weight was found)
return None

test_weights = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
matching_weights = get_indices_of_item_weights(test_weights, 10, 11)
# Should return 9, 0 (10 + 1 = 11)
print(matching_weights)

def print_answer(answer):
if answer is not None:
print(str(answer[0] + " " + answer[1]))
else:
print("None")
# def print_answer(answer):
# if answer is not None:
# print(str(answer[0] + " " + answer[1]))
# else:
# print("None")
42 changes: 33 additions & 9 deletions hashtables/ex2/ex2.py
@@ -1,23 +1,47 @@
# Hint: You may not need all of these. Remove the unused functions.
from hashtables import (HashTable,
hash_table_insert,
hash_table_remove,
hash_table_retrieve,
hash_table_resize)
hash_table_retrieve)


class Ticket:
def __init__(self, source, destination):
self.source = source
self.destination = destination


def reconstruct_trip(tickets, length):
hashtable = HashTable(length)
# Initialize Hashtable/ pass in the length(capacity), and assign to itinerary
itinerary = HashTable(length)
# Preallocate the route (array) memory by length/capacity
route = [None] * length
# For each ticket we have...
for t in tickets:
# Insert the itinerary (ht), source (key), and desination (value) in the hash table
hash_table_insert(itinerary, t.source, t.destination)
# Retrieve and assign the first ticket -- has destination, but no source (key or initial connecting flight)
ticket = hash_table_retrieve(itinerary, "NONE")
# While the ticket *does* have a destination on its key...
while ticket != "NONE":
# For each index in the range of our route array's length...
for i in range(len(route)):
# Assign each route's destination [value] to ticket, which places it inside the array
route[i] = ticket
# Call retrieve again on the same ticket's key to find the ticket with a matching source
ticket = hash_table_retrieve(itinerary, ticket)
# If the ticket doesn't have a destination...
if ticket == "NONE":
# Place at the end of our route
route[i + 1] # have to increment because we aren't assining a value to ticket
# Break out of the loop since work is done
break
# Return our newly organized route
return route

"""
YOUR CODE HERE
"""
tickets = [
Ticket("NONE", "ORD"),
Ticket("ORD", "CID"),
Ticket("CID", "NONE"),
]

pass
# Should print ORD, CID, NONE
print(reconstruct_trip(tickets, 3))

0 comments on commit e37ede6

Please sign in to comment.