diff --git a/README.md b/README.md index a4ba886f..a6e7d1cb 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,15 @@ # Stacks & Queues -In this exercise we will implement both a stack & a queue, and then use them in a variety of hands-on exercises. +In this exercise we will implement both a stack & a queue, and then use them in a variety of hands-on exercises. + +Due: **Monday Sept 9th** ## Learning Goals By the end of this exercise you should be able to: - Implement a stack & a queue using linked lists and arrays -- Explain how a circular buffer works +- Write a Queue with a circular buffer - Use a stack and a queue to solve common interview problems. ## Wave 1 - Implement a Stack @@ -40,6 +42,9 @@ For example: `{()}[)`, and `{(})` are not balanced + +## Optional Wave 4 + ### `evaluate_postfix(expression)` For solving a mathematical expression we sometimes use postfix form. For example: `35+` in postfix evaluates to 3 + 5 = 8. @@ -58,6 +63,7 @@ From the postfix expression, when some operands are found, push them in the stac **Output:** The result is: 39 -## Optional Wave 4 + +#### Additional Exercise If you finish the previous waves, complete [breadth-first-search](https://www.geeksforgeeks.org/bfs-vs-dfs-binary-tree/) on the binary trees project using a Queue. diff --git a/stacks-queues/README.md b/stacks-queues/README.md new file mode 100644 index 00000000..5fa5d917 --- /dev/null +++ b/stacks-queues/README.md @@ -0,0 +1,69 @@ +# Stacks & Queues + +In this exercise we will implement both a stack & a queue, and then use them in a variety of hands-on exercises. + +Due: **Monday Sept 9th** + +## Learning Goals + +By the end of this exercise you should be able to: + +- Implement a stack & a queue using linked lists and arrays +- Write a Queue with a circular buffer +- Use a stack and a queue to solve common interview problems. + +## Wave 1 - Implement a Stack + +Using a Linked list (from a previous exercise) implement a Stack with the following methods: + +- `push(value)` - Adds the value to the top of the stack +- `pop` - Removes and returns an element from the top of the stack +- `empty?` returns true if the stack is empty and false otherwise + +## Wave 2 Implement a Queue + +Using a circular buffer implement a Queue with the following methods: + +- `enqueue(value)` - Adds the value to the back of the queue. +- `dequeue` - removes and returns a value from the front of the queue +- `empty?` returns true if the queue is empty and false otherwise + +## Wave 3 + +Complete the methods in `lib/problems.rb` including: + +### `balanced(string)` + +Given a string containing opening and closing braces, check if it represents a balanced expression or not. + +For example: + +`{[{}{}]}`, and `{{}{}}` are balanced expressions. + +`{()}[)`, and `{(})` are not balanced + + +## Optional Wave 4 + +### `evaluate_postfix(expression)` + +For solving a mathematical expression we sometimes use postfix form. For example: `35+` in postfix evaluates to 3 + 5 = 8. + +Similarly `35+6*` = (3 + 5) * 6 + +Here also we have to use the stack data structure to solve the postfix expressions. + +From the postfix expression, when some operands are found, push them in the stack. When some operator is found, two items are popped from the stack and the operation is performed in correct sequence. After that, the result is also pushed in the stack for future use. After completing the whole expression, the final result is also stored in the stack top. + +**Example: Input and Output** + +**Input:** + Postfix expression: `53+62/*35*+` + +**Output:** + The result is: 39 + + +#### Additional Exercise + +If you finish the previous waves, complete [breadth-first-search](https://www.geeksforgeeks.org/bfs-vs-dfs-binary-tree/) on the binary trees project using a Queue. diff --git a/stacks-queues/Rakefile b/stacks-queues/Rakefile new file mode 100644 index 00000000..b5ea720d --- /dev/null +++ b/stacks-queues/Rakefile @@ -0,0 +1,10 @@ +require 'rake/testtask' + +Rake::TestTask.new do |t| + t.libs = ["lib"] + t.warning = true + t.test_files = FileList['test/*_test.rb'] +end + +task default: :test + diff --git a/stacks-queues/feedback.md b/stacks-queues/feedback.md new file mode 100644 index 00000000..28485e5a --- /dev/null +++ b/stacks-queues/feedback.md @@ -0,0 +1,17 @@ +# Stacks and Queues + +## What We're Looking For +| Feature | Feedback | +|---------- |----------- | +| Implementation of Stack looks complete | | +| Implementation of Queue looks complete | | + + +## OPTIONAL JobSimulation +| Extension | Feedback | +|----------- |----------- | +| Does the code solve the problem to specification? | | +| Is the code easy to follow? | | +| Does the output look good? | | +| Were provided variables and methods used appropriately and not altered in name or definition? | | +| Were any new variables and/or methods made private to the class? | | diff --git a/stacks-queues/lib/linked_list.rb b/stacks-queues/lib/linked_list.rb new file mode 100644 index 00000000..136d8ac9 --- /dev/null +++ b/stacks-queues/lib/linked_list.rb @@ -0,0 +1,342 @@ +# Defines a node in the singly linked list +class Node + attr_reader :data # allow external entities to read value but not write + attr_accessor :next, :previous # allow external entities to read or write next node + + def initialize(value, next_node = nil, previous_node = nil) + @data = value + @next = next_node + @previous = previous_node + end +end + +# Defines the singly linked list +class LinkedList + def initialize + @head = nil # keep the head private. Not accessible outside this class + @tail = nil + end + + # method to add a new node with the specific data value in the linked list + # insert the new node at the beginning of the linked list + # Time Complexity: O(1) + # Space Complexity O(1) + def add_first(value) + new_node = Node.new(value) + new_node.next = @head + + @head.previous = new_node unless @head.nil? + @head = new_node + if @tail.nil? + @tail = @head + end + end + + def remove_first() + raise ArgumentError, "Empty" if self.empty? + + value = @head.data + @head = @head.next + @head.previous = nil + return value + end + + def empty? + return @head.nil? + end + + # method to find if the linked list contains a node with specified value + # returns true if found, false otherwise + # Time Complexity: O(n) + # Space Complexity: O(1) + def search(value) + return false if @head.nil? + return true if @head.data = value + current = @head + until current.data.nil? + return true if current.data = value + current = current.next + end + end + + # method to return the max value in the linked list + # returns the data value and not the node + # Time Complexity: O(n) + # Space Complexity: O(1) + def find_max + return nil if @head.nil? + current = @head + max = current.data + until current.nil? + max = current.data if current.data > max + current = current.next + end + return max + end + + # method to return the min value in the linked list + # returns the data value and not the node + # Time Complexity: O(n) + # Space Complexity: O(1) + def find_min + return nil if @head.nil? + current = @head + min = current.data + until current.nil? + if current.data < min + min = current.data + end + current = current.next + end + return min + end + + + # method that returns the length of the singly linked list + # Time Complexity: O(n) + # Space Complexity: O(1) + def length + return 0 if @head.nil? + length = 0 + current = @head + until current.nil? + current = current.next + length += 1 + end + return length + + end + + # method that returns the value at a given index in the linked list + # index count starts at 0 + # returns nil if there are fewer nodes in the linked list than the index value + # Time Complexity: O(n) + # Space Complexity: O(1) + def get_at_index(index) + return nil if length <= index + current = @head + count = 0 + until current.nil? + return current.data if count == index + current = current.next + count += 1 + end + end + + # method to print all the values in the linked list + # Time Complexity: O(n) + # Space Complexity: O(1) + def visit + current = @head + until current.nil? + puts current.data + current = current.next + end + end + + # method to delete the first node found with specified value + # Time Complexity: O(n) where n is the number of nodes + # Space Complexity: O(1) + def delete(value) + return if @head.nil? + current = @head + + if current.data == value + @head = current.next + @head.previous = nil unless @head.nil? + return + end + + prev = current + until current.nil? + if current.data == value + prev.next = current.next + current.next.previous = prev unless current.next.nil? + @tail = prev if @tail == current + else + prev = current + end + current = current.next + end + end + + # method to reverse the singly linked list + # note: the nodes should be moved and not just the values in the nodes + # Time Complexity: O(n) where n is the number of nodes + # Space Complexity: O(1) + def reverse + return nil if @head.nil? + prev = nil + current = @head + until current.nil? + temp = current.next + current.next = prev + prev = current + current = temp + prev.previous = current + + end + + @head = prev + + end + + + ## Advanced Exercises + # returns the value at the middle element in the singly linked list + # Time Complexity: O(n) + # Space Complexity: O(1) + def find_middle_value + return nil if @head.nil? + current = @head + if length % 2 == 0 + return nil + else + middle_index = length / 2 + end + index = 0 + until current.nil? + return current.data if index == middle_index + current = current.next + index += 1 + end + + end + + # find the nth node from the end and return its value + # assume indexing starts at 0 while counting to n + # Time Complexity: O(n) + # Space Complexity: O(1) + def find_nth_from_end(n) + return nil if @head.nil? + current = @head + index = 0 + until current.nil? + if index == length - n - 1 + return current.data + end + index += 1 + current = current.next + end + end + + # checks if the linked list has a cycle. A cycle exists if any node in the + # linked list links to a node already visited. + # returns true if a cycle is found, false otherwise. + # Time Complexity: O(n) + # Space Complexity: O(1) + def has_cycle + return nil if @head.nil? + slow_p = @head + fast_p = @head + while slow_p != nil && fast_p != nil and fast_p.next != nil + slow_p = slow_p.next + fast_p = fast_p.next.next + if slow_p == fast_p + return true + end + return false + end + end + + + # Additional Exercises + # returns the value in the first node + # returns nil if the list is empty + # Time Complexity: O(1) + # Space Complexity: O(1) + def get_first + return nil if @head.nil? + return @head.data + end + + # method that inserts a given value as a new last node in the linked list + # Time Complexity: O(n) where n is the number of nodes + # Space Complexity: O(1) + def add_last(value) + new_node = Node.new(value) + if @head.nil? + self.add_first(value) + return + end + @tail.next = new_node + new_node.previous = @tail + @tail = new_node + end + + def remove_last() + value = @tail.data + if @head == @tail + @head = @tail = nil + else + @tail = @tail.previous + @tail.next = nil + end + + return value + end + + # method that returns the value of the last node in the linked list + # returns nil if the linked list is empty + # Time Complexity: O(n) where n is the number of nodes + # Space Complexity: O(1) + def get_last + return nil if @head.nil? + return @tail.data + end + + # method to insert a new node with specific data value, assuming the linked + # list is sorted in ascending order + # Time Complexity: O(n) + # Space Complexity: O(1) + def insert_ascending(value) + new_node = Node.new(value) + add_first(value) if @head.nil? + current = @head + if current.data > new_node.data + temp = @head + @head = new_node + temp.previous = @head + new_node.next = temp + end + + until current.nil? + if current.data <= value && current.next.data > value + temp = current.next + current.next = new_node + new_node.next = temp + new_node.previous = current + temp.previous = new_node + return + end + current = current.next + end + end + + # Helper method for tests + # Creates a cycle in the linked list for testing purposes + # Assumes the linked list has at least one node + def create_cycle + return if @head == nil # don't do anything if the linked list is empty + + # navigate to last node + current = @head + while current.next != nil + current = current.next + end + + current.next = @head # make the last node link to first node + end + + def to_s + list = [] + + current = @head + until current.nil? + list << current.data + current = current.next + end + + return list.to_s + end +end \ No newline at end of file diff --git a/stacks-queues/lib/problems.rb b/stacks-queues/lib/problems.rb new file mode 100644 index 00000000..f94f6b31 --- /dev/null +++ b/stacks-queues/lib/problems.rb @@ -0,0 +1,29 @@ +require_relative './stack.rb' + +def balanced(string) + bracket_buds = { + '(' => ')', + '[' => ']', + '{' => '}' + } + + stack = Stack.new + list = string.split('') + + if list.length % 2 != 0 + return false + end + + list.each do |char| + if bracket_buds.values.include?(char) + stack.push(char) + elsif char != bracket_buds[stack.pop] + return false + end + end + return true +end + +def evaluate_postfix(postfix_expression) + raise NotImplementedError, "Not implemented yet" +end \ No newline at end of file diff --git a/stacks-queues/lib/queue.rb b/stacks-queues/lib/queue.rb new file mode 100644 index 00000000..d3d70e33 --- /dev/null +++ b/stacks-queues/lib/queue.rb @@ -0,0 +1,64 @@ +class Queue + + QUEUE_SIZE = 20 + def initialize + @store = Array.new(QUEUE_SIZE) + @front = @rear = -1 + end + + def enqueue(element) + if @front == -1 + @front = 0 + @rear = 1 + @store[@front] = element + elsif @front == @rear + raise Error, "Queue is full" + else # not empty, not full + @store[@rear] = element + @rear = (@rear + 1) % QUEUE_SIZE + end + end + + def dequeue + if @front == -1 + raise Error, "Queue is empty" + else + dequeued = @store[@front] + @front = (@front + 1) % QUEUE_SIZE + if @front == @rear + @front = @rear = -1 + end + return dequeued + end + end + + def front + if @front.empty? + raise Error, "There is no front! There is no rear! Queue is empty" + else + return @store[@front] + end + end + + def size + return @store[@front..@rear].length + end + + def empty? + if @front == -1 + return true + else + return false + end + end + + def to_s + elements_in_queue = [] + i = @front + while i < @rear + elements_in_queue << @store[i] + i = (i + 1) % QUEUE_SIZE + end + return elements_in_queue.to_s + end +end diff --git a/stacks-queues/lib/stack.rb b/stacks-queues/lib/stack.rb new file mode 100644 index 00000000..d6d47802 --- /dev/null +++ b/stacks-queues/lib/stack.rb @@ -0,0 +1,25 @@ +require_relative 'linked_list' + +class Stack + def initialize + @store = LinkedList.new + end + + def push(element) + @store.add_last(element) + end + + def pop + @store.remove_last() + end + + def empty? + if @stack == nil + return true + end + end + + def to_s + return @store.to_s + end +end diff --git a/stacks-queues/test/problems_test.rb b/stacks-queues/test/problems_test.rb new file mode 100644 index 00000000..86b0fe5d --- /dev/null +++ b/stacks-queues/test/problems_test.rb @@ -0,0 +1,48 @@ +require 'minitest/autorun' +require 'minitest/reporters' +require_relative '../lib/problems' + +Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new + +describe "Test wave 3 problems" do + describe "balanced" do + it "Given balanced strings it should return true" do + expect(balanced('(({}))')).must_equal true + end + + it "regards an empty string as balanced" do + expect(balanced('')).must_equal true + end + + it "will return false for an unbalanced set of parens" do + expect(balanced('(()')).must_equal false + expect(balanced('(()}')).must_equal false + expect(balanced('([]]')).must_equal false + end + + it "also works for {} and []" do + expect(balanced('[]')).must_equal true + expect(balanced('{}')).must_equal true + end + end + + describe "postfix" do + it "can add a 2 numbers together" do + skip + expect(evaluate_postfix("34+")).must_equal 7 + expect(evaluate_postfix("34*")).must_equal 12 + expect(evaluate_postfix("34-")).must_equal -1 + expect(evaluate_postfix("34/")).must_equal 0 + end + + it "can add a evaluate a more complicated expression" do + skip + expect(evaluate_postfix("34+2*")).must_equal 14 + expect(evaluate_postfix("34*2/")).must_equal 6 + expect(evaluate_postfix("34-1+")).must_equal 0 + expect(evaluate_postfix("34/7-")).must_equal -7 + expect(evaluate_postfix("35+6*")).must_equal 48 + expect(evaluate_postfix("62/5+")).must_equal 8 + end + end +end \ No newline at end of file diff --git a/stacks-queues/test/queue_test.rb b/stacks-queues/test/queue_test.rb new file mode 100644 index 00000000..a000463b --- /dev/null +++ b/stacks-queues/test/queue_test.rb @@ -0,0 +1,69 @@ +require 'minitest/autorun' +require 'minitest/reporters' +require_relative '../lib/queue' + +Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new + +describe "Test Queue Implementation" do + it "creates a Queue" do + q = Queue.new + q.class.must_equal Queue + end + + it "adds something to an empty Queue" do + q = Queue.new + q.enqueue(10) + q.to_s.must_equal "[10]" + end + + it "adds multiple somethings to a Queue" do + q = Queue.new + q.enqueue(10) + q.enqueue(20) + q.enqueue(30) + q.to_s.must_equal "[10, 20, 30]" + end + + it "starts the size of a Queue at 0" do + q = Queue.new + q.empty?.must_equal true + end + + it "removes something from the Queue" do + q = Queue.new + q.enqueue(5) + removed = q.dequeue + removed.must_equal 5 + q.empty?.must_equal true + end + + it "removes the right something (LIFO)" do + q = Queue.new + q.enqueue(5) + q.enqueue(3) + q.enqueue(7) + removed = q.dequeue + removed.must_equal 5 + q.to_s.must_equal "[3, 7]" + end + + it "properly adjusts the size with enqueueing and dequeueing" do + q = Queue.new + q.empty?.must_equal true + q.enqueue(-1) + q.enqueue(-60) + q.empty?.must_equal false + q.dequeue + q.dequeue + q.empty?.must_equal true + end + + it "returns the front element in the Queue" do + q = Queue.new + q.enqueue(40) + q.enqueue(22) + q.enqueue(3) + q.dequeue + expect(q.dequeue).must_equal 22 + end +end \ No newline at end of file diff --git a/stacks-queues/test/stack_test.rb b/stacks-queues/test/stack_test.rb new file mode 100644 index 00000000..2cf9d5d9 --- /dev/null +++ b/stacks-queues/test/stack_test.rb @@ -0,0 +1,48 @@ +require 'minitest/autorun' +require 'minitest/reporters' +require_relative '../lib/stack' +Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new + +describe "Test Stack Implementation" do + it "creates a Stack" do + s = Stack.new + s.class.must_equal Stack + end + + it "pushes something onto a empty Stack" do + s = Stack.new + s.push(10) + s.to_s.must_equal "[10]" + end + + it "pushes multiple somethings onto a Stack" do + s = Stack.new + s.push(10) + s.push(20) + s.push(30) + s.to_s.must_equal "[10, 20, 30]" + end + + it "starts the stack empty" do + s = Stack.new + s.empty?.must_equal true + end + + it "removes something from the stack" do + s = Stack.new + s.push(5) + removed = s.pop + removed.must_equal 5 + s.empty?.must_equal true + end + + it "removes the right something (LIFO)" do + s = Stack.new + s.push(5) + s.push(3) + s.push(7) + removed = s.pop + removed.must_equal 7 + s.to_s.must_equal "[5, 3]" + end +end \ No newline at end of file