├── caesar_cipher ├── .rspec ├── lib │ └── caesar_cipher.rb └── spec │ ├── caesar_spec.rb │ └── spec_helper.rb ├── knights_travails ├── main.rb ├── knight.rb └── board.rb ├── linked_list ├── node.rb └── linked_list.rb ├── binary_search_tree ├── node.rb ├── main.rb └── tree.rb ├── substrings.rb ├── wnb-interview-prep ├── typoglycemia_generator.md ├── two_sum.md ├── find_even_index.md ├── chaser-schedule.md └── typoglycemia_generator.rb ├── script.rb ├── merge_sort.rb ├── stock_picker.rb ├── fibonacci.rb ├── README.md ├── reek-explanations.md ├── bubble_sort.rb └── enumerable_methods.rb /caesar_cipher/.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | --format documentation -------------------------------------------------------------------------------- /knights_travails/main.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'board.rb' 4 | require_relative 'knight.rb' 5 | 6 | game = Board.new 7 | game.knight_moves([3, 3], [0, 0]) 8 | puts '' 9 | game.knight_moves([6, 3], [0, 0]) 10 | puts '' 11 | game.knight_moves([0, 0], [7, 7]) 12 | -------------------------------------------------------------------------------- /linked_list/node.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Nodes are data elements collected in the LinkedList Class 4 | class Node 5 | attr_accessor :value, :next_node 6 | 7 | def initialize(value = nil, next_node = nil) 8 | @value = value 9 | @next_node = next_node 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /binary_search_tree/node.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Node 4 | include Comparable 5 | attr_accessor :data, :left, :right 6 | 7 | def initialize(data = nil, left = nil, right = nil) 8 | @data = data 9 | @left = left 10 | @right = right 11 | end 12 | 13 | def to_s 14 | @data.to_s 15 | end 16 | 17 | def <=>(other) 18 | value = other.class == Node ? other.data : other 19 | data <=> value 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /substrings.rb: -------------------------------------------------------------------------------- 1 | dictionary = ["below","down","go","going","horn","how","howdy","it","i","low","own","part","partner","sit"] 2 | 3 | def substrings (string, dictionary) 4 | matches = {} 5 | dictionary.each do |word| 6 | matches[word] = string.downcase.scan(/(?=#{word})/).count if string.downcase.include?(word) 7 | end 8 | matches 9 | end 10 | 11 | puts substrings("Below", dictionary) 12 | puts substrings("Howdy partner, sit down! How's it going?", dictionary) -------------------------------------------------------------------------------- /caesar_cipher/lib/caesar_cipher.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CaesarCipher 4 | def translate(message, shift, result = '') 5 | message.each_char do |char| 6 | base = char.ord < 91 ? 65 : 97 7 | # Modifies Lowercase & Uppercase 8 | if char.ord.between?(65, 90) || char.ord.between?(97, 122) 9 | rotation = (((char.ord - base) + shift) % 26) + base 10 | result += rotation.chr 11 | # Keeps spaces & punctuation 12 | else 13 | result += char 14 | end 15 | end 16 | result 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /wnb-interview-prep/typoglycemia_generator.md: -------------------------------------------------------------------------------- 1 | ```rb 2 | def scramble_words(words) 3 | words.split(' ').map { |word| scramble(word) }.join(' ') 4 | end 5 | 6 | def scramble(word) 7 | chars = word.chars 8 | letters = chars.select { |char| letter?(char) } 9 | scrambled_letters = scramble_letters(letters.join).chars 10 | chars.map do |char| 11 | letter?(char) ? scrambled_letters.shift : char 12 | end 13 | .join 14 | end 15 | 16 | def scramble_letters(word) 17 | return word if word.length <= 2 18 | 19 | word[0] + word[1...-1].chars.sort.join + word[-1] 20 | end 21 | 22 | def letter?(char) 23 | char.match? /[a-z]/ 24 | end 25 | 26 | ``` -------------------------------------------------------------------------------- /script.rb: -------------------------------------------------------------------------------- 1 | def find_even_index(array) 2 | array.each_index do |index| 3 | return index if array[0...index].sum == array[index + 1..].sum 4 | end 5 | -1 6 | end 7 | 8 | puts find_even_index([1,2,3,4,3,2,1]) == 3 9 | puts find_even_index([1,100,50,-51,1,1]) == 1 10 | puts find_even_index([1,2,3,4,5,6]) == -1 11 | puts find_even_index([20,10,30,10,10,15,35]) == 3 12 | puts find_even_index([20,10,-80,10,10,15,35]) == 0 13 | puts find_even_index([10,-80,10,10,15,35,20]) == 6 14 | puts find_even_index(Array(1..100)) == -1 15 | puts find_even_index([0,0,0,0,0]) == 0 16 | puts find_even_index([-1,-2,-3,-4,-3,-2,-1]) == 3 17 | puts find_even_index(Array(-100..-1)) == -1 -------------------------------------------------------------------------------- /merge_sort.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | def merge_sort(array) 4 | return array if array.length < 2 5 | 6 | middle = (array.length / 2) 7 | left = merge_sort(array[0..middle - 1]) 8 | right = merge_sort(array[middle..-1]) 9 | merge(left, right) 10 | end 11 | 12 | def merge(left, right) 13 | return right if left.empty? 14 | return left if right.empty? 15 | 16 | if left[0] < right[0] 17 | [left[0]] + merge(left[1..-1], right) 18 | else 19 | [right[0]] + merge(left, right[1..-1]) 20 | end 21 | end 22 | 23 | lost_numbers = [42, 15, 4, 23, 16, 8] 24 | prime_numbers = [11, 31, 3, 19, 37, 5, 29, 13, 2, 17, 41, 7, 23] 25 | 26 | p merge_sort(lost_numbers) 27 | p merge_sort(prime_numbers) 28 | -------------------------------------------------------------------------------- /knights_travails/knight.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Knight travels the chess board 4 | class Knight 5 | attr_accessor :location, :moves, :children 6 | 7 | def initialize(location) 8 | @location = location 9 | @moves = possible_moves(location) 10 | @children = [] 11 | # puts "KNIGHT:#{location} => #{moves}" 12 | end 13 | 14 | def possible_moves(location, result = []) 15 | moves = [ 16 | [-1, -2], [1, 2], [-1, 2], [1, -2], [-2, -1], [2, 1], [-2, 1], [2, -1] 17 | ] 18 | moves.each do |move| 19 | x = location[0] + move[0] 20 | y = location[1] + move[1] 21 | # puts "Rejected [#{x}, #{y}]" unless x.between?(0, 7) && y.between?(0, 7) 22 | result << [x, y] if x.between?(0, 7) && y.between?(0, 7) 23 | end 24 | result 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /stock_picker.rb: -------------------------------------------------------------------------------- 1 | def stock_picker prices 2 | # variables needed to solve 3 | highest_profit = 0 4 | best_day_to_buy = 0 5 | best_day_to_sell = 0 6 | # loop through each price 7 | prices.each do |price| 8 | current_day = prices.index(price) 9 | # compare each current price to future prices 10 | future_prices = prices[current_day + 1..-1] 11 | future_prices.each do |future_price| 12 | possible_profit = future_price - price 13 | future_days = future_prices.index(future_price) + 1 14 | # re-write variables when the profit is higher 15 | if possible_profit > highest_profit 16 | highest_profit = possible_profit 17 | best_day_to_buy = current_day 18 | best_day_to_sell = best_day_to_buy + future_days 19 | end 20 | end 21 | end 22 | print [best_day_to_buy, best_day_to_sell] 23 | end 24 | 25 | puts stock_picker([17,3,6,9,15,8,6,1,10]) -------------------------------------------------------------------------------- /fibonacci.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Use iteration. Takes in a number and returns that many numbers of the fibonacci result. 4 | def fibs(number) 5 | return 0 if number <= 1 6 | 7 | result = [0, 1] 8 | until result.length == number 9 | next_number = result[-1] + result[-2] 10 | result << next_number 11 | end 12 | result 13 | end 14 | puts fibs(11).join(' ') 15 | 16 | # Now write another method, to solves the same problem as above recursively. 17 | def fibs_rec(number) 18 | return [0] if number == 1 19 | return [0, 1] if number == 2 20 | 21 | result = fibs_rec(number - 1) 22 | result << result[-1] + result[-2] 23 | end 24 | puts fibs_rec(11).join(' ') 25 | 26 | # Bonus: 2-line recursive solution. 27 | def fibonacci(num) 28 | return [0, 1][0..num - 1] if num <= 2 29 | 30 | fibonacci(num - 1) << fibonacci(num - 1)[-1] + fibonacci(num - 1)[-2] 31 | end 32 | puts fibonacci(11).join(' ') 33 | 34 | # Bonus: 1-line recursive solution, but it just provides the nth digit 35 | def fib_digit(n) 36 | n == 1 || n == 2 ? n - 1 : fib_digit(n - 1) + fib_digit(n - 2) 37 | end 38 | puts fib_digit(11) 39 | -------------------------------------------------------------------------------- /caesar_cipher/spec/caesar_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # spec/caesar_spec.rb 4 | require './lib/caesar_cipher.rb' 5 | 6 | describe CaesarCipher do 7 | subject { CaesarCipher.new } 8 | 9 | describe '#translate' do 10 | it 'works with small positive shift' do 11 | expect(subject.translate('Zz', 5)).to eql('Ee') 12 | end 13 | 14 | it 'works with small negative shift' do 15 | expect(subject.translate('Ee', -5)).to eql('Zz') 16 | end 17 | 18 | it 'works with large positive shift' do 19 | expect(subject.translate('Zz', 83)).to eql('Ee') 20 | end 21 | 22 | it 'works with large negative shift' do 23 | expect(subject.translate('Ee', -83)).to eql('Zz') 24 | end 25 | 26 | it 'works with a phrase with punctuation' do 27 | expect(subject.translate('What a string!', 5)).to eql('Bmfy f xywnsl!') 28 | end 29 | 30 | it 'works with a phrase with a large shift' do 31 | expect(subject.translate('Hello, World!', 75)).to eql('Ebiil, Tloia!') 32 | end 33 | 34 | it 'works with a phrase with a large negative shift' do 35 | expect(subject.translate('Hello, World!', -55)).to eql('Ebiil, Tloia!') 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /wnb-interview-prep/two_sum.md: -------------------------------------------------------------------------------- 1 | # Two Sum (CodeWars) 2 | https://www.codewars.com/kata/52c31f8e6605bcc646000082/train/ruby 3 | 4 | ## Summary 5 | Write a function that takes an array of numbers (integers for the tests) and a target number. It should find two different items in the array that, when added together, give the target value. The indices of these items should then be returned in a tuple / list (depending on your language) like so: (index1, index2). 6 | 7 | ## My solution 8 | ```rb 9 | def two_sum(numbers, target) 10 | numbers.each_with_index do |number, index| 11 | second_number = target - number 12 | next unless numbers.include?(second_number) 13 | 14 | second_index = numbers.rindex(second_number) 15 | next unless second_index != index 16 | 17 | return [index, second_index] 18 | end 19 | end 20 | 21 | puts two_sum([1, 2, 3], 4).sort == [0, 2] 22 | puts two_sum([1234, 5678, 9012], 14690).sort == [1, 2] 23 | puts two_sum([2, 2, 3], 4).sort == [0, 1] 24 | ``` 25 | 26 | ## Favorite Submitted Solutions 27 | 28 | ```rb 29 | def two_sum(numbers, target) 30 | pair = numbers.combination(2).find{ |(a,b)| a+b == target } 31 | [numbers.index(pair[0]), numbers.rindex(pair[1])] 32 | end 33 | ``` 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ruby Exercises 2 | 3 | These exercises are from [The Odin Project](https://www.theodinproject.com/courses/ruby-programming)'s Ruby curriculum. 4 | 5 | ## Basic section 6 | 7 | [caesar_cipher](https://www.theodinproject.com/courses/ruby-programming/lessons/caesar-cipher)\ 8 | [substrings](https://www.theodinproject.com/courses/ruby-programming/lessons/sub-strings)\ 9 | [stock_picker](https://www.theodinproject.com/courses/ruby-programming/lessons/stock-picker)\ 10 | [bubble_sort](https://www.theodinproject.com/courses/ruby-programming/lessons/bubble-sort) 11 | 12 | ## Removed from curriculum 13 | 14 | enumerable_methods 15 | 16 | ## Recursion section 17 | 18 | [fibonacci](https://www.theodinproject.com/courses/ruby-programming/lessons/recursion)\ 19 | [merge_sort](https://www.theodinproject.com/courses/ruby-programming/lessons/recursion) 20 | 21 | ## Computer Science section 22 | 23 | [linked_list](https://www.theodinproject.com/courses/ruby-programming/lessons/linked-lists)\ 24 | [binary_search_tree](https://www.theodinproject.com/courses/ruby-programming/lessons/data-structures-and-algorithms)\ 25 | [knight_travails](https://www.theodinproject.com/courses/ruby-programming/lessons/data-structures-and-algorithms) 26 | 27 | ## Testing section 28 | 29 | [caesar_cipher](https://www.theodinproject.com/courses/ruby-programming/lessons/testing-your-ruby-code) 30 | -------------------------------------------------------------------------------- /binary_search_tree/main.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'node.rb' 4 | require_relative 'tree.rb' 5 | 6 | puts '1. Create a binary search tree from an array of random numbers' 7 | random_array = Array.new(15) { rand(1..100) } 8 | puts "Random Array: #{random_array}" 9 | tree = Tree.new(random_array) 10 | puts '' 11 | puts '2. Confirm that the tree is balanced by calling `#balanced?`' 12 | puts tree.balanced? 13 | puts '' 14 | puts '3. Print out all elements in level, pre, post, and in order' 15 | puts 'Level Order:' 16 | p tree.level_order 17 | puts '' 18 | puts 'Pre Order:' 19 | p tree.preorder 20 | puts '' 21 | puts 'Post Order:' 22 | p tree.postorder 23 | puts '' 24 | puts 'In Order:' 25 | p tree.inorder 26 | puts '' 27 | puts '4. Try to unbalance the tree by adding several numbers > 100' 28 | tree.insert(107) 29 | tree.insert(115) 30 | tree.insert(101) 31 | tree.insert(109) 32 | puts '' 33 | puts '5. Confirm that the tree is unbalanced by calling `#balanced?`' 34 | puts tree.balanced? 35 | puts '' 36 | puts '6. Balance the tree by calling `#rebalance!`' 37 | tree.rebalance! 38 | puts '' 39 | puts '7. Confirm that the tree is balanced by calling `#balanced?`' 40 | puts tree.balanced? 41 | puts '' 42 | puts '8. Print out all elements in level, pre, post, and in order' 43 | puts 'Level Order:' 44 | p tree.level_order 45 | puts '' 46 | puts 'Pre Order:' 47 | p tree.preorder 48 | puts '' 49 | puts 'Post Order:' 50 | p tree.postorder 51 | puts '' 52 | puts 'In Order:' 53 | p tree.inorder 54 | puts '' 55 | puts '--------------------------------------------' 56 | puts 'Node List Visualization - data, left & right' 57 | puts '--------------------------------------------' 58 | nodes = tree.node_list 59 | nodes.each { |node| puts node.to_s } 60 | -------------------------------------------------------------------------------- /wnb-interview-prep/find_even_index.md: -------------------------------------------------------------------------------- 1 | # Find Even Index (CodeWars) 2 | https://www.codewars.com/kata/5679aa472b8f57fb8c000047/train/ruby 3 | 4 | ## Summary 5 | You are going to be given an array of integers. Your job is to take that array and find an index N where the sum of the integers to the left of N is equal to the sum of the integers to the right of N. If there is no index that would make this happen, return -1. 6 | 7 | For example: 8 | Let's say you are given the array {1,2,3,4,3,2,1}: 9 | Your function will return the index 3, because at the 3rd position of the array, the sum of left side of the index ({1,2,3}) and the sum of the right side of the index ({3,2,1}) both equal 6. 10 | 11 | ## My solution 12 | ```rb 13 | def find_even_index(array) 14 | array.each_with_index do |number, index| 15 | # After installing rubocop, I learned something cool. 16 | # For the second half of the array, you don't need -1 for the range 17 | # return index if array[0...index].sum == array[index + 1..].sum 18 | return index if array[0...index].sum == array[index + 1..-1].sum 19 | 20 | return -1 if index == array.size - 1 21 | end 22 | end 23 | 24 | puts find_even_index([1,2,3,4,3,2,1]) == 3 25 | puts find_even_index([1,100,50,-51,1,1]) == 1 26 | puts find_even_index([1,2,3,4,5,6]) == -1 27 | puts find_even_index([20,10,30,10,10,15,35]) == 3 28 | puts find_even_index([20,10,-80,10,10,15,35]) == 0 29 | puts find_even_index([10,-80,10,10,15,35,20]) == 6 30 | puts find_even_index(Array(1..100)) == -1 31 | puts find_even_index([0,0,0,0,0]) == 0 32 | puts find_even_index([-1,-2,-3,-4,-3,-2,-1]) == 3 33 | puts find_even_index(Array(-100..-1)) == -1 34 | ``` 35 | 36 | ## Favorite Submitted Solutions 37 | 38 | ```rb 39 | # I did not need to use each_with_index 40 | def find_even_index(arr) 41 | arr.each_index { |i| return i if arr[0...i].sum == arr[(i + 1)..-1].sum } 42 | -1 43 | end 44 | 45 | # I did not need to explictly return -1 inside the each_with_index 46 | def find_even_index(array) 47 | array.each_with_index do |number, index| 48 | return index if array[0...index].sum == array[index + 1..-1].sum 49 | end 50 | -1 51 | end 52 | ``` 53 | -------------------------------------------------------------------------------- /reek-explanations.md: -------------------------------------------------------------------------------- 1 | # Reek Explanations 2 | 3 | ## Attribute: @instance_variable is a writable attribute 4 | [https://github.com/troessner/reek/blob/v6.0.1/docs/Attribute.md] 5 | My explanation -> Make @instance_variable an attr_reader only. Create a method inside that Class that receives a message to update the @instance_variable. (class encapsulation) 6 | 7 | ## ControlParameter: Class#method_name is controlled by (argument) 8 | [https://github.com/troessner/reek/blob/v6.0.1/docs/Control-Parameter.md] 9 | My explanation -> Do not make conditionals in methods controls by the argument 10 | Documentation -> A typical easy solution is to remove the method altogether and to move the conditionals to the method that called this one. 11 | 12 | ## DuplicateMethodCall: Class#method_name calls 'something' 2 times 13 | [https://github.com/troessner/reek/blob/v6.0.1/docs/Duplicate-Method-Call.md] 14 | My explanation -> Refactor code to not need to call same thing multiple times. 15 | 16 | ## UncommunicativeParameterName: Class#method_name has the parameter name 'name1' 17 | [https://github.com/troessner/reek/blob/v6.0.1/docs/Uncommunicative-Parameter-Name.md] 18 | My explanation -> Do not use 1 or 2 in variable names, or use a single-letter. 19 | 20 | ## InstanceVariableAssumption: Class assumes too much for '@instance_variable' 21 | [https://github.com/troessner/reek/blob/v6.0.1/docs/Instance-Variable-Assumption.md] 22 | My explanation -> Put @instance_variables in initialize method 23 | 24 | ## TooManyStatements: Class#method_name has approx + statements 25 | [https://github.com/troessner/reek/blob/v6.0.1/docs/Too-Many-Statements.md] 26 | My explanation -> Method needs to be simplified/shortened. Create helper method? 27 | 28 | ## UtilityFunction: Class#method_name doesn't depend on instance state (maybe move it to another class?) 29 | [https://github.com/troessner/reek/blob/v6.0.1/docs/Utility-Function.md] 30 | My explanation -> If method is important to the app & should stay public to be tested create another class. 31 | If method can be private & does not need to be tested, look at changing reek settings to ignore private methods. 32 | 33 | # FeatureEnvy: Class#method_name refers to 'something' more than self (maybe move it to another class?) 34 | [https://github.com/troessner/reek/blob/v6.0.1/docs/Feature-Envy.md] 35 | My explanation -> There must be something which 'knows' about the contents or purposes of the arguments. 36 | (Tended to ignore this one based on various research) 37 | -------------------------------------------------------------------------------- /knights_travails/board.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Chess board game 4 | class Board 5 | def create_children(parent) 6 | parent.moves.each do |move| 7 | child = find_child(move).nil? ? Knight.new(move) : find_child(move) 8 | parent.children << child 9 | end 10 | end 11 | 12 | def create_family_tree(destination, queue = [@alpha], index = 0) 13 | current = queue[index] 14 | create_children(current) 15 | current.children.each do |child| 16 | next if queue.include?(child) 17 | 18 | queue << child 19 | end 20 | 21 | return if current == find_child(destination) 22 | return if index >= 66 23 | 24 | index += 1 25 | create_family_tree(destination, queue, index) 26 | end 27 | 28 | def find_child(coordinates, queue = [@alpha], index = 0) 29 | found_knight = nil 30 | current = queue[index] 31 | return if current.nil? 32 | 33 | current.children.each do |child| 34 | queue << child unless queue.include?(child) 35 | found_knight = child if child.location == coordinates 36 | end 37 | index += 1 38 | return found_knight unless found_knight.nil? 39 | 40 | find_child(coordinates, queue, index) 41 | end 42 | 43 | def find_path(destination, path = [destination]) 44 | parent = find_parent(destination) 45 | path << parent.location 46 | return path if parent == @alpha 47 | 48 | find_path(parent.location, path) 49 | end 50 | 51 | def find_parent(destination, queue = [@alpha], index = 0) 52 | current = queue[index] 53 | parent = current.moves.any? { |move| move == destination } 54 | return if current.nil? 55 | return current if parent == true 56 | 57 | current.children.each do |child| 58 | queue << child unless queue.include?(child) 59 | end 60 | index += 1 61 | find_parent(destination, queue, index) 62 | end 63 | 64 | def knight_moves(start, destination) 65 | @alpha = Knight.new(start) 66 | create_family_tree(destination) 67 | path = find_path(destination) 68 | puts "You made it in #{path.size - 1} moves! Here's your path:" 69 | path.each_with_index { |move, index| puts "#{index}: #{move}" } 70 | end 71 | 72 | private 73 | 74 | def knight_list 75 | puts 'LIST OF ALL THE KNIGHTS:' 76 | knight_summary = board_locations 77 | knight_summary.each do |knight| 78 | current = find_child(knight) 79 | puts "Location: #{knight}" 80 | puts "Current #{current} at #{current.location}" unless current.nil? 81 | puts '' 82 | end 83 | end 84 | 85 | def board_locations 86 | [0, 1, 2, 3, 4, 5, 6, 7].repeated_permutation(2).to_a 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /bubble_sort.rb: -------------------------------------------------------------------------------- 1 | def bubble_sort array 2 | unsorted = true 3 | while unsorted do 4 | i = 0 5 | unsorted = false 6 | while i < (array.length - 1) 7 | if array[i] > array[i + 1] 8 | array[i], array[i + 1] = array[i + 1], array[i] 9 | unsorted = true 10 | end 11 | i += 1 12 | end 13 | end 14 | print array 15 | end 16 | 17 | bubble_sort([4,3,78,2,0,2]) 18 | puts "" 19 | # => [0,2,2,3,4,78] 20 | 21 | def bubble_sort_by array 22 | unsorted = true 23 | while unsorted do 24 | i = 0 25 | unsorted = false 26 | while i < (array.length - 1) 27 | # If statement that yields to the block of code to calculate the difference of left.length & right.length 28 | if (yield array[i], array[i + 1]).to_i >= 0 29 | # If difference >= 0, then second word is shorter and needs to switch places 30 | array[i], array[i + 1] = array[i + 1], array[i] 31 | unsorted = true 32 | end 33 | i += 1 34 | end 35 | end 36 | print array 37 | end 38 | 39 | bubble_sort_by(["hi","hello","hey"]) do |left,right| 40 | left.length - right.length 41 | end 42 | puts "" 43 | # => ["hi", "hey", "hello"] 44 | 45 | def bubble_sort_by_explanation array 46 | puts "The beginning order of the array: #{array}" 47 | puts "" 48 | unsorted = true 49 | while unsorted do 50 | i = 0 51 | unsorted = false 52 | while i < (array.length - 1) 53 | puts "Yield to calculate the difference of #{array[i]}.length - #{array[i + 1]}.length" 54 | difference = (yield array[i], array[i + 1]).to_i 55 | puts "The difference is #{difference}." 56 | puts "If is < 0 the order second word is longer and nothing changes" 57 | if (difference >= 0) 58 | puts "BUT IF THE DIFFERENCE >= 0 the order second word is shorter & needs to be switched!" 59 | puts "This next line switches the words #{array[i]} and #{array[i + 1]}" 60 | array[i], array[i + 1] = array[i + 1], array[i] 61 | unsorted = true 62 | end 63 | i += 1 64 | puts "#{array} is the current array in while loop" 65 | puts "" 66 | end 67 | end 68 | puts "The ending order of the array: #{array}" 69 | end 70 | 71 | puts "" 72 | puts "EXPLANATION:" 73 | bubble_sort_by_explanation(["labrador", "pug", "beagle"]) do |left,right| 74 | puts "Calculate the difference of #{left}.length - #{right}.length" 75 | left.length - right.length 76 | end 77 | puts "" 78 | # => ["pug", "beagle", "labrador"] -------------------------------------------------------------------------------- /linked_list/linked_list.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'node' 4 | 5 | # LinkedList is a linear collection of data elements called nodes 6 | class LinkedList 7 | attr_accessor :name, :head, :tail, :size 8 | def initialize 9 | @head = nil 10 | @tail = nil 11 | @size = 0 12 | end 13 | 14 | def append(node) 15 | node = Node.new(node) 16 | @head.nil? ? @head = node : @tail.next_node = node 17 | @tail = node 18 | @size += 1 19 | end 20 | 21 | def prepend(node) 22 | node = Node.new(node) 23 | node.next_node = @head 24 | @head = node 25 | @size += 1 26 | end 27 | 28 | def at(index) 29 | return 'Invalid index' if index.is_a?(String) 30 | return 'nil' if index > size - 1 31 | 32 | node = head 33 | index.times do 34 | node = node.next_node 35 | end 36 | node 37 | end 38 | 39 | def pop 40 | return 'nil' if @size.zero? 41 | 42 | @tail = size > 1 ? at(size - 2) : @head 43 | @tail.next_node = nil 44 | @size -= 1 45 | end 46 | 47 | def contains?(value, index = 0) 48 | return false if index >= size 49 | 50 | return true if at(index).value == value 51 | 52 | contains?(value, index + 1) 53 | end 54 | 55 | def find(value, index = 0) 56 | return 'nil' if index >= size 57 | 58 | return index if at(index).value == value 59 | 60 | find(value, index + 1) 61 | end 62 | 63 | def to_s 64 | string = '' 65 | string_node = head 66 | loop do 67 | string += "( #{string_node.value} ) -> " 68 | break if string_node.next_node.nil? 69 | 70 | string_node = string_node.next_node 71 | end 72 | string += 'nil' 73 | end 74 | 75 | def insert_at(value, index) 76 | return 'nil' if index.negative? || index > size 77 | 78 | if index.zero? 79 | prepend(value) 80 | else 81 | node = Node.new(value, at(index)) 82 | at(index - 1).next_node = node 83 | @size += 1 84 | end 85 | end 86 | 87 | def remove_at(index) 88 | return nil unless index.between?(0, size - 1) 89 | 90 | if index.zero? 91 | @head = head.next_node 92 | @size -= 1 93 | elsif index == size - 1 94 | pop 95 | else 96 | at(index - 1).next_node = at(index).next_node 97 | @size -= 1 98 | end 99 | end 100 | end 101 | 102 | names = LinkedList.new 103 | names.append('abby') 104 | names.append('becky') 105 | names.append('carl') 106 | names.append('denise') 107 | puts names.to_s 108 | puts '' 109 | puts "The head's value is #{names.head.value}" 110 | puts names.head 111 | puts "The tails value is #{names.tail.value}" 112 | puts names.tail 113 | puts '' 114 | names.prepend('ethan') 115 | puts names.to_s 116 | puts '' 117 | puts "Number of nodes #{names.size}" 118 | puts '' 119 | puts names.at(2).value 120 | puts names.at(2) 121 | puts '' 122 | puts names.at(5) 123 | puts names.at('ethan') 124 | names.pop 125 | puts names.to_s 126 | puts '' 127 | puts names.contains?('carl') 128 | puts names.contains?('sara') 129 | puts names.find('carl') 130 | puts names.find('sara') 131 | puts names.to_s 132 | puts '' 133 | names.insert_at('ben', 1) 134 | names.insert_at('dave', 4) 135 | names.insert_at('adam', 0) 136 | puts names.to_s 137 | puts '' 138 | puts "The first node is #{names.head.value}" 139 | puts "The last node is #{names.tail.value}" 140 | puts "Number of nodes #{names.size}" 141 | names.remove_at(6) 142 | names.remove_at(0) 143 | puts '' 144 | puts names.to_s 145 | puts "The first node is #{names.head.value}" 146 | puts "The last node is #{names.tail.value}" 147 | puts "Number of nodes #{names.size}" 148 | -------------------------------------------------------------------------------- /wnb-interview-prep/chaser-schedule.md: -------------------------------------------------------------------------------- 1 | # Chaser Schedule (CodeWars) 2 | https://www.codewars.com/kata/628df6b29070907ecb3c2d83 3 | 4 | ## Summary 5 | A runner, who runs with base speed s with duration t will cover a distances d: d = s * t 6 | However, this runner can sprint for one unit of time with double speed s * 2 7 | After sprinting, base speed s will permanently reduced by 1, and for next one unit of time runner will enter recovery phase and can't sprint again. 8 | 9 | Your task, given base speed s and time t, is to find the maximum possible distance d. 10 | 11 | ## My solution 12 | ```rb 13 | def speed_and_time(speed, time_slots) 14 | sprints = efficient_sprint_times(speed, (time_slots.to_f / 2.0).ceil) 15 | runs = efficient_run_times(speed, time_slots, sprints) 16 | runs.sum + sprints.sum 17 | end 18 | 19 | def efficient_sprint_times(speed, max_sprint_slots) 20 | sprint_speeds = [] 21 | max_sprint_slots.times do |i| 22 | sprint_speed = (speed - i) * 2 23 | run_speed = speed - sprint_speeds.count 24 | sprint_speeds << sprint_speed if (sprint_speed + run_speed) >= (speed * 2) 25 | end 26 | sprint_speeds 27 | end 28 | 29 | def efficient_run_times(speed, time_slots, sprint) 30 | sprint_slots = sprint.count 31 | run_slots = time_slots - sprint_slots 32 | default_run_times = Array.new(run_slots, speed) 33 | return default_run_times if sprint_slots == 1 34 | 35 | sprint_slots.times do |slot| 36 | sprint_first = sprint.last > speed && sprint_slots <= run_slots ? 0 : 1 37 | return default_run_times if slot >= run_slots 38 | 39 | default_run_times[slot] = speed - sprint_first - slot 40 | end 41 | 42 | default_run_times 43 | end 44 | 45 | puts speed_and_time(2, 4) == 10 # 2 + 2 + 2 + 4 46 | puts speed_and_time(2, 3) == 8 # 2 + 2 + 4 47 | puts speed_and_time(1, 1) == 2 # 2 48 | puts speed_and_time(9, 8) == 90 # 9 + 18 + 8 + 16 + 7 + 14 + 6 + 12 = 90 49 | puts speed_and_time(8, 3) == 37 # 16 + 7 + 14 = 37 50 | puts speed_and_time(4, 9) == 41 # 4 + 4 + 4 + 4 + 4 + 4 + 9 + 3 + 6 = 41 51 | puts speed_and_time(7, 8) == 68 # 7 + 7 + 7 + 14 + 6 + 12 + 5 + 10 = 68 52 | puts speed_and_time(8, 7) == 71 # 7 + 7 + 14 + 6 + 12 + 5 + 10 = 71 53 | puts speed_and_time(774, 464) == 458_316 54 | puts speed_and_time(900, 237) == 299_337 55 | puts speed_and_time(847, 97) == 120_134 56 | puts speed_and_time(846, 51) == 64_167 57 | puts speed_and_time(633, 263) == 224_097 58 | puts speed_and_time(284, 185) == 66_118 59 | ``` 60 | 61 | ## Favorite Submitted Solutions 62 | 63 | ```rb 64 | # what the heck!?!?! 65 | def SpeedAndTime(s, t) 66 | n = [(t + 1) / 2, s - 2 * s / 3].min 67 | s * (t - 2 * n) + 3 * n * (2 * s - n + 1) / 2 68 | end 69 | 70 | def SpeedAndTime(s, t) 71 | n = [s / 3 + 1, (t + 1) / 2].min 72 | (t + n) * s - n * (n - 1) * 3 / 2 73 | end 74 | 75 | def SpeedAndTime(s, t) 76 | k = [(t + 1) / 2, s / 3 + 1].min 77 | s * t + s * k - 3 * k * (k - 1) / 2 78 | end 79 | 80 | def SpeedAndTime(s, t) 81 | (1..(t/2.to_f).ceil).map { |n| (t * s) - (3/2.to_f * n ** 2) + (3/2.to_f * n) + (s * n) }.max.to_i 82 | end 83 | 84 | def SpeedAndTime(s, t) 85 | [run(s,t), sprint(s,t)].max 86 | end 87 | 88 | def run(s, t) 89 | s<=0 || t<=0 ? 0 : [s + run(s,t-1), t==1 ? s*2 : s*3-1 + sprint(s-1, t-2)].max 90 | end 91 | 92 | def sprint(s, t) 93 | s<=0 || t<=0 ? 0 : t==1 ? s*2 : s*3-1 + sprint(s-1, t-2) 94 | end 95 | 96 | def SpeedAndTime(speed, time) 97 | max_sprints = [speed, time.fdiv(2).ceil].min 98 | 99 | max_sprints.downto(1).map { |sprint_count| 100 | recovery_count = sprint_count - 1 101 | time_at_max_speed = time - sprint_count - recovery_count 102 | distance = time_at_max_speed * speed 103 | 104 | sprint_distance = 2 * sprint_count.times.map { |i| speed - i }.sum 105 | recovery_distance = recovery_count.times.map { |i| speed - i - 1 }.sum 106 | 107 | distance + sprint_distance + recovery_distance 108 | }.max 109 | end 110 | ``` 111 | -------------------------------------------------------------------------------- /enumerable_methods.rb: -------------------------------------------------------------------------------- 1 | module Enumerable 2 | def my_each 3 | for item in self 4 | yield(item) 5 | end 6 | end 7 | 8 | def my_each_with_index 9 | for index in (0..self.length - 1) 10 | yield(self[index], index) 11 | end 12 | end 13 | 14 | def my_select 15 | result = [] 16 | my_each do |item| 17 | result << item if yield(item) 18 | end 19 | result 20 | end 21 | 22 | def my_all? 23 | result = true 24 | my_each do |item| 25 | result = false if !yield(item) 26 | end 27 | result 28 | end 29 | 30 | def my_any? 31 | result = false 32 | my_each do |item| 33 | result = true if yield(item) 34 | end 35 | result 36 | end 37 | 38 | def my_none? 39 | result = false 40 | my_each do |item| 41 | result = true if !yield(item) 42 | end 43 | result 44 | end 45 | 46 | def my_count 47 | return self.length unless block_given? 48 | result = 0 49 | my_each do |item| 50 | result += 1 if yield(item) 51 | end 52 | result 53 | end 54 | 55 | def my_map proc=nil 56 | result = [] 57 | self.my_each do |item| 58 | unless block_given? 59 | result << proc.call(item) 60 | else 61 | result << yield(item) 62 | end 63 | end 64 | result 65 | end 66 | 67 | def my_inject initial=nil 68 | initial.nil? ? result = self[0] : result = initial 69 | initial.nil? ? index = 1 : index = 0 70 | self[index...self.length].my_each do |item| 71 | result = yield(result, item) 72 | end 73 | result 74 | end 75 | 76 | end 77 | 78 | def multiply_els array 79 | array.inject { |result, item| result * item } 80 | end 81 | 82 | # puts "my_each vs. each" 83 | # [1,2,3,4,5].my_each { |item| print item * 2 } 84 | # puts "" 85 | # [1,2,3,4,5].each { |item| print item * 2 } 86 | # puts "" 87 | 88 | # puts "my_each_with_index vs. each_with_index" 89 | # [1,2,3,4,5].my_each_with_index { |item, index| print [item, index] } 90 | # puts "" 91 | # [1,2,3,4,5].each_with_index { |item, index| print [item, index] } 92 | # puts "" 93 | 94 | # puts "my_select vs. select" 95 | # print [1,2,3,4,5].my_select { |num| num.even? } 96 | # puts "" 97 | # print [1,2,3,4,5].select { |num| num.even? } 98 | # puts "" 99 | 100 | # puts "my_all? vs. all?" 101 | # puts [1,2,3,4,5].my_all? { |num| num < 10 } 102 | # puts [1,2,3,4,5].all? { |num| num < 10 } 103 | 104 | # puts "my_any? vs. any?" 105 | # puts [1,2,3,4,5].my_any? { |num| num > 3 } 106 | # puts [1,2,3,4,5].any? { |num| num > 3 } 107 | 108 | # puts "my_none? vs. none?" 109 | # puts [1,2,3,4,5].my_none? { |num| num < 10 } 110 | # puts [1,2,3,4,5].none? { |num| num < 10 } 111 | 112 | # puts "my_count vs. count" 113 | # puts [1,2,3,4,5].my_count {|x| x % 2 == 0} 114 | # puts [1,2,3,4,5].my_count 115 | # puts [1,2,3,4,5].count {|num| num % 2 == 0} 116 | # puts [1,2,3,4,5].count 117 | 118 | # puts "my_map vs. map" 119 | # print [1,2,3,4,5].my_map { |num| num * 2 } 120 | # puts "" 121 | # print [1,2,3,4,5].map { |num| num * 2 } 122 | # puts "" 123 | # puts "my_map with my_proc" 124 | # my_proc = Proc.new {|num| num * 3 } 125 | # print [1,2,3,4,5].my_map my_proc 126 | # puts "" 127 | # puts "my_map with a block and my_proc" 128 | # print [1,2,3,4,5].my_map {|num| num * 4 }.my_map my_proc 129 | # puts "" 130 | 131 | # puts "my_inject vs. inject" 132 | # puts [1,2,3,4,5].my_inject { |sum, num| sum + num } 133 | # puts [1,2,3,4,5].inject { |sum, num| sum + num } 134 | # puts "initial = 2" 135 | # puts [1,2,3,4,5].my_inject(2) { |sum, num| sum + num } 136 | # puts [1,2,3,4,5].inject(2) { |sum, num| sum + num } 137 | # puts "multiplication" 138 | # puts [1,2,3,4,5].my_inject { |sum, num| sum * num } 139 | # puts [1,2,3,4,5].inject { |sum, num| sum * num } 140 | # puts "initial = 2" 141 | # puts [1,2,3,4,5].my_inject(2) { |sum, num| sum * num } 142 | # puts [1,2,3,4,5].inject(2) { |sum, num| sum * num } 143 | 144 | # puts "multiply_els" 145 | # puts multiply_els([2,4,5]) -------------------------------------------------------------------------------- /wnb-interview-prep/typoglycemia_generator.rb: -------------------------------------------------------------------------------- 1 | # Typoglycemia Generator 2 | 3 | # def scramble_words(words) 4 | # symbols = ['-', "'", ',', '.'] 5 | # words_array = words.split(' ') 6 | # # phrase = '' 7 | # phrase = words_array.map do |word| 8 | # middle = word[1..-2].split(//).reject { |letter| symbols.include?(letter) }.sort 9 | # # p middle 10 | # word_array = word.split(//) 11 | # result = '' 12 | # word_array.each_with_index do |letter, index| 13 | # if index.zero? || index == word_array.length - 1 || symbols.include?(letter) || (result.length == 1 && symbols.include?(result[0])) 14 | # result << letter 15 | # else 16 | # result << middle.shift 17 | # end 18 | # # puts "middle: #{middle} and result: #{result}" 19 | # end 20 | # result 21 | # end 22 | # phrase.join(' ') 23 | # end 24 | 25 | # def scramble_words(words) 26 | # symbols = ['-', "'", ',', '.'] 27 | # words_array = words.split(' ') 28 | # phrase = words_array.map do |word| 29 | # skeleton_word = word.split(//).map { |letter| symbols.include?(letter) ? letter : '' } 30 | # first_letter_index = skeleton_word.index('') 31 | # last_letter_index = skeleton_word.length - skeleton_word.reverse.index('') - 1 32 | # middle = word[(first_letter_index + 1)..(last_letter_index - 1)].split(//).reject { |letter| symbols.include?(letter) }.sort 33 | # word_array = word.split(//) 34 | # result = '' 35 | # skeleton_word.each_with_index do |letter, index| 36 | # if index == first_letter_index || index == last_letter_index 37 | # result << word_array[index] 38 | # elsif letter == '' 39 | # result << middle.shift 40 | # else 41 | # result << letter 42 | # end 43 | # end 44 | # result 45 | # end 46 | # phrase.join(' ') 47 | # end 48 | 49 | # SOLUTION TO SEND TO CAROLINE 50 | # def scramble_words(words) 51 | # symbols = ['-', "'", ',', '.'] 52 | # words_array = words.split(' ') 53 | # phrase = words_array.map do |word| 54 | # skeleton_word = word.split(//).map { |letter| symbols.include?(letter) ? letter : '' } 55 | # first_letter_index = skeleton_word.index('') 56 | # last_letter_index = skeleton_word.length - skeleton_word.reverse.index('') - 1 57 | # middle = word[(first_letter_index + 1)..(last_letter_index - 1)].split(//).reject { |letter| symbols.include?(letter) }.sort 58 | # word_array = word.split(//) 59 | # result = '' 60 | # skeleton_word.each_with_index do |letter, index| 61 | # if index == first_letter_index || index == last_letter_index 62 | # result << word_array[index] 63 | # elsif letter == '' 64 | # result << middle.shift 65 | # else 66 | # result << letter 67 | # end 68 | # end 69 | # result 70 | # end 71 | # phrase.join(' ') 72 | # end 73 | 74 | # def scramble_words(words) 75 | # words_array = words.split(' ') 76 | # phrase = words_array.map do |word| 77 | # skeleton_word = word.gsub(/[a-z]/, '?').split(//) 78 | # first_letter_index = skeleton_word.index('?') 79 | # last_letter_index = skeleton_word.length - skeleton_word.reverse.index('?') - 1 80 | # middle_letters = word[(first_letter_index + 1)..(last_letter_index - 1)].gsub(/[-.,']/, '').split(//).sort! 81 | # result = '' 82 | # # map.with_index 83 | # skeleton_word.each_with_index do |letter, index| 84 | # if index == first_letter_index || index == last_letter_index 85 | # result << word[index] 86 | # elsif letter == '?' 87 | # result << middle_letters.shift 88 | # else 89 | # result << letter 90 | # end 91 | # end 92 | # result 93 | # end 94 | # phrase.join(' ') 95 | # end 96 | 97 | def scramble_words(words) 98 | words_array = words.split(' ') 99 | phrase = words_array.map do |word| 100 | skeleton_word = word.gsub(/[a-z]/, '?').split(//) 101 | first_letter_index = skeleton_word.index('?') 102 | last_letter_index = skeleton_word.length - skeleton_word.reverse.index('?') - 1 103 | middle_letters = word[(first_letter_index + 1)..(last_letter_index - 1)].gsub(/[-.,']/, '').split(//).sort! 104 | skeleton_word[first_letter_index] = word[first_letter_index] 105 | skeleton_word[last_letter_index] = word[last_letter_index] 106 | skeleton_word.map do |letter| 107 | letter == '?' ? middle_letters.shift : letter 108 | end.join('') 109 | end 110 | phrase.join(' ') 111 | end 112 | 113 | puts scramble_words('ruby ruby-on-rails') == 'rbuy rabi-ln-oruys' 114 | puts scramble_words('professionals') == 'paefilnoorsss' 115 | puts scramble_words('i') == 'i' 116 | puts scramble_words('') == '' 117 | puts scramble_words('me') == 'me' 118 | puts scramble_words('you') == 'you' 119 | puts scramble_words('card-carrying') == 'caac-dinrrryg' 120 | puts scramble_words("shan't") == "sahn't" 121 | puts scramble_words('-dcba') == '-dbca' 122 | puts scramble_words('dcba.') == 'dbca.' 123 | puts scramble_words("you've gotta dance like there's nobody watching, love like you'll never be hurt, sing like there's nobody listening, and live like it's heaven on earth.") == "you've gotta dacne like teehr's nbdooy wachintg, love like ylo'ul neevr be hrut, sing like teehr's nbdooy leiinnstg, and live like it's haeevn on earth." -------------------------------------------------------------------------------- /caesar_cipher/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # This file was generated by the `rspec --init` command. Conventionally, all 2 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 3 | # The generated `.rspec` file contains `--require spec_helper` which will cause 4 | # this file to always be loaded, without a need to explicitly require it in any 5 | # files. 6 | # 7 | # Given that it is always loaded, you are encouraged to keep this file as 8 | # light-weight as possible. Requiring heavyweight dependencies from this file 9 | # will add to the boot time of your test suite on EVERY test run, even for an 10 | # individual file that may not need all of that loaded. Instead, consider making 11 | # a separate helper file that requires the additional dependencies and performs 12 | # the additional setup, and require it from the spec files that actually need 13 | # it. 14 | # 15 | # The `.rspec` file also contains a few flags that are not defaults but that 16 | # users commonly want. 17 | # 18 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 19 | RSpec.configure do |config| 20 | # rspec-expectations config goes here. You can use an alternate 21 | # assertion/expectation library such as wrong or the stdlib/minitest 22 | # assertions if you prefer. 23 | config.expect_with :rspec do |expectations| 24 | # This option will default to `true` in RSpec 4. It makes the `description` 25 | # and `failure_message` of custom matchers include text for helper methods 26 | # defined using `chain`, e.g.: 27 | # be_bigger_than(2).and_smaller_than(4).description 28 | # # => "be bigger than 2 and smaller than 4" 29 | # ...rather than: 30 | # # => "be bigger than 2" 31 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 32 | end 33 | 34 | # rspec-mocks config goes here. You can use an alternate test double 35 | # library (such as bogus or mocha) by changing the `mock_with` option here. 36 | config.mock_with :rspec do |mocks| 37 | # Prevents you from mocking or stubbing a method that does not exist on 38 | # a real object. This is generally recommended, and will default to 39 | # `true` in RSpec 4. 40 | mocks.verify_partial_doubles = true 41 | end 42 | 43 | # This option will default to `:apply_to_host_groups` in RSpec 4 (and will 44 | # have no way to turn it off -- the option exists only for backwards 45 | # compatibility in RSpec 3). It causes shared context metadata to be 46 | # inherited by the metadata hash of host groups and examples, rather than 47 | # triggering implicit auto-inclusion in groups with matching metadata. 48 | config.shared_context_metadata_behavior = :apply_to_host_groups 49 | 50 | # The settings below are suggested to provide a good initial experience 51 | # with RSpec, but feel free to customize to your heart's content. 52 | =begin 53 | # This allows you to limit a spec run to individual examples or groups 54 | # you care about by tagging them with `:focus` metadata. When nothing 55 | # is tagged with `:focus`, all examples get run. RSpec also provides 56 | # aliases for `it`, `describe`, and `context` that include `:focus` 57 | # metadata: `fit`, `fdescribe` and `fcontext`, respectively. 58 | config.filter_run_when_matching :focus 59 | 60 | # Allows RSpec to persist some state between runs in order to support 61 | # the `--only-failures` and `--next-failure` CLI options. We recommend 62 | # you configure your source control system to ignore this file. 63 | config.example_status_persistence_file_path = "spec/examples.txt" 64 | 65 | # Limits the available syntax to the non-monkey patched syntax that is 66 | # recommended. For more details, see: 67 | # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ 68 | # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 69 | # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode 70 | config.disable_monkey_patching! 71 | 72 | # This setting enables warnings. It's recommended, but in some cases may 73 | # be too noisy due to issues in dependencies. 74 | config.warnings = true 75 | 76 | # Many RSpec users commonly either run the entire suite or an individual 77 | # file, and it's useful to allow more verbose output when running an 78 | # individual spec file. 79 | if config.files_to_run.one? 80 | # Use the documentation formatter for detailed output, 81 | # unless a formatter has already been configured 82 | # (e.g. via a command-line flag). 83 | config.default_formatter = 'doc' 84 | end 85 | 86 | # Print the 10 slowest examples and example groups at the 87 | # end of the spec run, to help surface which specs are running 88 | # particularly slow. 89 | config.profile_examples = 10 90 | 91 | # Run specs in random order to surface order dependencies. If you find an 92 | # order dependency and want to debug it, you can fix the order by providing 93 | # the seed, which is printed after each run. 94 | # --seed 1234 95 | config.order = :random 96 | 97 | # Seed global randomization in this process using the `--seed` CLI option. 98 | # Setting this allows you to use `--seed` to deterministically reproduce 99 | # test failures related to randomization by passing the same `--seed` value 100 | # as the one that triggered the failure. 101 | Kernel.srand config.seed 102 | =end 103 | end 104 | -------------------------------------------------------------------------------- /binary_search_tree/tree.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Tree 4 | def initialize(array) 5 | @root = build_tree(array) 6 | end 7 | 8 | def build_tree(array) 9 | return if array.empty? 10 | 11 | result = array.sort.uniq 12 | return Node.new(result[0]) if result.length <= 1 13 | 14 | middle = result.length / 2 15 | root = Node.new(result[middle]) 16 | root.left = build_tree(result[0...middle]) 17 | root.right = build_tree(result[middle + 1..-1]) 18 | root 19 | end 20 | 21 | # THIS IS NOT CORRECT - For it to be balanced, the left and right subtrees of every node must differ in height by no more than 1. 22 | def balanced? 23 | (depth(@root.left) - depth(@root.right)).abs <= 1 24 | end 25 | 26 | def rebalance! 27 | @root = build_tree(inorder) 28 | end 29 | 30 | def node_list(current = @root, queue = [], result = []) 31 | return result if current.nil? 32 | 33 | node = [current.data] 34 | unless current.left.nil? 35 | node << "L: #{current.left.data}" 36 | queue << current.left 37 | end 38 | unless current.right.nil? 39 | node << "R: #{current.right.data}" 40 | queue << current.right 41 | end 42 | result << node 43 | queue.shift unless result.length == 1 44 | node_list(queue[0], queue, result) 45 | end 46 | 47 | def level_order 48 | queue = [@root] 49 | result = [] 50 | until queue.empty? 51 | node = queue.shift 52 | block_given? ? yield(node) : result << node.data 53 | queue << node.left unless node.left.nil? 54 | queue << node.right unless node.right.nil? 55 | end 56 | result unless block_given? 57 | end 58 | 59 | ORDERS = %w[preorder inorder postorder].freeze 60 | ORDERS.each do |method| 61 | define_method method.to_s do |root = @root, result = [], &block| 62 | return if root.nil? 63 | 64 | traverse(root, result, &block) if method == 'preorder' 65 | send(method, root.left, result, &block) 66 | traverse(root, result, &block) if method == 'inorder' 67 | send(method, root.right, result, &block) 68 | traverse(root, result, &block) if method == 'postorder' 69 | 70 | result unless block 71 | end 72 | end 73 | 74 | def traverse(root, result, &block) 75 | block ? block.call(root.data) : result << root.data 76 | end 77 | 78 | def find_value(value) 79 | inorder { |node| return node if node == value } 80 | # preorder { |node| return node if node == value } 81 | # postorder { |node| return node if node == value } 82 | # level_order { |node| return node if node.data == value } 83 | end 84 | 85 | def insert(value, node = @root) 86 | return if node == value 87 | 88 | insert_left(value, node) if node > value 89 | insert_right(value, node) if node < value 90 | end 91 | 92 | def delete(value) 93 | return nil if find(value).nil? 94 | 95 | parent_node = find_parent(value) 96 | if find(value).left.nil? && find(value).right.nil? 97 | delete_leaf_node(parent_node, value) 98 | elsif find(value).left.nil? || find(value).right.nil? 99 | delete_single_child_node(parent_node, value) 100 | else 101 | delete_double_child_node(parent_node, value) 102 | end 103 | end 104 | 105 | def find(value, current = @root) 106 | return nil if current.nil? 107 | return current if current == value 108 | 109 | current > value ? find(value, current.left) : find(value, current.right) 110 | end 111 | 112 | private 113 | 114 | def depth(node, current_depth = 0) 115 | return current_depth if node.nil? 116 | return current_depth if node.left.nil? && node.right.nil? 117 | 118 | current_depth += 1 119 | 120 | [depth(node.left, current_depth), depth(node.right, current_depth)].max 121 | end 122 | 123 | def insert_left(value, node) 124 | node.left ? insert(value, node.left) : node.left = Node.new(value) 125 | end 126 | 127 | def insert_right(value, node) 128 | node.right ? insert(value, node.right) : node.right = Node.new(value) 129 | end 130 | 131 | def find_parent(value, current = @root) 132 | return nil if @root == value 133 | return current if current.left == value || current.right == value 134 | 135 | current > value ? find_parent(value, current.left) : find_parent(value, current.right) 136 | end 137 | 138 | def delete_leaf_node(parent, value) 139 | parent > value ? parent.left = nil : parent.right = nil 140 | end 141 | 142 | def delete_single_child_node(parent, value) 143 | grandchild = find(value).right if find(value).left.nil? 144 | grandchild = find(value).left if find(value).right.nil? 145 | attach_right(parent, grandchild) if parent.right == find(value) 146 | attach_left(parent, grandchild) if parent.left == find(value) 147 | end 148 | 149 | def delete_double_child_node(parent, value) 150 | replace = find_inorder_successor(value) 151 | delete(replace.data) 152 | attach_right(replace, find(value).right) 153 | attach_left(replace, find(value).left) 154 | attach_right(parent, replace) if parent.right == find(value) 155 | attach_left(parent, replace) if parent.left == find(value) 156 | end 157 | 158 | def attach_right(parent, node) 159 | parent.right = node 160 | end 161 | 162 | def attach_left(parent, node) 163 | parent.left = node 164 | end 165 | 166 | def find_inorder_successor(value, successor = value) 167 | result = find(successor + 1) 168 | return result unless result.nil? 169 | 170 | find_inorder_successor(successor + 1) 171 | end 172 | end 173 | --------------------------------------------------------------------------------