diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..61190987 Binary files /dev/null and b/.DS_Store differ diff --git a/01-data-structures/.DS_Store b/01-data-structures/.DS_Store new file mode 100644 index 00000000..83496f7f Binary files /dev/null and b/01-data-structures/.DS_Store differ diff --git a/01-data-structures/01-introduction-to-data-structures/line/line.rb b/01-data-structures/01-introduction-to-data-structures/line/line.rb index 84bfe59e..22d5a316 100644 --- a/01-data-structures/01-introduction-to-data-structures/line/line.rb +++ b/01-data-structures/01-introduction-to-data-structures/line/line.rb @@ -9,26 +9,33 @@ def initialize end def join(person) + members.push(person) end def leave(person) + members.delete(person) end def front + members.first end def middle + members[members.length / 2] end def back + members.last end def search(person) + members.detect {|e| e === person} end private def index(person) + members.find_index(person) end -end \ No newline at end of file +end diff --git a/01-data-structures/01-introduction-to-data-structures/line/line_answers.txt b/01-data-structures/01-introduction-to-data-structures/line/line_answers.txt index e69de29b..46398e69 100644 --- a/01-data-structures/01-introduction-to-data-structures/line/line_answers.txt +++ b/01-data-structures/01-introduction-to-data-structures/line/line_answers.txt @@ -0,0 +1,23 @@ + For this challenge regarding the line at an amusement park I opted to use an +array as the data structure. I came to this decision as an array is already much +like a line. + + You can easily add "people" to the end of the "line" using the array.push +method, similar to people joining a real world line at the end. An array of +is also linear like a line, and using some basic ruby method you can modify the +array to show people leaving and entering the line in addition to seeing who is +first, last and in the middle. + + Developers would be able to access and manipulate this data by using Ruby array +method in addition to searching by index. Developers could also search by name +using the detect method Ruby provides. + + I believe that an array was the right solution. I can also envision arrays +being a useful data structure for other situations such as a to-do list of items, +or the midi values and their related notes on a piano. Some other examples that +come to mind would be a repair queue at a phone shop, storing high scores on +sequential levels of an arcade game, etc. The list could go on and on as there +is much real-world data that an array could represent. + + While arrays are a more straight forward and simple data structure their use +and ability to represent data is very valuable to developers. diff --git a/01-data-structures/01-introduction-to-data-structures/line/line_spec.rb b/01-data-structures/01-introduction-to-data-structures/line/line_spec.rb index b755dabb..13435d11 100644 --- a/01-data-structures/01-introduction-to-data-structures/line/line_spec.rb +++ b/01-data-structures/01-introduction-to-data-structures/line/line_spec.rb @@ -1,6 +1,6 @@ include RSpec -require_relative 'line' +require_relative 'line' RSpec.describe Line, type: Class do let(:line) { Line.new } @@ -59,4 +59,4 @@ end end -end \ No newline at end of file +end diff --git a/01-data-structures/01-introduction-to-data-structures/screen/pixel.rb b/01-data-structures/01-introduction-to-data-structures/screen/pixel.rb index e286557e..5e087197 100644 --- a/01-data-structures/01-introduction-to-data-structures/screen/pixel.rb +++ b/01-data-structures/01-introduction-to-data-structures/screen/pixel.rb @@ -12,11 +12,23 @@ class Pixel def initialize(red, green, blue, x, y) + @red = validate_color(red) + @green = validate_color(green) + @blue = validate_color(blue) + @x = x + @y = y end private def validate_color(color) + if color < 0 + 0 + elsif color > 255 + 255 + else + color + end end end diff --git a/01-data-structures/01-introduction-to-data-structures/screen/pixel_spec.rb b/01-data-structures/01-introduction-to-data-structures/screen/pixel_spec.rb index b3ab426e..3136cbed 100644 --- a/01-data-structures/01-introduction-to-data-structures/screen/pixel_spec.rb +++ b/01-data-structures/01-introduction-to-data-structures/screen/pixel_spec.rb @@ -4,24 +4,24 @@ describe "#initialize" do it "creates a pixel with the specified parameters" do pixel = Pixel.new(255, 200, 160, 5, 7) - + expect(pixel.red).to eq 255 expect(pixel.green).to eq 200 expect(pixel.blue).to eq 160 expect(pixel.x).to eq 5 expect(pixel.y).to eq 7 end - + it "corrects a red value if it's less than 0" do pixel = Pixel.new(-7, 100, 100, 5, 5) expect(pixel.red).to eq 0 end - + it "corrects a blue value if it's less than 0" do pixel = Pixel.new(100, -10, 100, 5, 5) expect(pixel.green).to eq 0 end - + it "corrects a green value if it's less than 0" do pixel = Pixel.new(100, 100, -12, 5, 5) expect(pixel.blue).to eq 0 @@ -31,12 +31,12 @@ pixel = Pixel.new(256, 100, 100, 5, 5) expect(pixel.red).to eq 255 end - + it "corrects a green value if it's greater than 255" do pixel = Pixel.new(100, 258, 100, 5, 5) expect(pixel.green).to eq 255 end - + it "corrects a blue value if it's greater than 255" do pixel = Pixel.new(100, 100, 300, 5, 5) expect(pixel.blue).to eq 255 diff --git a/01-data-structures/01-introduction-to-data-structures/screen/screen.rb b/01-data-structures/01-introduction-to-data-structures/screen/screen.rb index 8a0aee67..32b0acf1 100644 --- a/01-data-structures/01-introduction-to-data-structures/screen/screen.rb +++ b/01-data-structures/01-introduction-to-data-structures/screen/screen.rb @@ -6,18 +6,29 @@ class Screen attr_accessor :matrix def initialize(width, height) + @matrix = Array.new(width) { Array.new(height, 0) } + @height = height + @width = width end # Insert a Pixel at x, y def insert(pixel, x, y) + @matrix[x][y] = pixel end def at(x, y) + inbounds(x, y) end private def inbounds(x, y) + if x > @width || y > @height || x < 0 || y < 0 + nil + else + @matrix[x][y] + end + end -end \ No newline at end of file +end diff --git a/01-data-structures/01-introduction-to-data-structures/screen/screen_answers.txt b/01-data-structures/01-introduction-to-data-structures/screen/screen_answers.txt index e69de29b..ca244897 100644 --- a/01-data-structures/01-introduction-to-data-structures/screen/screen_answers.txt +++ b/01-data-structures/01-introduction-to-data-structures/screen/screen_answers.txt @@ -0,0 +1,12 @@ + For this challenge regarding the pixel and screen I opted to use a matrix made +up of an array containing arrays. This allowed me to represent the height and +width of the screen that will then contain the pixels. + + This data structure will allow for easy access to the data in the matrix. By +using matrix[coordinate-x][coordinate-y] you can easily manipulate or retrieve the data in the structure. Developers +would be able to easily search for pixels by coordinates. + + This data structure could be useful for other real world data representations +such as any game that utilizes a grid, for example battleship, connect 4 or +tick tack toe. In addition previous checkpoints showed that a matrix can be used +to represent graph data such as vertices and edges. diff --git a/01-data-structures/01-introduction-to-data-structures/screen/screen_spec.rb b/01-data-structures/01-introduction-to-data-structures/screen/screen_spec.rb index f05becb2..45a2e008 100644 --- a/01-data-structures/01-introduction-to-data-structures/screen/screen_spec.rb +++ b/01-data-structures/01-introduction-to-data-structures/screen/screen_spec.rb @@ -9,7 +9,7 @@ screen.insert(pixel, 1, 1) expect(screen.at(1, 1)).to eq pixel - end + end it "retains color information upon insertion" do pixel = Pixel.new(255, 200, 175, 1, 1) diff --git a/01-data-structures/02-stacks-and-queues/myqueue/myqueue.rb b/01-data-structures/02-stacks-and-queues/myqueue/myqueue.rb index 3b66c08b..8f0817ef 100644 --- a/01-data-structures/02-stacks-and-queues/myqueue/myqueue.rb +++ b/01-data-structures/02-stacks-and-queues/myqueue/myqueue.rb @@ -8,11 +8,24 @@ def initialize end def enqueue(element) + @queue << element + @head = @queue[0] + @tail = @queue[-1] end def dequeue + temp = @head + @queue.delete_at(0) + @head = @queue[0] + @tail = @queue[-1] + temp end def empty? + if(@queue.length === 0) + true + else + false + end end -end \ No newline at end of file +end diff --git a/01-data-structures/02-stacks-and-queues/myqueue/myqueue_answers.txt b/01-data-structures/02-stacks-and-queues/myqueue/myqueue_answers.txt index e69de29b..f72c15d4 100644 --- a/01-data-structures/02-stacks-and-queues/myqueue/myqueue_answers.txt +++ b/01-data-structures/02-stacks-and-queues/myqueue/myqueue_answers.txt @@ -0,0 +1,14 @@ + I have created a queue and based on the tests, in addition to my understanding +I believe it has what is necessary to function properly as a queue. + + My enqueue method appends the element to the end of the array. It will then +set the new head as the element at queue[0] and the tail as element at queue[-1]. + + My dequeue method allows for FIFO as it will remove the first item in the queue +after storing it temporarily(so that it can be returned). Following the removal of +the first element it will set the new head to the new item at queue[0] and the new +tail to queue[-1]. It will then return the previous head. + + My empty method will check to see if the queue length is equal to zero and if +it is we know the queue is empty and the method will return true. If not equal +to zero then the method will return false as it is not empty diff --git a/01-data-structures/02-stacks-and-queues/mystack/mystack.rb b/01-data-structures/02-stacks-and-queues/mystack/mystack.rb index ff1ebe97..61a96e0c 100644 --- a/01-data-structures/02-stacks-and-queues/mystack/mystack.rb +++ b/01-data-structures/02-stacks-and-queues/mystack/mystack.rb @@ -7,11 +7,22 @@ def initialize end def push(item) + self.top = item + @stack << item end def pop + temp = self.top + @stack.delete_at(-1) + self.top = @stack[-1] + temp end def empty? + if(@stack.length === 0) + true + else + false + end end -end \ No newline at end of file +end diff --git a/01-data-structures/02-stacks-and-queues/mystack/mystack_answers.txt b/01-data-structures/02-stacks-and-queues/mystack/mystack_answers.txt index e69de29b..c0e80d9e 100644 --- a/01-data-structures/02-stacks-and-queues/mystack/mystack_answers.txt +++ b/01-data-structures/02-stacks-and-queues/mystack/mystack_answers.txt @@ -0,0 +1,12 @@ + With my current understanding of a stack I believe my solution meets the necessary +requirements. + + For my push method I appended the item to the end of the stack. From there my +pop method will temporarily store the current top then delete the last element +in the array. It will then reset the top to the new last element and return +the old top. + + The empty? method will check to see if the array length is equal to zero and +if it is it means the stack is empty and will return true. If the length is not +equal to zero then that will mean there is at least one element in the array +and will return false. diff --git a/01-data-structures/02-stacks-and-queues/mystack/mystack_spec.rb b/01-data-structures/02-stacks-and-queues/mystack/mystack_spec.rb index c0d1af80..37f2a39b 100644 --- a/01-data-structures/02-stacks-and-queues/mystack/mystack_spec.rb +++ b/01-data-structures/02-stacks-and-queues/mystack/mystack_spec.rb @@ -40,9 +40,15 @@ expect(stack.empty?).to eq true end + it "returns true when the stack is empty" do + stack.push("Rob") + stack.pop + expect(stack.empty?).to eq true + end + it "returns false when the stack is not empty" do stack.push("Rob") expect(stack.empty?).to eq false end end -end \ No newline at end of file +end diff --git a/01-data-structures/03-linked-lists/linked-lists-answers.txt b/01-data-structures/03-linked-lists/linked-lists-answers.txt index e69de29b..28d717d9 100644 --- a/01-data-structures/03-linked-lists/linked-lists-answers.txt +++ b/01-data-structures/03-linked-lists/linked-lists-answers.txt @@ -0,0 +1,15 @@ + Spacial Locality involves accessing items that are close together in memory, as +in an array. This benefits performance as it limits the number of RAM lookups which +are more time consuming. + + I have created the benchmark tests for the array and the linked list. I found +array out performed the linked list in all 3 tests. When creating the 10,000 item +array and appending 10,000 items to the linked list the array completed slightly +more then 4 seconds before the linked list. The list had to move through all previous +nodes to add to the tail node, explaining why it took so much longer. Accessing +the 5,000th element and deleting the 5,000 element were much closer as far as time +but the array was still faster in both tests. + + I have attached links to my benchmark tests on repl.it. +Array Benchmarks: https://repl.it/@ConSou/Array-Benchmark +Linked List Benchmarks: https://repl.it/@ConSou/Linked-List-Benchmark diff --git a/01-data-structures/03-linked-lists/linked_list.rb b/01-data-structures/03-linked-lists/linked_list.rb index 5ee2a533..3dfea23c 100644 --- a/01-data-structures/03-linked-lists/linked_list.rb +++ b/01-data-structures/03-linked-lists/linked_list.rb @@ -4,27 +4,91 @@ class LinkedList attr_accessor :head attr_accessor :tail + def initialize + @head = nil + @tail = @head + end + # This method creates a new `Node` using `data`, and inserts it at the end of the list. def add_to_tail(node) + if !@head + @head = node + @tail = node + else + currentNode = @head + + while(currentNode.next != nil) + currentNode = currentNode.next + end + + currentNode.next = node + @tail = node + end end # This method removes the last node in the lists and must keep the rest of the list intact. def remove_tail + currentNode = @head + + while currentNode.next + previousNode = currentNode + currentNode = currentNode.next + end + + if @head.next + currentNode = nil + previousNode.next = nil + @tail = previousNode + else + @head = nil + @tail = nil + end + end # This method prints out a representation of the list. def print + currentNode = @head + + while currentNode + puts currentNode.data + currentNode = currentNode.next + end end # This method removes `node` from the list and must keep the rest of the list intact. def delete(node) + currentNode = @head + + if(node == @head) + remove_front + + elsif(node == @tail) + remove_tail + + else + + while currentNode != node + previousNode = currentNode + currentNode = currentNode.next + end + + previousNode.next = currentNode.next + currentNode = nil + end end # This method adds `node` to the front of the list and must set the list's head to `node`. def add_to_front(node) + old_head = @head + @head = node + @head.next = old_head end # This method removes and returns the first node in the Linked List and must set Linked List's head to the second node. def remove_front + front = @head + @head = @head.next + front end -end \ No newline at end of file +end diff --git a/01-data-structures/03-linked-lists/linked_list_spec.rb b/01-data-structures/03-linked-lists/linked_list_spec.rb index cabef225..bf236ace 100644 --- a/01-data-structures/03-linked-lists/linked_list_spec.rb +++ b/01-data-structures/03-linked-lists/linked_list_spec.rb @@ -89,4 +89,4 @@ expect(llist.head).to eq nil end end -end \ No newline at end of file +end diff --git a/01-data-structures/03-linked-lists/node.rb b/01-data-structures/03-linked-lists/node.rb index 016acb90..326bdcc0 100644 --- a/01-data-structures/03-linked-lists/node.rb +++ b/01-data-structures/03-linked-lists/node.rb @@ -3,5 +3,7 @@ class Node attr_accessor :data def initialize(data) + @data = data + @next = nil end -end \ No newline at end of file +end diff --git a/01-data-structures/04-hashes-part-1/hash_item.rb b/01-data-structures/04-hashes-part-1/hash_item.rb index 4a420212..5953907a 100644 --- a/01-data-structures/04-hashes-part-1/hash_item.rb +++ b/01-data-structures/04-hashes-part-1/hash_item.rb @@ -3,5 +3,7 @@ class HashItem attr_accessor :value def initialize(key, value) + @key = key + @value = value end -end \ No newline at end of file +end diff --git a/01-data-structures/04-hashes-part-1/hashclass.rb b/01-data-structures/04-hashes-part-1/hashclass.rb index e538428a..ca7ae639 100644 --- a/01-data-structures/04-hashes-part-1/hashclass.rb +++ b/01-data-structures/04-hashes-part-1/hashclass.rb @@ -5,23 +5,51 @@ def initialize(size) end def []=(key, value) + index_val = index(key, size) + new_item = @items[index_val] + + if new_item == nil + @items[index_val] = HashItem.new(key, value) + elsif new_item.key != key + while @items[index(key, size)].key != nil && @items[index(key, size)].key != key + resize + i = index(key, size) + break if @items[i] == nil + end + self[key] = value + elsif new_item.key == key && new_item.value != value + resize() + new_item.value = value + end end def [](key) + index_val = index(key, size) + @items[index_val].value end def resize + resized_hash = @items += Array.new(size) + + @items.each do |i| + if i != nil + resized_hash[index(i.key, size)] = i + end + end + @items = resized_hash end # Returns a unique, deterministically reproducible index into an array # We are hashing based on strings, let's use the ascii value of each string as # a starting point. def index(key, size) + key.sum % size end # Simple method to return the number of items in the hash def size + @items.length end -end \ No newline at end of file +end diff --git a/01-data-structures/04-hashes-part-1/hashclass_spec.rb b/01-data-structures/04-hashes-part-1/hashclass_spec.rb index 31b4ced6..5c7d0e97 100644 --- a/01-data-structures/04-hashes-part-1/hashclass_spec.rb +++ b/01-data-structures/04-hashes-part-1/hashclass_spec.rb @@ -73,4 +73,4 @@ expect(lotr_movies["The Hobbit: The Battle of Five Armies"]).to eq "2 hours, 44 minutes" end end -end \ No newline at end of file +end diff --git a/01-data-structures/04-hashes-part-1/hashes-1-answers.txt b/01-data-structures/04-hashes-part-1/hashes-1-answers.txt index e69de29b..c5b35311 100644 --- a/01-data-structures/04-hashes-part-1/hashes-1-answers.txt +++ b/01-data-structures/04-hashes-part-1/hashes-1-answers.txt @@ -0,0 +1,16 @@ + In order to deal with collisions, this checkpoint instructed us to double the +length of the array and re-map the keys with the new array size. This made it +relatively simple to fix collisions. + + Although this is an easy way to deal with collisions it is not the most effective. +There are a couple things to consider about this option and why it may not be +the best. As you double the size of arrays they will grow faster and faster, +potentially taking up too much space. This process also requires allocating more +memory which is not a quick task. In addition if collisions are happening +frequently the array is doubling making it so items in your array are more spaced +out, leaving a lot of nil values in the array and wasting space, which would not be very +efficient. + + While doubling the size of an array may work in certain scenarios there are +definitely better solutions to this problem, such as the expansion method described +in the checkpoint (increasing to a prime number near the next largest power of two). diff --git a/01-data-structures/05-hashes-part-2/hashes-part-2-answers.txt b/01-data-structures/05-hashes-part-2/hashes-part-2-answers.txt new file mode 100644 index 00000000..ae2fbf6d --- /dev/null +++ b/01-data-structures/05-hashes-part-2/hashes-part-2-answers.txt @@ -0,0 +1,56 @@ +Q: Describe three collision resolution strategies not mentioned here. + +A: The first collision resolution I found that wasn't mentioned in bloc's curriculum +is the Coalesced hashing strategy. It is described as a mix between chaining and +open addressing. This method allows for better use of space like open addressing. +Each slot gets a single element, and if a collision occurs the node in the slot +stores a link to the next open index value. This allows for a quicker look up like +you get with chaining. + + Another method not mentioned is "Separate chaining with list head cells". It is +very similar to the chaining method we discussed in the checkpoint. The standout +difference is that it stores the first element of each chain in the slot itself, +reducing the pointer traversals by one. While it is a slight difference it could +be valuable to get that extra boost of performance in certain scenarios. + + A third method that wasn't in the curriculum is Hopscotch hashing. This method +utilizes a single array and for each slot in the array it creates a "neighborhood" +of small consecutive slots. Because of the close proximity of the "neighborhood" +the cost of finding an item with in this area is close to the cost of finding the +desired slot itself. If there isn't a spot in the "neighborhood" it uses linear +probing to find an available slot then must displace items to move this open spot +closer to the original and into the "neighborhood", which can be an expensive process. + + +Q: Create your own collision resolution strategy and describe how it works. + +A: After researching many different collision resolution strategies, and spending +a fair amount of time brain storming one of my own I came with a couple ideas. +One that incorporates separate chaining but will potentially help with the time +it takes to recall an element when a collision has taken place. My second idea +uses binary search to help with the look up when multiple keys collide with +one index. Both will utilize load factors and dynamic resizing to ensure that they +are as performant as possible. + + My first method would use linked lists and would traverse the list to find other nodes that +have collided with this index. Though I would include a counter instance variable +that would increment as the node is accessed. The linked list would be ordered by +number of times items were need, placing the most accessed item in the list at +the index and linking the second most and so on down the list. As an item is accessed +more it will be in turn be faster to access. On the fist collision in an index the second +item added will be placed at the end of the linked list, as item[i] is called its +counter would increase and if it is accessed more it will become quicker to find, thus +reducing traversals for items that have been accessed more frequently. While this +method would be harder to implement I think it could add a slight performance boost +in the right situations. + + Another method I came up with would incorporate a binary search. When a collision +occurs an array would be placed in the index location. The keys that collided +would then be placed in alphabetical order. Upon look up a binary search would run +looking for the specific key. This would be useful in hash tables that have a lot +of collisions although it would be harder to configure and set up. + + Coming up with collision resolution strategies was a difficult assignment, as +many of the most performant and space conscious methods and ideas have already been +created. While I know my methods are not perfect I enjoyed thinking about this topic +and trying to come up with some of my own. diff --git a/01-data-structures/05-hashes-part-2/open_addressing/node.rb b/01-data-structures/05-hashes-part-2/open_addressing/node.rb index 07f867b7..ce37ad7b 100644 --- a/01-data-structures/05-hashes-part-2/open_addressing/node.rb +++ b/01-data-structures/05-hashes-part-2/open_addressing/node.rb @@ -4,5 +4,7 @@ class Node attr_accessor :value def initialize(key, value) + @key = key + @value = value end -end \ No newline at end of file +end diff --git a/01-data-structures/05-hashes-part-2/open_addressing/open_addressing.rb b/01-data-structures/05-hashes-part-2/open_addressing/open_addressing.rb index 30db5754..dd5b2463 100644 --- a/01-data-structures/05-hashes-part-2/open_addressing/open_addressing.rb +++ b/01-data-structures/05-hashes-part-2/open_addressing/open_addressing.rb @@ -2,29 +2,94 @@ class OpenAddressing def initialize(size) + @items = Array.new(size) + @item_count = 0.0 end def []=(key, value) + index = index(key, size) + + if @items[index] == nil || @items[index].key === key + @items[index] = Node.new(key, value) + elsif next_open_index(index) == -1 + resize + self[key] = value + else + index = next_open_index(index) + @items[index] = Node.new(key, value) + end + @item_count += 1 + #hash_status end def [](key) + index = index(key, size) + + if @items[index].key == key + return @items[index].value + end + until index == size + index += 1 + if @items[index].key == key + return @items[index].value + end + end end # Returns a unique, deterministically reproducible index into an array # We are hashing based on strings, let's use the ascii value of each string as # a starting point. def index(key, size) + key.sum % size end # Given an index, find the next open index in @items def next_open_index(index) + while @items[index] + index += 1 + if index === size + return -1 + end + end + index end # Simple method to return the number of items in the hash def size + @items.length end # Resize the hash def resize + resized_hash = Array.new(size * 2) + + @items.each do |i| + if i != nil + resized_hash[index(i.key, resized_hash.length)] = i + end + end + @items = resized_hash + end + + def load_factor + @item_count / size end -end \ No newline at end of file + + def hash_status + j = 0 + p "-------------------" + p "Load Factor: #{load_factor}" + @items.each do |i| + if i != nil + p "Index: #{j}" + p "Key: #{i.key}" + p "Value: #{i.value}" + p "~~~~~~~~~~~~~~~~~" + j += 1 + end + end + p "-------------------" + + end + +end diff --git a/01-data-structures/05-hashes-part-2/open_addressing/open_addressing_spec.rb b/01-data-structures/05-hashes-part-2/open_addressing/open_addressing_spec.rb index 2611b64d..73b3f1d6 100644 --- a/01-data-structures/05-hashes-part-2/open_addressing/open_addressing_spec.rb +++ b/01-data-structures/05-hashes-part-2/open_addressing/open_addressing_spec.rb @@ -42,10 +42,20 @@ hash = OpenAddressing.new(1) hash["key"] = "value" expect(hash.size).to eq 1 - hash["key"] = "second value" + # issues in test, two like keys should not cause a collision. I have added + # additional testing + # hash["key"] = "second value" + # expect(hash.size).to eq 2 + hash["key_two"] = "second value" + expect(hash.size).to eq 2 + hash["key_two"] = "Change value" expect(hash.size).to eq 2 + expect(hash["key_two"]).to eq "Change value" + hash["key_three"] = "Three Value" + expect(hash.size).to eq 4 end + it "sets the value of key to value" do expect(star_wars_movies["Star Wars: The Phantom Menace"]).to eq "Number One" expect(star_wars_movies["Star Wars: Attack of the Clones"]).to eq "Number Two" diff --git a/01-data-structures/05-hashes-part-2/separate_chaining/linked_list.rb b/01-data-structures/05-hashes-part-2/separate_chaining/linked_list.rb index 5ee2a533..d8565a0a 100644 --- a/01-data-structures/05-hashes-part-2/separate_chaining/linked_list.rb +++ b/01-data-structures/05-hashes-part-2/separate_chaining/linked_list.rb @@ -4,27 +4,86 @@ class LinkedList attr_accessor :head attr_accessor :tail + def initialize(head) + @head = head + @tail = @head + end + # This method creates a new `Node` using `data`, and inserts it at the end of the list. def add_to_tail(node) + currentNode = @head + + while(currentNode.next != nil) + currentNode = currentNode.next + end + + currentNode.next = node + @tail = node end # This method removes the last node in the lists and must keep the rest of the list intact. def remove_tail + currentNode = @head + + while currentNode.next + previousNode = currentNode + currentNode = currentNode.next + end + + if @head.next + currentNode = nil + previousNode.next = nil + @tail = previousNode + else + @head = nil + @tail = nil + end end # This method prints out a representation of the list. def print + currentNode = @head + + while currentNode + p "Key: #{currentNode.key}" + p "Value: #{currentNode.value}" + currentNode = currentNode.next + end end # This method removes `node` from the list and must keep the rest of the list intact. def delete(node) + currentNode = @head + + if(node == @head) + remove_front + + elsif(node == @tail) + remove_tail + + else + + while currentNode != node + previousNode = currentNode + currentNode = currentNode.next + end + + previousNode.next = currentNode.next + currentNode = nil + end end # This method adds `node` to the front of the list and must set the list's head to `node`. def add_to_front(node) + old_head = @head + @head = node + @head.next = old_head end # This method removes and returns the first node in the Linked List and must set Linked List's head to the second node. def remove_front + front = @head + @head = @head.next + front end -end \ No newline at end of file +end diff --git a/01-data-structures/05-hashes-part-2/separate_chaining/node.rb b/01-data-structures/05-hashes-part-2/separate_chaining/node.rb index 07f867b7..b8724673 100644 --- a/01-data-structures/05-hashes-part-2/separate_chaining/node.rb +++ b/01-data-structures/05-hashes-part-2/separate_chaining/node.rb @@ -4,5 +4,8 @@ class Node attr_accessor :value def initialize(key, value) + @key = key + @value = value + @next = nil end -end \ No newline at end of file +end diff --git a/01-data-structures/05-hashes-part-2/separate_chaining/separate_chaining.rb b/01-data-structures/05-hashes-part-2/separate_chaining/separate_chaining.rb index 1b46c46d..8852d7dd 100644 --- a/01-data-structures/05-hashes-part-2/separate_chaining/separate_chaining.rb +++ b/01-data-structures/05-hashes-part-2/separate_chaining/separate_chaining.rb @@ -5,29 +5,105 @@ class SeparateChaining def initialize(size) @max_load_factor = 0.7 + @items = Array.new(size) + @item_count = 0.0 end def []=(key, value) + index = index(key, size) + new_node = Node.new(key, value) + + if @items[index] == nil + @items[index] = new_node + + elsif @items[index].instance_variable_defined?(:@next) + ll = LinkedList.new(@items[index]) + ll.add_to_tail(new_node) + @items[index] = ll + + elsif @items[index].instance_variable_defined?(:@head) + @items[index].add_to_tail(new_node) + end + + @item_count += 1 + + if load_factor > @max_load_factor + resize + end + + #hash_status end def [](key) + index = index(key, size) + + if @items[index].instance_variable_defined?(:@head) + current_node = @items[index].head + until current_node.key == key + current_node = current_node.next + end + return current_node.value + else + @items[index].value + end end # Returns a unique, deterministically reproducible index into an array # We are hashing based on strings, let's use the ascii value of each string as # a starting point. def index(key, size) + key.sum % size end # Calculate the current load factor def load_factor + @item_count / size end # Simple method to return the number of items in the hash def size + @items.length end # Resize the hash def resize + resized_hash = Array.new(size * 2) + + @items.each do |i| + if i != nil && i.instance_variable_defined?(:@next) + resized_hash[index(i.key, resized_hash.length)] = i + elsif i.instance_variable_defined?(:@head) + resized_hash[index(i.head.key, resized_hash.length)] = i + end + end + @items = resized_hash + end + + def hash_status + j = 0 + p "-------------------" + p "Load Factor: #{load_factor}" + @items.each do |i| + if i != nil + if i.instance_variable_defined?(:@next) + p i + p "Index: #{j}" + p "Key: #{i.key}" + p "Value: #{i.value}" + p "~~~~~~~~~~~~~~~~~" + j += 1 + end + if i.instance_variable_defined?(:@head) + p i + p "Index: #{j}" + p "Linked List:" + p i.print + p "~~~~~~~~~~~~~~~~~" + j += 1 + end + end + end + p "-------------------" + end end diff --git a/01-data-structures/05-hashes-part-2/separate_chaining/separate_chaining_spec.rb b/01-data-structures/05-hashes-part-2/separate_chaining/separate_chaining_spec.rb index 4013ed2a..66a6e086 100644 --- a/01-data-structures/05-hashes-part-2/separate_chaining/separate_chaining_spec.rb +++ b/01-data-structures/05-hashes-part-2/separate_chaining/separate_chaining_spec.rb @@ -65,7 +65,7 @@ # Load factor should be .5 when two items are added expect(h.load_factor).to eq 0.5 - h["keytwo"] = "value" + h["keythree"] = "value" # Load factor goes down to .375 (3/8) since when third item is added, load factor goes to .75 # then the resize is triggered and load factor is recalculated diff --git a/01-data-structures/06-trees/binary_tree/binary_search_tree.rb b/01-data-structures/06-trees/binary_tree/binary_search_tree.rb index 5cded8ea..94e30f8b 100644 --- a/01-data-structures/06-trees/binary_tree/binary_search_tree.rb +++ b/01-data-structures/06-trees/binary_tree/binary_search_tree.rb @@ -3,19 +3,87 @@ class BinarySearchTree def initialize(root) + @root = root + @print_queue = Queue.new end def insert(root, node) + if node.rating < root.rating + current_node = root.left + + if current_node + insert(current_node, node) + else + root.left = node + end + + elsif node.rating > root.rating + current_node = root.right + + if current_node + insert(current_node, node) + else + root.right = node + end + + end end # Recursive Depth First Search def find(root, data) + if data.nil? + return nil + end + + if root.right != nil + current_node = root.right + elsif root.left != nil + current_node = root.left + end + + if current_node.title != data + find(current_node, data) + else + current_node + end end def delete(root, data) + if data.nil? + return nil + else + node_to_delete = find(root, data) + if node_to_delete.nil? + nil + else + node_to_delete.title = nil + node_to_delete.rating = nil + end + end end # Recursive Breadth First Search def printf(children=nil) + + if children === nil + children = @root + puts "#{children.title}: #{children.rating}" + end + + if children.left != nil + @print_queue << children.left + end + + if children.right != nil + @print_queue << children.right + end + + if @print_queue.empty? + return + else + next_val = @print_queue.pop + puts "#{next_val.title}: #{next_val.rating}" + printf(next_val) + end end end diff --git a/01-data-structures/06-trees/binary_tree/node.rb b/01-data-structures/06-trees/binary_tree/node.rb index c0106c8c..2de6111f 100644 --- a/01-data-structures/06-trees/binary_tree/node.rb +++ b/01-data-structures/06-trees/binary_tree/node.rb @@ -5,5 +5,9 @@ class Node attr_accessor :right def initialize(title, rating) - end -end \ No newline at end of file + @title = title + @rating = rating + @right = nil + @left = nil + end +end diff --git a/01-data-structures/06-trees/min_binary_heap/min_binary_heap.rb b/01-data-structures/06-trees/min_binary_heap/min_binary_heap.rb new file mode 100644 index 00000000..e61b63a8 --- /dev/null +++ b/01-data-structures/06-trees/min_binary_heap/min_binary_heap.rb @@ -0,0 +1,60 @@ +require_relative 'node' + +class MinHeap + +attr_accessor :root, :heap + + def initialize(root) + @root = root + @heap = Array.new(1, @root) + end + + def insert(data) + @heap << data + heap_up + end + + def heap_up + index = @heap.length - 1 + parent = ((index - 2.to_f) / 2).ceil + while @heap[parent] && parent >= 0 && @heap[parent].rating > @heap[index].rating + swap(parent, index) + index = parent + parent = ((index - 2.to_f) / 2).ceil + end + end + + def swap(index_one, index_two) + temp = @heap[index_one] + @heap[index_one] = @heap[index_two] + @heap[index_two] = temp + end + + def find(data) + i = 0 + while i < @heap.length + if(@heap[i].title == data) + return @heap[i] + i = @heap.length + else + i += 1 + end + end + end + + def delete(data) + if(data == @heap.last.title) + @heap.pop() + else + item_delete = find(data) + @heap.delete(item_delete) + end + end + + def printf + @heap.each do |item| + puts "#{item.title}: #{item.rating}" + end + end + +end diff --git a/01-data-structures/06-trees/min_binary_heap/min_binary_heap_spec.rb b/01-data-structures/06-trees/min_binary_heap/min_binary_heap_spec.rb new file mode 100644 index 00000000..2a6362f5 --- /dev/null +++ b/01-data-structures/06-trees/min_binary_heap/min_binary_heap_spec.rb @@ -0,0 +1,214 @@ +include RSpec + +require_relative 'min_binary_heap' + +RSpec.describe MinHeap, type: Class do + let (:root_start) { Node.new("The Matrix", 87) } + + let (:heap) { MinHeap.new(root_start) } + let (:pacific_rim) { Node.new("Pacific Rim", 72) } + let (:braveheart) { Node.new("Braveheart", 78) } + let (:jedi) { Node.new("Star Wars: Return of the Jedi", 80) } + let (:donnie) { Node.new("Donnie Darko", 85) } + let (:inception) { Node.new("Inception", 96) } + let (:district) { Node.new("District 9", 90) } + let (:shawshank) { Node.new("The Shawshank Redemption", 91) } + let (:martian) { Node.new("The Martian", 92) } + let (:hope) { Node.new("Star Wars: A New Hope", 93) } + let (:empire) { Node.new("Star Wars: The Empire Strikes Back", 94) } + let (:mad_max_2) { Node.new("Mad Max 2: The Road Warrior", 98) } + + describe "#insert(data)" do + it "swaps root if insert is smaller then root" do + heap.insert(pacific_rim) + expect(heap.heap[0].title).to eq "Pacific Rim" + end + + it "properly inserts a new node as a left child" do + heap.insert(district) + expect(heap.heap[1].title).to eq "District 9" + end + + it "properly inserts a new node as a left-left child" do + heap.insert(pacific_rim) + heap.insert(district) + heap.insert(donnie) + expect(heap.heap[3].title).to eq "The Matrix" + end + + it "properly inserts a new node as a left-right child" do + heap.insert(pacific_rim) + heap.insert(district) + heap.insert(donnie) + heap.insert(empire) + expect(heap.heap[4].title).to eq "Star Wars: The Empire Strikes Back" + end + + it "properly inserts a new node as a right child" do + heap.insert(district) + heap.insert(empire) + expect(heap.heap[2].title).to eq "Star Wars: The Empire Strikes Back" + end + + it "properly inserts a new node as a right-left child" do + heap.insert(district) + heap.insert(empire) + heap.insert(martian) + heap.insert(hope) + heap.insert(mad_max_2) + expect(heap.heap[5].title).to eq "Mad Max 2: The Road Warrior" + end + + it "properly inserts a new node as a right-right child" do + heap.insert(district) + heap.insert(empire) + heap.insert(martian) + heap.insert(hope) + heap.insert(mad_max_2) + heap.insert(inception) + expect(heap.heap[6].title).to eq "Inception" + end + end + + describe "#find(data)" do + it "handles nil gracefully" do + heap.insert(empire) + heap.insert(mad_max_2) + expect(heap.find(nil)).to eq nil + end + + it "properly finds a left node" do + heap.insert(district) + expect(heap.find(district.title).title).to eq "District 9" + end + + it "properly finds a left-left node" do + heap.insert(district) + heap.insert(empire) + heap.insert(martian) + expect(heap.find(martian.title).title).to eq "The Martian" + end + + it "properly finds a left-right node" do + heap.insert(pacific_rim) + heap.insert(district) + heap.insert(donnie) + heap.insert(empire) + expect(heap.find(empire.title).title).to eq "Star Wars: The Empire Strikes Back" + end + + it "properly finds a right node" do + heap.insert(district) + heap.insert(empire) + expect(heap.find(empire.title).title).to eq "Star Wars: The Empire Strikes Back" + end + + it "properly finds a right-left node" do + heap.insert(district) + heap.insert(empire) + heap.insert(martian) + heap.insert(hope) + heap.insert(mad_max_2) + expect(heap.find(mad_max_2.title).title).to eq "Mad Max 2: The Road Warrior" + end + + it "properly finds a right-right node" do + heap.insert(district) + heap.insert(empire) + heap.insert(martian) + heap.insert(hope) + heap.insert(mad_max_2) + heap.insert(inception) + expect(heap.find(inception.title).title).to eq "Inception" + end + end + + describe "#delete(data)" do + it "handles nil gracefully" do + expect(heap.delete(nil)).to eq nil + end + + it "properly deletes a left node" do + heap.insert(district) + heap.delete(district.title) + expect(heap.find(district.title)).to be_nil + end + + it "properly deletes a left-left node" do + heap.insert(district) + heap.insert(empire) + heap.insert(martian) + heap.delete(martian.title) + expect(heap.find(martian.title)).to be_nil + end + + it "properly deletes a left-right node" do + heap.insert(pacific_rim) + heap.insert(district) + heap.insert(donnie) + heap.insert(empire) + heap.delete(empire.title) + expect(heap.find(empire.title)).to be_nil + end + + it "properly deletes a right node" do + heap.insert(district) + heap.insert(empire) + heap.delete(empire.title) + expect(heap.find(empire.title)).to be_nil + end + + it "properly deletes a right-left node" do + heap.insert(district) + heap.insert(empire) + heap.insert(martian) + heap.insert(hope) + heap.insert(mad_max_2) + heap.delete(mad_max_2.title) + expect(heap.find(mad_max_2.title)).to be_nil + end + + it "properly deletes a right-right node" do + heap.insert(district) + heap.insert(empire) + heap.insert(martian) + heap.insert(hope) + heap.insert(mad_max_2) + heap.insert(inception) + heap.delete(inception.title) + expect(heap.find(inception.title)).to be_nil + end + end + + describe "#printf" do + specify { + expected_output = "Pacific Rim: 72\nBraveheart: 78\nStar Wars: Return of the Jedi: 80\nThe Matrix: 87\nDistrict 9: 90\nStar Wars: The Empire Strikes Back: 94\nInception: 96\nStar Wars: A New Hope: 93\nThe Shawshank Redemption: 91\nThe Martian: 92\nMad Max 2: The Road Warrior: 98\n" + heap.insert(hope) + heap.insert(empire) + heap.insert(jedi) + heap.insert(martian) + heap.insert(pacific_rim) + heap.insert(inception) + heap.insert(braveheart) + heap.insert(shawshank) + heap.insert(district) + heap.insert(mad_max_2) + expect { heap.printf }.to output(expected_output).to_stdout + } + + specify { + expected_output = "Pacific Rim: 72\nStar Wars: Return of the Jedi: 80\nBraveheart: 78\nThe Matrix: 87\nThe Shawshank Redemption: 91\nInception: 96\nDistrict 9: 90\nMad Max 2: The Road Warrior: 98\nThe Martian: 92\nStar Wars: The Empire Strikes Back: 94\nStar Wars: A New Hope: 93\n" + heap.insert(mad_max_2) + heap.insert(district) + heap.insert(shawshank) + heap.insert(braveheart) + heap.insert(inception) + heap.insert(pacific_rim) + heap.insert(martian) + heap.insert(jedi) + heap.insert(empire) + heap.insert(hope) + expect { heap.printf }.to output(expected_output).to_stdout + } + end +end diff --git a/01-data-structures/06-trees/min_binary_heap/node.rb b/01-data-structures/06-trees/min_binary_heap/node.rb new file mode 100644 index 00000000..76997318 --- /dev/null +++ b/01-data-structures/06-trees/min_binary_heap/node.rb @@ -0,0 +1,9 @@ +class Node + attr_accessor :title + attr_accessor :rating + + def initialize(title, rating) + @title = title + @rating = rating + end +end diff --git a/01-data-structures/06-trees/min_binary_heap/root.rating b/01-data-structures/06-trees/min_binary_heap/root.rating new file mode 100644 index 00000000..e69de29b diff --git a/01-data-structures/06-trees/tree-answers.txt b/01-data-structures/06-trees/tree-answers.txt index e69de29b..d6b55836 100644 --- a/01-data-structures/06-trees/tree-answers.txt +++ b/01-data-structures/06-trees/tree-answers.txt @@ -0,0 +1,58 @@ + + For my implementation of the min heap I opted to use an array. After much +research over different methods to implement a heap I found many developers turned +to this option. I found the author of cracking the coding interview recommended +this option as you don’t need the overhead of a node.right and node.left as elements +are inserted from left and right in a heap anyways. In addition to this the math +for finding a parent is as easy as (index - 2) / 2, the left child (index * 2) + 1, +and the right child (index * 2) + 2. + +Q: Print both Trees to the console and compare the difference between your Binary +Search Tree and your Heap. + +A: After printing both trees to the console there were a couple of key differences. +The min heap would re-arrange on inserting to maintain its heap property. While +the binary search tree would insert to the right or left depending on if it was +greater then or less then the parent value. This meant the order of the printed +values was very different. The lowest value in the min heap will be the root and +the min value in the binary tree will be the left most value. In addition to this +the heap fills in from left to right while the binary search tree fills in depending +if the value is greater then or less then. Making it so the print function shows +the greatest value in the min heap on the bottom row and the search tree the far right. + +Q: How much time does an average insertion consume in the Binary Search Tree +compared to the Heap? + +A: After running the benchmark test on three different insertions for both the +Binary Search Tree and the Min Heap you can see that while they are very close +but the binary search tree has a slight edge. This seems to be because the min +heap has to “heapify” itself upon insert to make sure that the heap property is +maintained. It may have to swap values to get the insert value to the right location. +While the binary search tree must only check if it is greater then less then and +move on. Although when inserting numbers between 1-10,000 the heap when much +quicker, this may be because of my implementation using an array in addition to +no swaps taking place as all were inserted in order. The Binary Search Tree took +quite a bit longer as when inserting it has to make more and more comparisons as +the tree gets larger. + +Q: How much time does finding 5000 in the Binary Search Tree consume compared to the Heap? + +A: When searching for the middle element I found that the binary search tree +performed slightly better. While both were quick the binary search tree performed +better as it used a depth first search and my heap find method breadth first search. + +Q: When would you use a Binary Search Tree and why? + +A: You would use a binary search tree when you need to maintain a changing dataset +in sorted order. Allowing for performant insertions while still being able to utilize +a binary search. + +Q: When would you use an Heap and why? + +A: You would use a heap when ever you would need quick access to the smallest or +largest item as depending on your heap property smallest or largest could be the root. + Insertions are also fast using a heap making it valuable for changing datasets. + +Links to benchmark repl.it +Binary Search Tree: https://repl.it/@ConSou/Ruby-Binary-Search-Tree +Min Heap: https://repl.it/@ConSou/Ruby-Min-Heap