├── .github ├── CODEOWNERS ├── stale.yml └── workflows │ ├── test.yml │ └── update_directory_md.yml ├── .gitignore ├── CONTRIBUTING.md ├── DIRECTORY.md ├── LICENSE.md ├── README.md ├── Rakefile ├── backtracking └── generate_paranthesis.rb ├── bit_manipulation ├── binary_and_operator.rb ├── binary_count_setbits.rb ├── binary_count_trailing_zeroes.rb ├── binary_or_operator.rb ├── binary_xor_operator.rb ├── power_of_two.rb └── single_bit_binary_operations.rb ├── ciphers ├── caesar.rb ├── caesar_test.rb ├── merkle_hellman_cryptosystem.rb └── rsa.rb ├── conversions ├── temperature_conversions.rb └── weight_conversions.rb ├── data_structures ├── arrays │ ├── 3sum.rb │ ├── add_digits.rb │ ├── find_all_duplicates_in_an_array.rb │ ├── find_the_highest_altitude.rb │ ├── fizz_buzz.rb │ ├── get_products_of_all_other_elements.rb │ ├── good_pairs.rb │ ├── intersection.rb │ ├── max_69_number.rb │ ├── maximum_product_subarray.rb │ ├── maximum_subarray.rb │ ├── next_greater_element.rb │ ├── remove_elements.rb │ ├── richest_customer_wealth.rb │ ├── shortest_word_distance.rb │ ├── shuffle_array.rb │ ├── single_number.rb │ ├── sort_squares_of_an_array.rb │ ├── sorted_arrays_intersection.rb │ ├── strings │ │ ├── almost_palindrome_checker.rb │ │ ├── anagram_checker.rb │ │ ├── jewels_and_stones.rb │ │ ├── palindrome.rb │ │ └── remove_vowels.rb │ ├── sudoku.rb │ ├── two_sum.rb │ └── two_sum_ii.rb ├── binary_trees │ ├── avl_tree.rb │ ├── avl_tree_test.rb │ ├── bst.rb │ ├── bst_test.rb │ ├── inorder_traversal.rb │ ├── invert.rb │ ├── postorder_traversal.rb │ └── preorder_traversal.rb ├── disjoint_sets │ └── disjoint_sets.rb ├── graphs │ ├── bfs.rb │ ├── bfs_test.rb │ ├── topological_sort.rb │ ├── topological_sort_test.rb │ ├── unweighted_graph.rb │ ├── unweighted_graph_test.rb │ ├── weighted_graph.rb │ └── weighted_graph_test.rb ├── hash_table │ ├── anagram_checker.rb │ ├── arrays_intersection.rb │ ├── common_characters.rb │ ├── find_all_duplicates_in_an_array.rb │ ├── fizz_buzz.rb │ ├── good_pairs.rb │ ├── isomorphic_strings.rb │ ├── richest_customer_wealth.rb │ ├── two_sum.rb │ └── uncommon_words.rb ├── heaps │ ├── max_heap.rb │ └── max_heap_test.rb ├── linked_lists │ ├── circular_linked_list.rb │ ├── doubly_linked_list.rb │ └── singly_linked_list.rb ├── queues │ ├── circular_queue.rb │ └── queue.rb ├── stacks │ └── stack.rb └── tries │ └── trie.rb ├── discrete_mathematics ├── euclidean_gcd.rb ├── exteded_euclidean_algorithm.rb └── lcm.rb ├── dynamic_programming ├── climbing_stairs.rb ├── coin_change.rb ├── count_sorted_vowel_strings.rb ├── editdistance.rb ├── fibonacci.rb ├── house_robber.rb ├── knapsack.rb ├── ones_and_zeros.rb └── pascal_triangle_ii.rb ├── electronics └── ohms_law.rb ├── maths ├── 3nPlus1.rb ├── abs.rb ├── abs_max.rb ├── abs_min.rb ├── abs_test.rb ├── add.rb ├── add_digits.rb ├── aliquot_sum.rb ├── aliquot_sum_test.rb ├── armstrong_number.rb ├── average_mean.rb ├── average_median.rb ├── binary_to_decimal.rb ├── ceil.rb ├── ceil_test.rb ├── count_sorted_vowel_strings.rb ├── decimal_to_binary.rb ├── factorial.rb ├── factorial_non_recursive_non_iterative.rb ├── fibonacci.rb ├── find_max.rb ├── find_min.rb ├── lucas_series.rb ├── number_of_digits.rb ├── pascal_triangle_ii.rb ├── power_of_two.rb ├── prime_number.rb ├── roman_to_integer.rb ├── square_root.rb ├── square_root_test.rb └── sum_of_digits.rb ├── other ├── fisher_yates.rb └── number_of_days.rb ├── project_euler ├── README.md ├── problem_001 │ └── sol1.rb ├── problem_002 │ └── sol1.rb ├── problem_003 │ ├── sol1.rb │ └── sol2.rb ├── problem_004 │ ├── sol1.rb │ └── sol2.rb ├── problem_005 │ └── sol1.rb ├── problem_006 │ └── sol1.rb ├── problem_007 │ └── sol1.rb ├── problem_010 │ └── sol1.rb ├── problem_014 │ └── sol1.rb ├── problem_020 │ └── sol1.rb ├── problem_021 │ └── sol1.rb ├── problem_022 │ ├── p022_names.txt │ └── sol1.rb └── problem_025 │ └── sol1.rb ├── searches ├── binary_search.rb ├── depth_first_search.rb ├── double_linear_search.rb ├── fibonacci_search.rb ├── jump_search.rb ├── linear_search.rb ├── number_of_islands.rb ├── recursive_double_linear_search.rb ├── recursive_linear_search.rb └── ternary_search.rb ├── sorting ├── bead_sort.rb ├── bead_sort_test.rb ├── binary_insertion_sort.rb ├── binary_insertion_sort_test.rb ├── bogo_sort.rb ├── bogo_sort_test.rb ├── bubble_sort.rb ├── bubble_sort_test.rb ├── bucket_sort.rb ├── bucket_sort_test.rb ├── cocktail_sort.rb ├── cocktail_sort_test.rb ├── comb_sort.rb ├── comb_sort_test.rb ├── counting_sort.rb ├── counting_sort_test.rb ├── gnome_sort.rb ├── gnome_sort_test.rb ├── heap_sort.rb ├── heap_sort_test.rb ├── insertion_sort.rb ├── insertion_sort_test.rb ├── merge_sort.rb ├── merge_sort_test.rb ├── pancake_sort.rb ├── pancake_sort_test.rb ├── quicksort.rb ├── quicksort_test.rb ├── radix_sort.rb ├── radix_sort_test.rb ├── selection_sort.rb ├── selection_sort_test.rb ├── shell_sort.rb ├── shell_sort_test.rb ├── sort_color.rb └── sort_tests.rb └── strings ├── boyer_moore_horspool_search.rb ├── boyer_moore_horspool_search_test.rb ├── hamming_distance.rb ├── max_k_most_frequent_words.rb └── max_k_most_frequent_words_test.rb /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This is a comment. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # More details are here: https://help.github.com/articles/about-codeowners/ 5 | 6 | # The '*' pattern is global owners. 7 | 8 | # Order is important. The last matching pattern has the most precedence. 9 | 10 | /.* @vbrazo @vzvu3k6k 11 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | 3 | # Number of days of inactivity before an Issue or Pull Request becomes stale 4 | daysUntilStale: 30 5 | 6 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. 7 | # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. 8 | daysUntilClose: 7 9 | 10 | # Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) 11 | onlyLabels: [] 12 | 13 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable 14 | exemptLabels: 15 | - "Status: on hold" 16 | 17 | # Set to true to ignore issues in a project (defaults to false) 18 | exemptProjects: false 19 | 20 | # Set to true to ignore issues in a milestone (defaults to false) 21 | exemptMilestones: false 22 | 23 | # Set to true to ignore issues with an assignee (defaults to false) 24 | exemptAssignees: false 25 | 26 | # Label to use when marking as stale 27 | staleLabel: stale 28 | 29 | # Comment to post when removing the stale label. 30 | # unmarkComment: > 31 | # Your comment here. 32 | 33 | # Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': 34 | pulls: 35 | # Comment to post when marking as stale. Set to `false` to disable 36 | markComment: > 37 | This pull request has been automatically marked as stale because it has not had 38 | recent activity. It will be closed if no further activity occurs. Thank you 39 | for your contributions. 40 | 41 | # Comment to post when closing a stale Pull Request. 42 | closeComment: > 43 | Please reopen this pull request once you commit the changes requested 44 | or make improvements on the code. If this is not the case and you need 45 | some help, feel free to seek help from our [Gitter](https://gitter.im/TheAlgorithms) 46 | or ping one of the reviewers. Thank you for your contributions! 47 | 48 | issues: 49 | # Comment to post when marking as stale. Set to `false` to disable 50 | markComment: > 51 | This issue has been automatically marked as stale because it has not had 52 | recent activity. It will be closed if no further activity occurs. Thank you 53 | for your contributions. 54 | 55 | # Comment to post when closing a stale Issue. 56 | closeComment: > 57 | Please reopen this issue once you add more information and updates here. 58 | If this is not the case and you need some help, feel free to seek help 59 | from our [Gitter](https://gitter.im/TheAlgorithms) or ping one of the 60 | reviewers. Thank you for your contributions! 61 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Build/test code 2 | on: [push, pull_request] 3 | jobs: 4 | test: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v4 8 | - uses: ruby/setup-ruby@v1 9 | with: 10 | ruby-version: '3.4' 11 | - name: Run tests 12 | run: rake test 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 The Algorithms 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Algorithms - Ruby 2 | 3 | [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/#TheAlgorithms_community:gitter.im)  4 | [![contributions welcome](https://img.shields.io/static/v1.svg?label=Contributions&message=Welcome&color=0059b3&style=flat-square)](https://github.com/TheAlgorithms/Ruby/blob/master/CONTRIBUTING.md)  5 | ![](https://img.shields.io/github/repo-size/TheAlgorithms/Ruby.svg?label=Repo%20size&style=flat-square)  6 | [![Discord chat](https://img.shields.io/discord/808045925556682782.svg?logo=discord&colorB=7289DA&style=flat-square)](https://discord.gg/c7MnfGFGa6)  7 | 8 | ### All algorithms implemented in Ruby (for education) 9 | 10 | These implementations are for learning purposes only. Therefore they may be less efficient than the implementations in the Ruby standard library. 11 | 12 | ## Contribution Guidelines 13 | 14 | Read our [Contribution Guidelines](CONTRIBUTING.md) before you contribute. 15 | 16 | ## Community Channel 17 | 18 | We're on [Gitter](https://gitter.im/TheAlgorithms)! Please join us. 19 | 20 | ## List of Algorithms 21 | 22 | See our [directory](DIRECTORY.md). 23 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake/testtask' 2 | 3 | Rake::TestTask.new(:test) do |t| 4 | t.test_files = FileList['**/*_test.rb'] 5 | end 6 | 7 | task default: :test 8 | -------------------------------------------------------------------------------- /bit_manipulation/binary_and_operator.rb: -------------------------------------------------------------------------------- 1 | def binary_and(x, y) 2 | raise 'Input must only contain positive integers' if (x < 0) || (y < 0) 3 | 4 | '0b' + (x & y).to_s(2) 5 | end 6 | 7 | begin 8 | binary_and(-1, 0) 9 | rescue StandardError => e 10 | puts e.message 11 | end 12 | # Input must only contain positive integers 13 | 14 | puts binary_and(1, 1) 15 | # 0b1 16 | puts binary_and(0, 1) 17 | # 0b0 18 | puts binary_and(1024, 1024) 19 | # 0b10000000000 20 | puts binary_and(0, 1023) 21 | # 0b0000000000 22 | puts binary_and(16, 58) 23 | # 0b010000 24 | -------------------------------------------------------------------------------- /bit_manipulation/binary_count_setbits.rb: -------------------------------------------------------------------------------- 1 | def binary_count_setbits(x) 2 | raise 'Input must be a positive integer' if x < 0 3 | 4 | binary = x.to_s(2) 5 | 6 | binary.chars.map { |c| c.to_i }.reduce(:+) 7 | end 8 | 9 | begin 10 | binary_count_setbits(-1) 11 | rescue StandardError => e 12 | puts e.message 13 | end 14 | # Input must be a positive integer 15 | 16 | puts binary_count_setbits(0) 17 | # 0 18 | 19 | puts binary_count_setbits(1) 20 | # 1 21 | 22 | puts binary_count_setbits(1024) 23 | # 1 24 | 25 | puts binary_count_setbits(1023) 26 | # 10 27 | -------------------------------------------------------------------------------- /bit_manipulation/binary_count_trailing_zeroes.rb: -------------------------------------------------------------------------------- 1 | def binary_count_trailing_zeroes(x) 2 | raise 'Input must be a positive integer' if x < 0 3 | 4 | binary = x.to_s(2) 5 | 6 | count = 0 7 | binary.chars.reverse_each do |char| 8 | break if char == '1' 9 | 10 | count += 1 11 | end 12 | 13 | count 14 | end 15 | 16 | begin 17 | binary_count_trailing_zeroes(-1) 18 | rescue StandardError => e 19 | puts e.message 20 | end 21 | # Input must be a positive integer 22 | 23 | puts binary_count_trailing_zeroes(0) 24 | # 1 25 | 26 | puts binary_count_trailing_zeroes(1023) 27 | # 0 28 | 29 | puts binary_count_trailing_zeroes(1024) 30 | # 10 31 | 32 | puts binary_count_trailing_zeroes(54) 33 | # 1 34 | 35 | puts binary_count_trailing_zeroes(121_024) 36 | # 6 37 | -------------------------------------------------------------------------------- /bit_manipulation/binary_or_operator.rb: -------------------------------------------------------------------------------- 1 | def binary_or(x, y) 2 | raise 'Input must only contain positive integers' if (x < 0) || (y < 0) 3 | 4 | '0b' + (x | y).to_s(2) 5 | end 6 | 7 | begin 8 | binary_or(-1, 0) 9 | rescue StandardError => e 10 | puts e.message 11 | end 12 | # Input must only contain positive integers 13 | 14 | puts binary_or(1, 1) 15 | # 0b1 16 | puts binary_or(0, 1) 17 | # 0b1 18 | puts binary_or(1024, 1024) 19 | # 0b10000000000 20 | puts binary_or(0, 1023) 21 | # 0b1111111111 22 | puts binary_or(16, 58) 23 | # 0b110010 24 | -------------------------------------------------------------------------------- /bit_manipulation/binary_xor_operator.rb: -------------------------------------------------------------------------------- 1 | def binary_xor(x, y) 2 | raise 'Input must only contain positive integers' if (x < 0) || (y < 0) 3 | 4 | binary_x = x.to_s(2) 5 | binary_y = y.to_s(2) 6 | 7 | if binary_x.length > binary_y.length 8 | prefix = '0' * (binary_x.length - binary_y.length) 9 | binary_y = prefix + binary_y 10 | elsif binary_y.length > binary_x.length 11 | prefix = '0' * (binary_y.length - binary_x.length) 12 | binary_x = prefix + binary_x 13 | end 14 | result = '0b' 15 | binary_x.each_char.with_index do |x_char, i| 16 | y_char = binary_y[i] 17 | 18 | result += if (x_char == '1' && y_char != '1') || (x_char != '1' && y_char == '1') 19 | '1' 20 | else 21 | '0' 22 | end 23 | end 24 | 25 | result 26 | end 27 | 28 | begin 29 | binary_xor(-1, 0) 30 | rescue StandardError => e 31 | puts e.message 32 | end 33 | # Input must only contain positive integers 34 | 35 | puts binary_xor(1, 1) 36 | # 0b0 37 | puts binary_xor(0, 1) 38 | # 0b1 39 | puts binary_xor(1024, 1024) 40 | # 0b00000000000 41 | puts binary_xor(0, 1023) 42 | # 0b1111111111 43 | puts binary_xor(16, 58) 44 | # 0b101010 45 | -------------------------------------------------------------------------------- /bit_manipulation/power_of_two.rb: -------------------------------------------------------------------------------- 1 | # Power of 2 2 | # 3 | # Given an integer n, return true if it is a power of two. Otherwise, return false. 4 | # 5 | # An integer n is a power of two, if there exists an integer x such that n == 2^x. 6 | # 7 | # Example 1: 8 | # Input: n = 1 9 | # Output: true 10 | # Explanation: 2^0 = 1 11 | # 12 | # Example 2: 13 | # Input: n = 16 14 | # Output: true 15 | # Explanation: 2^4 = 16 16 | # 17 | # Example 3: 18 | # Input: n = 3 19 | # Output: false 20 | # 21 | # Example 4: 22 | # Input: n = 4 23 | # Output: true 24 | # 25 | # Example 5: 26 | # Input: n = 5 27 | # Output: false 28 | # 29 | # Constraints: -231 <= n <= 231 - 1 30 | # @param {Integer} n 31 | # @return {Boolean} 32 | # 33 | 34 | # 35 | # Approach 1: Bitwise operators: Turn off the Rightmost 1-bit 36 | # 37 | 38 | # Note that there are two ways of solving this problem via bitwise operations: 39 | # 1. How to get / isolate the rightmost 1-bit: x & (-x). 40 | # 2. How to turn off (= set to 0) the rightmost 1-bit: x & (x - 1). 41 | # In this approach, we're reproducing item 2. 42 | 43 | # Complexity Analysis 44 | # 45 | # Time complexity: O(1). 46 | # Space complexity: O(1). 47 | 48 | def is_power_of_two(n) 49 | return false if n < 1 50 | 51 | n & (n - 1) == 0 52 | end 53 | 54 | n = 1 55 | # Output: true 56 | puts is_power_of_two(n) 57 | n = 16 58 | # Output: true 59 | puts is_power_of_two(n) 60 | n = 3 61 | # Output: false 62 | puts is_power_of_two(n) 63 | n = 4 64 | # Output: true 65 | puts is_power_of_two(n) 66 | n = 5 67 | # Output: false 68 | puts is_power_of_two(n) 69 | -------------------------------------------------------------------------------- /bit_manipulation/single_bit_binary_operations.rb: -------------------------------------------------------------------------------- 1 | def set_bit(x, position) 2 | raise 'position must be >= 0' if position < 0 3 | 4 | x | (1 << position) 5 | end 6 | 7 | puts set_bit(0, 0) 8 | # 1 9 | 10 | puts set_bit(0, 4) 11 | # 16 12 | 13 | puts set_bit(8, 3) 14 | # 8 15 | 16 | puts set_bit(8, 4) 17 | # 24 18 | 19 | begin 20 | puts set_bit(8, -4) 21 | rescue StandardError => e 22 | puts e.message 23 | end 24 | # position must be >= 0 25 | 26 | def clear_bit(x, position) 27 | raise 'position must be > 0' if position < 0 28 | 29 | x & ~(1 << position) 30 | end 31 | 32 | puts clear_bit(0, 0) 33 | # 0 34 | 35 | puts clear_bit(0, 4) 36 | # 0 37 | 38 | puts clear_bit(8, 3) 39 | # 0 40 | 41 | puts clear_bit(24, 4) 42 | # 8 43 | 44 | begin 45 | puts clear_bit(0, -4) 46 | rescue StandardError => e 47 | puts e.message 48 | end 49 | # position must be > 0 50 | 51 | def flip_bit(x, position) 52 | raise 'position must be > 0' if position < 0 53 | 54 | x ^ (1 << position) 55 | end 56 | 57 | puts flip_bit(0, 0) 58 | # 1 59 | 60 | puts flip_bit(0, 4) 61 | # 16 62 | 63 | puts flip_bit(8, 3) 64 | # 0 65 | 66 | puts flip_bit(24, 4) 67 | # 8 68 | 69 | begin 70 | puts flip_bit(0, -4) 71 | rescue StandardError => e 72 | puts e.message 73 | end 74 | # position must be > 0 75 | 76 | def is_bit_set(x, position) 77 | raise 'position must be > 0' if position < 0 78 | 79 | ((x >> position) & 1) == 1 80 | end 81 | 82 | puts is_bit_set(0, 0) 83 | # false 84 | 85 | puts is_bit_set(1, 0) 86 | # true 87 | 88 | puts is_bit_set(8, 3) 89 | # true 90 | 91 | puts is_bit_set(24, 4) 92 | # true 93 | 94 | begin 95 | puts is_bit_set(0, -4) 96 | rescue StandardError => e 97 | puts e.message 98 | end 99 | # position must be > 0 100 | -------------------------------------------------------------------------------- /ciphers/caesar.rb: -------------------------------------------------------------------------------- 1 | # Caesar Cipher replaces characters rotating X number of positions to the left or to the right. 2 | # 3 | # Alphabet 4 | # a b c d e f g h i j k l m n o p q r s t u v w x y z 5 | # 6 | # shift 4 >> it means to rotate 4 places 7 | # 8 | # After shifting 9 | # e f g h i j k l m n o p q r s t u v w x y z a b c d 10 | # 11 | # plaintext -> apple 12 | # ciphertext -> ettpi 13 | 14 | class CaesarCipher 15 | ALPHABET = ('a'..'z').to_a 16 | 17 | def self.encrypt(plaintext, shift) 18 | plaintext.chars.map do |letter| 19 | temp = letter.ord + shift 20 | temp -= ALPHABET.length while temp > 'z'.ord 21 | temp.chr 22 | end.join 23 | end 24 | 25 | def self.decrypt(ciphertext, shift) 26 | ciphertext.chars.map do |letter| 27 | temp = letter.ord - shift 28 | temp += ALPHABET.length while temp < 'a'.ord 29 | temp.chr 30 | end.join 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /ciphers/caesar_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require_relative 'caesar' 3 | 4 | class CaesarCipherTest < Minitest::Test 5 | def test_shift4 6 | run_tests('apple', 'ettpi', 4) 7 | end 8 | 9 | def test_shift27 10 | run_tests('amateur', 'bnbufvs', 27) 11 | end 12 | 13 | private 14 | 15 | def run_tests(plaintext, expected_cipher, shift) 16 | encrypted = CaesarCipher.encrypt(plaintext, shift) 17 | assert_equal encrypted, expected_cipher 18 | decrypted = CaesarCipher.decrypt(encrypted, shift) 19 | assert_equal decrypted, plaintext 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /ciphers/merkle_hellman_cryptosystem.rb: -------------------------------------------------------------------------------- 1 | require 'openssl' 2 | 3 | class MerkleHellman 4 | SMALLEST_KNAPSACK_ITEM = 2**32 5 | STEP = 2**32 6 | 7 | def initialize(size) 8 | @size = size 9 | sum = SMALLEST_KNAPSACK_ITEM 10 | @easy_knapsack = size.times.map do |_k| 11 | x = sum + rand(STEP) 12 | sum += x 13 | x 14 | end 15 | @n = sum + rand(STEP) 16 | 17 | loop do 18 | @a = rand(0..@n) 19 | break if @a.gcd(@n) == 1 20 | end 21 | 22 | @hard_knapsack = @easy_knapsack.map do |x| 23 | (@a * x) % @n 24 | end 25 | end 26 | 27 | def encrypt(msg) 28 | raise ArgumentError, "max length is #{@size / 8} characters" if msg.length * 8 > @size 29 | 30 | c = 0 31 | msg.each_codepoint.reverse_each.with_index do |ch, i| 32 | 7.downto(0) do |j| 33 | wj = ch.>>(j).& 1 34 | c += wj * @hard_knapsack[i * 8 + 7 - j] 35 | end 36 | end 37 | c 38 | end 39 | 40 | def decrypt(c) 41 | p = @a.to_bn.mod_inverse(@n).mod_mul(c, @n).to_i 42 | byte = 0 43 | msg = [] 44 | @easy_knapsack.reverse_each.with_index do |x, i| 45 | bit = 0 46 | if p >= x 47 | p -= x 48 | bit = 1 49 | end 50 | byte |= (bit << (i % 8)) 51 | if i % 8 == 7 52 | msg << byte.chr 53 | byte = 0 54 | end 55 | end 56 | msg.join 57 | end 58 | attr_accessor :hard_knapsack 59 | end 60 | 61 | str = 'Hello there, this is my plaintext' 62 | mh = MerkleHellman.new(str.length * 8) 63 | puts "[*] Encrypting \"#{str}\"" 64 | 65 | c = mh.encrypt(str) 66 | 67 | puts "[*] Ciphertext : #{c}" 68 | 69 | decrypted = mh.decrypt(c) 70 | 71 | puts "[*] after decryption : \"#{decrypted}\"" 72 | -------------------------------------------------------------------------------- /ciphers/rsa.rb: -------------------------------------------------------------------------------- 1 | require 'prime' 2 | 3 | def initialize(keys = {}) 4 | @e ||= keys[:e] 5 | @n ||= keys[:n] 6 | end 7 | 8 | def cipher(message) 9 | message.bytes.map do |byte| 10 | cbyte = ((byte.to_i**e) % n).to_s 11 | missing_chars = n.to_s.size - cbyte.size 12 | '0' * missing_chars + cbyte 13 | end.join 14 | end 15 | 16 | def decipher(ciphed_message) 17 | ciphed_message.chars.each_slice(n.to_s.size).map do |arr| 18 | (arr.join.to_i**d) % n 19 | end.pack('c*') 20 | end 21 | 22 | def public_keys 23 | { n: n, e: e } 24 | end 25 | 26 | private 27 | 28 | def p 29 | @p ||= random_prime_number 30 | end 31 | 32 | def q 33 | @q ||= random_prime_number 34 | end 35 | 36 | def n 37 | @n ||= p * q 38 | end 39 | 40 | def totient 41 | @totient ||= (p - 1) * (q - 1) 42 | end 43 | 44 | def e 45 | @e ||= totient.downto(2).find do |i| 46 | Prime.prime?(i) && totient % i != 0 47 | end 48 | end 49 | 50 | def d 51 | @d ||= invmod(e, totient) 52 | end 53 | 54 | def extended_gcd(a, b) 55 | last_remainder = a.abs 56 | remainder = b.abs 57 | x = 0 58 | last_x = 1 59 | y = 1 60 | last_y = 0 61 | while remainder != 0 62 | (quotient, remainder) = last_remainder.divmod(remainder) 63 | last_remainder = remainder 64 | x, last_x = last_x - quotient * x, x 65 | y, last_y = last_y - quotient * y, y 66 | end 67 | 68 | [last_remainder, last_x * (a < 0 ? -1 : 1)] 69 | end 70 | 71 | def invmod(e, et) 72 | g, x = extended_gcd(e, et) 73 | raise 'The maths are broken!' if g != 1 74 | 75 | x % et 76 | end 77 | 78 | def random_prime_number 79 | number = Random.rand(1..1000) 80 | number = Random.rand(1..1000) until Prime.prime?(number) || number == p || number == q 81 | number 82 | end 83 | 84 | def main 85 | puts 'Enter the message you want to encrypt and decrypt with RSA algorithm: ' 86 | message = gets.chomp.to_s 87 | puts 'Encoded Text:' 88 | puts cipher(message) 89 | puts 'Decoded Text:' 90 | puts decipher(cipher(message)) 91 | puts "p: #{p}" 92 | puts "q: #{q}" 93 | puts "e: #{e}" 94 | puts "d: #{d}" 95 | puts "totient: #{totient}" 96 | end 97 | 98 | main 99 | -------------------------------------------------------------------------------- /conversions/temperature_conversions.rb: -------------------------------------------------------------------------------- 1 | # A ruby program for temperature conversions 2 | 3 | module TemperatureConversion 4 | # celsius -> kelvin = value of celsius + 273.15 => K 5 | def self.celsius_to_kelvin(celsius_input) 6 | kelvin_output = (celsius_input + 273.15).round(2) 7 | puts "#{celsius_input}°C = #{kelvin_output}K" 8 | rescue StandardError 9 | puts 'Error: Please provide number only!' 10 | end 11 | 12 | # kelvin -> celsius = vale of kelvin - 273.15 => °C 13 | def self.kelvin_to_celsius(kelvin_input) 14 | celsius_output = (kelvin_input - 273.15).round(2) 15 | puts "#{kelvin_input}K = #{celsius_output}°C" 16 | rescue StandardError 17 | puts 'Error: Please provide number only!' 18 | end 19 | 20 | # celsius -> fahrenheit = (value of celsius * 9 / 5) + 32 => °F 21 | def self.celsius_to_fahrenheit(celsius_input) 22 | fahrenheit_output = ((celsius_input * 9 / 5) + 32).round(2) 23 | puts "#{celsius_input}°C = #{fahrenheit_output}°F" 24 | rescue StandardError 25 | puts 'Error: Please provide number only!' 26 | end 27 | 28 | # fahrenheit -> celsius = (value of fahrenheit - 32) * 5 / 9 => °C 29 | def self.fahrenheit_to_celsius(fahrenheit_input) 30 | celsius_output = ((fahrenheit_input - 32) * 5 / 9).round(2) 31 | puts "#{fahrenheit_input}°F = #{celsius_output}°C" 32 | rescue StandardError 33 | puts 'Error: Please provide number only!' 34 | end 35 | 36 | # fahrenheit -> kelvin = [(value of fahrenheit - 32) * 5 / 9] + 273.15 => K 37 | def self.fahrenheit_to_kelvin(fahrenheit_input) 38 | kelvin_output = ((fahrenheit_input - 32) * 5 / 9).round(2).round(2) 39 | puts "#{fahrenheit_input}°F = #{kelvin_output}K" 40 | rescue StandardError 41 | puts 'Error: Please provide number only!' 42 | end 43 | 44 | # kelvin -> fahrenheit = [(value of kelvin - 32) * 5 / 9] + 273.15 => K 45 | def self.kelvin_to_fahrenheit(kelvin_input) 46 | fahrenheit_output = (((kelvin_input - 273.15) * 9 / 5) + 32).round(2).round(2) 47 | puts "#{kelvin_input}K = #{fahrenheit_output}°F" 48 | rescue StandardError 49 | puts 'Error: Please provide number only!' 50 | end 51 | end 52 | 53 | # celsius <-> kelvin 54 | TemperatureConversion.celsius_to_kelvin(20) 55 | TemperatureConversion.kelvin_to_celsius(20) 56 | 57 | # Invalid input 58 | TemperatureConversion.kelvin_to_celsius('a') 59 | 60 | # celsius <-> fahrenheit 61 | TemperatureConversion.celsius_to_fahrenheit(-20) 62 | TemperatureConversion.fahrenheit_to_celsius(68) 63 | 64 | # Invalid input 65 | TemperatureConversion.celsius_to_fahrenheit('abc') 66 | 67 | # fahrenheit <-> kelvin 68 | TemperatureConversion.fahrenheit_to_kelvin(60) 69 | TemperatureConversion.kelvin_to_fahrenheit(-60) 70 | 71 | # Invalid input 72 | TemperatureConversion.fahrenheit_to_kelvin('60') 73 | -------------------------------------------------------------------------------- /data_structures/arrays/3sum.rb: -------------------------------------------------------------------------------- 1 | # Given an integer array nums, return all the triplets [nums[i], nums[j], nums[k]] .. 2 | # .. such that i != j, i != k, and j != k, and nums[i] + nums[j] + nums[k] == 0. 3 | # Notice that the solution set must not contain duplicate triplets. 4 | 5 | # Example 1: 6 | # Input: nums = [-1,0,1,2,-1,-4] 7 | # Output: [[-1,-1,2],[-1,0,1]] 8 | 9 | # Example 2: 10 | # Input: nums = [] 11 | # Output: [] 12 | 13 | # Example 3: 14 | # Input: nums = [0] 15 | # Output: [] 16 | 17 | # Constraints: 18 | # 0 <= nums.length <= 3000 19 | #-105 <= nums[i] <= 105 20 | 21 | # Two Pointer Approach - O(n) Time / O(1) Space 22 | # Return edge cases. 23 | # Sort nums, and init ans array 24 | # For each |val, index| in nums: 25 | # if the current value is the same as last, then go to next iteration 26 | # init left and right pointers for two pointer search of the two sum in remaining elements of array 27 | # while left < right: 28 | # find current sum 29 | # if sum > 0, right -= 1 30 | # if sum < 0, left += 1 31 | # if it's 0, then add the values to the answer array, and set the left pointer to the next valid value .. 32 | # .. (left += 1 while nums[left] == nums[left - 1] && left < right) 33 | # Return ans[] 34 | 35 | # @param {Integer[]} nums 36 | # @return {Integer[][]} 37 | def three_sum(nums) 38 | # return if length too short 39 | return [] if nums.length < 3 40 | 41 | # sort nums, init ans array 42 | nums = nums.sort 43 | ans = [] 44 | 45 | # loop through nums 46 | nums.each_with_index do |val, ind| 47 | # if the previous value is the same as current, then skip this iteration as it would create duplicates 48 | next if ind > 0 && nums[ind] == nums[ind - 1] 49 | 50 | # init & run two pointer search 51 | left = ind + 1 52 | right = nums.length - 1 53 | 54 | while left < right 55 | # find current sum 56 | sum = val + nums[left] + nums[right] 57 | 58 | # decrease sum if it's too great, increase sum if it's too low 59 | if sum > 0 60 | right -= 1 61 | elsif sum < 0 62 | left += 1 63 | # if it's zero, then add the answer to array and set left pointer to next valid value 64 | else 65 | ans << [val, nums[left], nums[right]] 66 | 67 | left += 1 68 | 69 | left += 1 while nums[left] == nums[left - 1] && left < right 70 | end 71 | end 72 | end 73 | 74 | # return answer 75 | ans 76 | end 77 | 78 | nums = [-1, 0, 1, 2, -1, -4] 79 | print three_sum(nums) 80 | # Output: [[-1,-1,2],[-1,0,1]] 81 | 82 | nums = [] 83 | print three_sum(nums) 84 | # Output: [] 85 | 86 | nums = [0] 87 | print three_sum(nums) 88 | # Output: [] 89 | -------------------------------------------------------------------------------- /data_structures/arrays/add_digits.rb: -------------------------------------------------------------------------------- 1 | # Challenge name: Add Digits 2 | # 3 | # Given a non-negative integer num, repeatedly add all its digits until the result has only one digit. 4 | # 5 | # Example: 6 | # 7 | # Input: 38 8 | # Output: 2 9 | # Explanation: The process is like: 3 + 8 = 11, 1 + 1 = 2. 10 | # Since 2 has only one digit, return it. 11 | # 12 | # Follow up: 13 | # Could you do it without any loop/recursion in O(1) runtime? 14 | # @param {Integer} num 15 | # @return {Integer} 16 | 17 | # 18 | # Approach 1: Recursion 19 | # 20 | # Time complexity: O(n) 21 | # 22 | def add_digits(num) 23 | return num if num.to_s.length < 2 24 | 25 | digits_to_sum = num.to_s.split('') 26 | sum = 0 27 | digits_to_sum.each do |num| 28 | sum += num.to_i 29 | end 30 | 31 | add_digits(sum) 32 | end 33 | 34 | puts(add_digits(38)) 35 | # # => 2 36 | 37 | puts(add_digits(284)) 38 | # # => 5 39 | 40 | # 41 | # Approach 2: Without recursion 42 | # 43 | def add_digits(num) 44 | until num.to_s.length < 2 45 | digits_to_sum = num.to_s.split('') 46 | num = 0 47 | 48 | digits_to_sum.each do |number| 49 | num += number.to_i 50 | end 51 | end 52 | num 53 | end 54 | 55 | puts(add_digits(38)) 56 | # => 2 57 | 58 | puts(add_digits(284)) 59 | # => 5 60 | -------------------------------------------------------------------------------- /data_structures/arrays/find_all_duplicates_in_an_array.rb: -------------------------------------------------------------------------------- 1 | # Find All Duplicates in an Array 2 | # 3 | # Given an array of integers, 1 ≤ a[i] ≤ n (n = size of array), 4 | # some elements appear twice and others appear once. 5 | # 6 | # Find all the elements that appear twice in this array. 7 | # 8 | # Could you do it without extra space and in O(n) runtime? 9 | # 10 | # Example: 11 | # Input: 12 | # [4,3,2,7,8,2,3,1] 13 | # 14 | # Output: 15 | # [2,3] 16 | 17 | require 'benchmark' 18 | 19 | array = [4, 3, 2, 7, 8, 2, 3, 1] 20 | long_array = [4, 3, 2, 7, 8, 2, 3, 1] * 100 21 | 22 | # 23 | # Approach 1: Brute force 24 | # 25 | 26 | # 27 | # Complexity Analysis 28 | # 29 | # Time complexity: O(n^2) average case. 30 | # 31 | 32 | def find_duplicates(array) 33 | current_num = array[0] 34 | result_array = [] 35 | 36 | array.count.times do |i| 37 | array.count.times do |j| 38 | next if i == j || current_num != array[j] 39 | 40 | result_array.push(current_num) 41 | end 42 | 43 | current_num = array[i + 1] 44 | end 45 | 46 | result_array.uniq 47 | end 48 | 49 | Benchmark.bmbm do |x| 50 | x.report('execute algorithm 1') do 51 | print(find_duplicates(array)) 52 | print(find_duplicates(long_array)) 53 | end 54 | end 55 | 56 | # 57 | # Approach 2: Sort and Compare Adjacent Elements 58 | # 59 | 60 | # Intuition 61 | 62 | # After sorting a list of elements, all elements of equivalent value get placed together. 63 | # Thus, when you sort an array, equivalent elements form contiguous blocks. 64 | 65 | # 66 | # Complexity Analysis 67 | # 68 | # Time complexity: O(n log n) 69 | # 70 | 71 | def find_duplicates(array) 72 | sorted_array = array.sort 73 | result_array = [] 74 | 75 | (1..sorted_array.count).each do |i| 76 | next if sorted_array[i] != sorted_array[i - 1] 77 | 78 | result_array.push(sorted_array[i]) 79 | end 80 | 81 | result_array.uniq 82 | end 83 | 84 | Benchmark.bmbm do |x| 85 | x.report('execute algorithm 2') do 86 | print(find_duplicates(array)) 87 | print(find_duplicates(long_array)) 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /data_structures/arrays/find_the_highest_altitude.rb: -------------------------------------------------------------------------------- 1 | # Find the Highest Altitude 2 | 3 | # There is a biker going on a road trip. The road trip 4 | # consists of n + 1 points at different altitudes. The 5 | # biker starts his trip on point 0 with altitude equal 0. 6 | 7 | # You are given an integer array gain of length n where 8 | # gain[i] is the net gain in altitude between points i​​​​​​ 9 | # and i + 1 for all (0 <= i < n). 10 | 11 | # Return the highest altitude of a point. 12 | 13 | # Example 1: 14 | # 15 | # Input: gain = [-5,1,5,0,-7] 16 | # Output: 1 17 | # Explanation: The altitudes are [0,-5,-4,1,1,-6]. 18 | # The highest is 1. 19 | # 20 | 21 | # 22 | # Approach 1: Creating an additional array 23 | # 24 | 25 | # @param {Integer[]} gain 26 | # @return {Integer} 27 | def largest_altitude(gain) 28 | arr = [0] 29 | 30 | # calculate altitude array 31 | (1..gain.count).each do |pointer| 32 | sum = arr[pointer - 1] + gain[pointer - 1] 33 | arr.push(sum) 34 | end 35 | 36 | # find maximum altitude 37 | max = 0 38 | arr.each { |i| max = i if max < i } 39 | max 40 | end 41 | 42 | gain = [-5, 1, 5, 0, -7] 43 | largest_altitude(gain) 44 | # Output: 1 45 | 46 | gain = [-4, -3, -2, -1, 4, 3, 2] 47 | largest_altitude(gain) 48 | # Output: 0 49 | 50 | # 51 | # Approach 2: Without creating an additional array 52 | # 53 | 54 | # @param {Integer[]} gain 55 | # @return {Integer} 56 | def largest_altitude(gain) 57 | max_alt = alt = 0 58 | 59 | (0...gain.count).each do |i| 60 | alt += gain[i] 61 | 62 | max_alt = alt if max_alt < alt 63 | end 64 | 65 | max_alt 66 | end 67 | 68 | gain = [-5, 1, 5, 0, -7] 69 | largest_altitude(gain) 70 | # Output: 1 71 | 72 | gain = [-4, -3, -2, -1, 4, 3, 2] 73 | largest_altitude(gain) 74 | # Output: 0 75 | -------------------------------------------------------------------------------- /data_structures/arrays/fizz_buzz.rb: -------------------------------------------------------------------------------- 1 | # Write a program that outputs the string representation of numbers 2 | # from 1 to n. But for multiples of three it should output “Fizz” 3 | # instead of the number and for the multiples of five output “Buzz”. 4 | # For numbers which are multiples of both three and five output 5 | # “FizzBuzz”. 6 | 7 | # 8 | # Approach 1: Naive Approach 9 | # 10 | 11 | # Complexity Analysis 12 | 13 | # Time Complexity: O(N) 14 | # Space Complexity: O(1) 15 | 16 | # @param {Integer} n 17 | # @return {String[]} 18 | def fizz_buzz(n) 19 | str = [] 20 | 21 | n.times do |i| 22 | i += 1 23 | 24 | if i % 5 == 0 && i % 3 == 0 25 | str.push('FizzBuzz') 26 | elsif i % 3 == 0 27 | str.push('Fizz') 28 | elsif i % 5 == 0 29 | str.push('Buzz') 30 | else 31 | str.push(i.to_s) 32 | end 33 | end 34 | 35 | str 36 | end 37 | 38 | n = 15 39 | fizz_buzz(n) 40 | # => [ 41 | # "1", 42 | # "2", 43 | # "Fizz", 44 | # "4", 45 | # "Buzz", 46 | # "Fizz", 47 | # "7", 48 | # "8", 49 | # "Fizz", 50 | # "Buzz", 51 | # "11", 52 | # "Fizz", 53 | # "13", 54 | # "14", 55 | # "FizzBuzz" 56 | # ] 57 | 58 | # 59 | # Approach 2: String Concatenation 60 | # 61 | 62 | # Algorithm 63 | # 64 | # Instead of checking for every combination of these conditions, 65 | # check for divisibility by given numbers i.e. 3, 5 as given in the 66 | # problem. If the number is divisible, concatenate the corresponding 67 | # string mapping Fizz or Buzz to the current answer string. 68 | # 69 | # For eg. If we are checking for the number 15, the steps would be: 70 | # 71 | # Condition 1: 15 % 3 == 0 , num_ans_str = "Fizz" 72 | # Condition 2: 15 % 5 == 0 , num_ans_str += "Buzz" 73 | # => num_ans_str = "FizzBuzz" 74 | # 75 | 76 | # Complexity Analysis 77 | # 78 | # Time Complexity: O(N) 79 | # Space Complexity: O(1) 80 | 81 | # @param {Integer} n 82 | # @return {String[]} 83 | def fizz_buzz(n) 84 | str = [] 85 | 86 | n.times do |i| 87 | i += 1 88 | num_str = '' 89 | 90 | num_str += 'Fizz' if i % 3 == 0 91 | num_str += 'Buzz' if i % 5 == 0 92 | 93 | num_str = i.to_s if num_str == '' 94 | 95 | str.push(num_str) 96 | end 97 | 98 | str 99 | end 100 | 101 | n = 15 102 | puts(fizz_buzz(n)) 103 | -------------------------------------------------------------------------------- /data_structures/arrays/good_pairs.rb: -------------------------------------------------------------------------------- 1 | # Challenge name: Number of good pairs 2 | # 3 | # Given an array of integers nums. 4 | # A pair (i,j) is called good if nums[i] == nums[j] and i < j. 5 | # Return the number of good pairs. 6 | # 7 | # @param {Integer[]} nums 8 | # @return {Integer} 9 | # 10 | 11 | # 12 | # Approach 1: Brute Force 13 | # 14 | # Time Complexity: O(n^2) 15 | # 16 | def num_identical_pairs(nums) 17 | count = 0 18 | nums.each_with_index do |num, i| 19 | target = num 20 | nums.each_with_index do |num, j| 21 | next if i >= j 22 | 23 | count += 1 if num == target 24 | end 25 | end 26 | count 27 | end 28 | 29 | nums = [1, 2, 3, 1, 1, 3] 30 | puts(num_identical_pairs(nums)) 31 | # Output: 4 32 | # Explanation: There are 4 good pairs (0,3), (0,4), (3,4), (2,5) 0-indexed. 33 | 34 | nums = [1, 1, 1, 1] 35 | puts(num_identical_pairs(nums)) 36 | # Output: 6 37 | # Explanation: Each pair in the array are good. 38 | 39 | nums = [1, 2, 3] 40 | puts(num_identical_pairs(nums)) 41 | # Output: 0 42 | -------------------------------------------------------------------------------- /data_structures/arrays/max_69_number.rb: -------------------------------------------------------------------------------- 1 | # Challenge name: Maximum 69 number 2 | # 3 | # Given a positive integer num consisting only of digits 6 and 9. 4 | # Return the maximum number you can get by changing at most one digit (6 becomes 9, and 9 becomes 6). 5 | # 6 | # Example 1: 7 | # Input: num = 9669 8 | # Output: 9969 9 | # Explanation: 10 | # Changing the first digit results in 6669. 11 | # Changing the second digit results in 9969. 12 | # Changing the third digit results in 9699. 13 | # Changing the fourth digit results in 9666. 14 | # The maximum number is 9969. 15 | # 16 | # Example 2: 17 | # Input: num = 9996 18 | # Output: 9999 19 | # Explanation: Changing the last digit 6 to 9 results in the maximum number. 20 | 21 | # 22 | # Approach 1: Logical Approach 23 | # Explanation: Changing the first available 6 to a 9 will give the max number 24 | # 25 | def max_number(num) 26 | arr = num.to_s.split('') 27 | 28 | arr.each_with_index do |num, i| 29 | if num == '6' 30 | arr[i] = '9' 31 | return arr.join.to_i 32 | end 33 | end 34 | end 35 | 36 | puts max_number(9669) 37 | # => 9969 38 | 39 | puts max_number(9996) 40 | # => 9999 41 | -------------------------------------------------------------------------------- /data_structures/arrays/maximum_product_subarray.rb: -------------------------------------------------------------------------------- 1 | # Given an integer array nums, find a contiguous non-empty subarray within the array that has the largest product, and return the product. 2 | # It is guaranteed that the answer will fit in a 32-bit integer. 3 | # A subarray is a contiguous subsequence of the array. 4 | 5 | # Example 1: 6 | # Input: nums = [2,3,-2,4] 7 | # Output: 6 8 | # Explanation: [2,3] has the largest product 6. 9 | 10 | # Example 2: 11 | # Input: nums = [-2,0,-1] 12 | # Output: 0 13 | # Explanation: The result cannot be 2, because [-2,-1] is not a subarray. 14 | 15 | # Constraints: 16 | # 1 <= nums.length <= 2 * 104 17 | #-10 <= nums[i] <= 10 18 | # The product of any prefix or suffix of nums is guaranteed to fit in a 32-bit integer. 19 | 20 | # Dynamic Programming Approach (Kadane's Algorithm) - O(n) Time / O(1) Space 21 | # Track both current minimum and current maximum (Due to possibility of multiple negative numbers) 22 | # Answer is the highest value of current maximum 23 | 24 | # @param {Integer[]} nums 25 | # @return {Integer} 26 | def max_product(nums) 27 | return nums[0] if nums.length == 1 28 | 29 | cur_min = 1 30 | cur_max = 1 31 | max = -11 32 | 33 | nums.each do |val| 34 | tmp_cur_max = cur_max 35 | cur_max = [val, val * cur_max, val * cur_min].max 36 | cur_min = [val, val * tmp_cur_max, val * cur_min].min 37 | 38 | max = [max, cur_max].max 39 | end 40 | 41 | max 42 | end 43 | 44 | nums = [2, 3, -2, 4] 45 | puts max_product(nums) 46 | # Output: 6 47 | 48 | nums = [-2, 0, -1] 49 | puts max_product(nums) 50 | # Output: 0 51 | -------------------------------------------------------------------------------- /data_structures/arrays/maximum_subarray.rb: -------------------------------------------------------------------------------- 1 | # Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum. 2 | # A subarray is a contiguous part of an array. 3 | 4 | # Example 1: 5 | # Input: nums = [-2,1,-3,4,-1,2,1,-5,4] 6 | # Output: 6 7 | # Explanation: [4,-1,2,1] has the largest sum = 6. 8 | 9 | # Example 2: 10 | # Input: nums = [1] 11 | # Output: 1 12 | 13 | # Example 3: 14 | # Input: nums = [5,4,-1,7,8] 15 | # Output: 23 16 | 17 | # Constraints: 18 | # 1 <= nums.length <= 3 * 104 19 | # -105 <= nums[i] <= 105 20 | 21 | # Dynamic Programming Approach (Kadane's Algorithm) - O(n) Time / O(1) Space 22 | # 23 | # Init max_sum as first element 24 | # Return first element if the array length is 1 25 | # Init current_sum as 0 26 | # Iterate through the array: 27 | # if current_sum < 0, then reset it to 0 (to eliminate any negative prefixes) 28 | # current_sum += num 29 | # max_sum = current_sum if current_sum is greater than max_sum 30 | # Return max_sum 31 | 32 | # @param {Integer[]} nums 33 | # @return {Integer} 34 | def max_sub_array(nums) 35 | # initialize max sum to first number 36 | max_sum = nums[0] 37 | 38 | # return first number if array length is 1 39 | return max_sum if nums.length == 1 40 | 41 | # init current sum to 0 42 | current_sum = 0 43 | 44 | # iterate through array, reset current_sum to 0 if it ever goes below 0, track max_sum with highest current_sum 45 | nums.each do |num| 46 | current_sum = 0 if current_sum < 0 47 | 48 | current_sum += num 49 | 50 | max_sum = [max_sum, current_sum].max 51 | end 52 | 53 | max_sum 54 | end 55 | 56 | nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4] 57 | print max_sub_array(nums) 58 | # Output: 6 59 | 60 | nums = [1] 61 | print max_sub_array(nums) 62 | # Output: 1 63 | 64 | nums = [5, 4, -1, 7, 8] 65 | print max_sub_array(nums) 66 | # Output: 23 67 | -------------------------------------------------------------------------------- /data_structures/arrays/next_greater_element.rb: -------------------------------------------------------------------------------- 1 | # You are given two integer arrays nums1 and nums2 both of unique elements, where nums1 is a subset of nums2. 2 | # 3 | # Find all the next greater numbers for nums1's elements in the corresponding places of nums2. 4 | # 5 | # The Next Greater Number of a number x in nums1 is the first greater number to its right in nums2. If it does not exist, return -1 for this number. 6 | 7 | # Example 1: 8 | # 9 | # Input: nums1 = [4,1,2], nums2 = [1,3,4,2] 10 | # Output: [-1,3,-1] 11 | # 12 | # Explanation: 13 | # For number 4 in the first array, you cannot find the next greater number for it in the second array, so output -1. 14 | # For number 1 in the first array, the next greater number for it in the second array is 3. 15 | # For number 2 in the first array, there is no next greater number for it in the second array, so output -1. 16 | # 17 | # Example 2: 18 | # 19 | # Input: nums1 = [2,4], nums2 = [1,2,3,4] 20 | # Output: [3,-1] 21 | # 22 | # Explanation: 23 | # For number 2 in the first array, the next greater number for it in the second array is 3. 24 | # For number 4 in the first array, there is no next greater number for it in the second array, so output -1. 25 | 26 | # 27 | # Approach: Brute Force 28 | # 29 | 30 | # Complexity Analysis 31 | # 32 | # Time complexity: O(m*n). The complete nums1 array (of size n) needs to be scanned for all the m elements of nums2 in the worst case. 33 | # Space complexity: O(1). No additional space since we're swapping elements in nums1 and returning the input array. 34 | 35 | # @param {Integer[]} nums1 36 | # @param {Integer[]} nums2 37 | # @return {Integer[]} 38 | def next_greater_element(nums1, nums2) 39 | nums1.each_with_index do |nums1_value, pointer1| 40 | max = 0 41 | pos_nums2 = nums2.find_index(nums1_value) 42 | 43 | nums2[pos_nums2..nums2.count].each do |nums2_value| 44 | if nums2_value > nums1_value 45 | max = nums2_value 46 | break 47 | end 48 | end 49 | 50 | nums1[pointer1] = (nums1_value < max ? max : -1) 51 | end 52 | 53 | nums1 54 | end 55 | 56 | nums1 = [4, 1, 2] 57 | nums2 = [1, 3, 4, 2] 58 | print next_greater_element(nums1, nums2) 59 | # Output: [-1,3,-1] 60 | 61 | nums1 = [2, 4] 62 | nums2 = [1, 2, 3, 4] 63 | print next_greater_element(nums1, nums2) 64 | # Output: [3,-1] 65 | -------------------------------------------------------------------------------- /data_structures/arrays/richest_customer_wealth.rb: -------------------------------------------------------------------------------- 1 | # Challenge name: Richest Customer Wealth 2 | # 3 | # You are given an m x n integer grid accounts where accounts[i][j] 4 | # is the amount of money the i​​​​​​​​​​​th​​​​ customer has in the j​​​​​​​​​​​th​​​​ bank. 5 | # 6 | # Return the wealth that the richest customer has. 7 | # A customer's wealth is the amount of money they have in all 8 | # their bank accounts. The richest customer is the customer that 9 | # has the maximum wealth. 10 | # 11 | # Example 1: 12 | # Input: accounts = [[1,2,3],[3,2,1]] 13 | # Output: 6 14 | # Explanation: 15 | # 1st customer has wealth = 1 + 2 + 3 = 6 16 | # 2nd customer has wealth = 3 + 2 + 1 = 6 17 | # Both customers are considered the richest with a wealth of 6 18 | # each, so return 6. 19 | # 20 | # Example 2: 21 | # Input: accounts = [[1,5],[7,3],[3,5]] 22 | # Output: 10 23 | # Explanation: 24 | # 1st customer has wealth = 6 25 | # 2nd customer has wealth = 10 26 | # 3rd customer has wealth = 8 27 | # The 2nd customer is the richest with a wealth of 10. 28 | # 29 | # Example 3: 30 | # Input: accounts = [[2,8,7],[7,1,3],[1,9,5]] 31 | # Output: 17 32 | 33 | # 34 | # Approach: Brute Force 35 | # 36 | # Time Complexity: O(n) 37 | # 38 | def find_richest_customer_wealth(accounts) 39 | summed_accounts = [] 40 | accounts.each do |customer| 41 | summed = 0 42 | customer.each do |account| 43 | summed += account 44 | end 45 | summed_accounts.push(summed) 46 | end 47 | 48 | summed_accounts.sort.pop 49 | end 50 | 51 | puts find_richest_customer_wealth([[1, 2, 3], [3, 2, 1]]) 52 | # => 6 53 | puts find_richest_customer_wealth([[1, 5], [7, 3], [3, 5]]) 54 | # => 10 55 | puts find_richest_customer_wealth([[2, 8, 7], [7, 1, 3], [1, 9, 5]]) 56 | # => 17 57 | -------------------------------------------------------------------------------- /data_structures/arrays/shortest_word_distance.rb: -------------------------------------------------------------------------------- 1 | # Shortest Word Distance 2 | # Given a list of words and two words word1 and word2, 3 | # return the shortest distance between these two words in the list. 4 | # @param {String[]} words 5 | # @param {String} word1 6 | # @param {String} word2 7 | # @return {Integer} 8 | 9 | def shortest_distance(words, word1, word2) 10 | return 0 if word1 == word2 11 | return 0 unless words.include?(word1) && words.include?(word2) 12 | 13 | minimum_distance = words.length 14 | words.each_with_index do |outer_value, outer_index| 15 | words.each_with_index do |inner_value, inner_index| 16 | if ((inner_value == word1 && outer_value == word2) || (inner_value == word2 && outer_value == word1)) && (minimum_distance > (outer_index - inner_index).abs) 17 | minimum_distance = (outer_index - inner_index).abs 18 | end 19 | end 20 | end 21 | minimum_distance 22 | end 23 | 24 | words = %w[practice makes perfect coding makes] 25 | word1 = 'coding' 26 | word2 = 'practice' 27 | puts(shortest_distance(words, word1, word2)) 28 | # Output: 3 29 | words = %w[practice makes perfect coding makes] 30 | word1 = 'makes' 31 | word2 = 'coding' 32 | puts(shortest_distance(words, word1, word2)) 33 | # Output: 1 34 | -------------------------------------------------------------------------------- /data_structures/arrays/shuffle_array.rb: -------------------------------------------------------------------------------- 1 | # Challenge name: Shuffle the array 2 | # 3 | # Given the array nums consisting of 2n elements 4 | # in the form [x1,x2,...,xn,y1,y2,...,yn]. 5 | # Return the array in the form [x1,y1,x2,y2,...,xn,yn]. 6 | # 7 | # Example 1: 8 | # Input: nums = [2,5,1,3,4,7], n = 3 9 | # Output: [2,3,5,4,1,7] 10 | # Explanation: Since x1=2, x2=5, x3=1, y1=3, y2=4, y3=7 then the answer is [2,3,5,4,1,7]. 11 | # 12 | # Example 2: 13 | # Input: nums = [1,2,3,4,4,3,2,1], n = 4 14 | # Output: [1,4,2,3,3,2,4,1] 15 | # 16 | # Example 3: 17 | # Input: nums = [1,1,2,2], n = 2 18 | # Output: [1,2,1,2] 19 | # 20 | # @param {Integer[]} nums 21 | # @param {Integer} n 22 | # @return {Integer[]} 23 | 24 | # 25 | # Approach 1: New Array 26 | # 27 | # Time Complexity: O(N) 28 | # 29 | def shuffle(nums, n) 30 | result = [] 31 | (0..n - 1).count do |i| 32 | result.push(nums[i], nums[i + n]) 33 | end 34 | result 35 | end 36 | 37 | nums = [2, 5, 1, 3, 4, 7] 38 | n = 3 39 | print(shuffle(nums, n)) 40 | # Output: [2,3,5,4,1,7] 41 | nums = [1, 2, 3, 4, 4, 3, 2, 1] 42 | n = 4 43 | print(shuffle(nums, n)) 44 | # Output: [1,4,2,3,3,2,4,1] 45 | nums = [1, 1, 2, 2] 46 | n = 2 47 | print(shuffle(nums, n)) 48 | # Output: [1,2,1,2] 49 | 50 | # 51 | # Approach 2: Use Ruby methods .insert() and .delete_at() 52 | # 53 | # Time Complexity: O(N) 54 | # 55 | 56 | def shuffle(nums, n) 57 | current_index = 1 58 | (0..n - 1).each do |i| 59 | nums.insert(current_index, nums.delete_at(i + n)) 60 | current_index += 2 61 | end 62 | nums 63 | end 64 | 65 | nums = [2, 5, 1, 3, 4, 7] 66 | n = 3 67 | print(shuffle(nums, n)) 68 | # Output: [2,3,5,4,1,7] 69 | nums = [1, 2, 3, 4, 4, 3, 2, 1] 70 | n = 4 71 | print(shuffle(nums, n)) 72 | # Output: [1,4,2,3,3,2,4,1] 73 | nums = [1, 1, 2, 2] 74 | n = 2 75 | print(shuffle(nums, n)) 76 | # Output: [1,2,1,2] 77 | 78 | # 79 | # Approach 3: Two Pointers 80 | # 81 | # Time Complexity: O(N) 82 | # 83 | 84 | def shuffle(nums, n) 85 | result = [] 86 | p1 = 0 87 | p2 = n 88 | 89 | while p1 < n 90 | result.push(nums[p1], nums[p2]) 91 | p1 += 1 92 | p2 += 1 93 | end 94 | 95 | result 96 | end 97 | 98 | nums = [2, 5, 1, 3, 4, 7] 99 | n = 3 100 | print(shuffle(nums, n)) 101 | # Output: [2,3,5,4,1,7] 102 | nums = [1, 2, 3, 4, 4, 3, 2, 1] 103 | n = 4 104 | print(shuffle(nums, n)) 105 | # Output: [1,4,2,3,3,2,4,1] 106 | nums = [1, 1, 2, 2] 107 | n = 2 108 | print(shuffle(nums, n)) 109 | # Output: [1,2,1,2] 110 | -------------------------------------------------------------------------------- /data_structures/arrays/single_number.rb: -------------------------------------------------------------------------------- 1 | # Challenge name: Single Number 2 | # 3 | # Given a non-empty array of integers nums, every element appears twice 4 | # except for one. Find that single one. 5 | # 6 | # Follow up: Could you implement a solution with a linear runtime 7 | # complexity and without using extra memory? 8 | # 9 | # @param {Integer[]} nums 10 | # @return {Integer} 11 | 12 | # 13 | # Approach 1: Hash map 14 | # 15 | # Time Complexity: O(n) 16 | # 17 | def single_number(nums) 18 | result_hash = {} 19 | nums.each do |num| 20 | if result_hash[num] 21 | result_hash[num] += 1 22 | else 23 | result_hash[num] = 1 24 | end 25 | end 26 | 27 | result_hash.each do |k, v| 28 | return k if v == 1 29 | end 30 | end 31 | 32 | nums = [2, 2, 1] 33 | puts(single_number(nums)) 34 | # Output: 1 35 | nums = [4, 1, 2, 1, 2] 36 | puts(single_number(nums)) 37 | # Output: 4 38 | nums = [1] 39 | puts(single_number(nums)) 40 | # Output: 1 41 | 42 | # 43 | # Approach 2: Use Ruby .count() 44 | # 45 | # Time Complexity: O(n^2) 46 | # 47 | def single_number(nums) 48 | nums.find do |num| 49 | nums.count(num) == 1 50 | end 51 | end 52 | 53 | nums = [2, 2, 1] 54 | puts(single_number(nums)) 55 | # Output: 1 56 | nums = [4, 1, 2, 1, 2] 57 | puts(single_number(nums)) 58 | # Output: 4 59 | nums = [1] 60 | puts(single_number(nums)) 61 | # Output: 1 62 | -------------------------------------------------------------------------------- /data_structures/arrays/sort_squares_of_an_array.rb: -------------------------------------------------------------------------------- 1 | # Arrays - Sorted Squares 2 | 3 | # Algorithm challenge description: 4 | # Given an integer array nums sorted in non-decreasing order 5 | # return an array of the squares of each number sorted in non-decreasing order. 6 | # Input: [4, -1, -9, 2] 7 | # Output: [1, 4, 16, 81] 8 | 9 | # 10 | # Approach 1: is using Ruby function (for sure)! 11 | # 12 | 13 | def sorted_squares(nums) 14 | nums.map! { |num| num**2 }.sort 15 | end 16 | print(sorted_squares([4, -1, -9, 2])) 17 | 18 | # 19 | # Approach 2: is using bubble sort 20 | # 21 | 22 | def bubble_sort(array) 23 | array_length = array.size 24 | return array if array_length <= 1 25 | 26 | loop do 27 | swapped = false 28 | (array_length - 1).times do |i| 29 | if array[i] > array[i + 1] 30 | array[i], array[i + 1] = array[i + 1], array[i] 31 | swapped = true 32 | end 33 | end 34 | break unless swapped 35 | end 36 | array 37 | end 38 | 39 | # 40 | # Time complexity: O(n logn), where n is the length of the array. 41 | # Space complexity: O(n) or O(logn) 42 | # 43 | 44 | def sorted_squares(nums) 45 | # This takes O(n) 46 | nums.map! { |num| num**2 } 47 | # This can take Ο(n logn) 48 | bubble_sort(nums) 49 | end 50 | print(sorted_squares([4, -1, -9, 2])) 51 | 52 | # 53 | # Approach 3: solving without ruby sort method. Using two-pointers 54 | # 55 | # Time complexity: O(n), where n is the length of the array. 56 | # Space complexity: O(n), if you take output into account and O(1) otherwise. 57 | # 58 | def sorted_squares(nums) 59 | p1 = 0 60 | p2 = nums.length - 1 61 | # since we're returing the result in ascending order, 62 | # we'll fill in the array from the end 63 | max_index = p2 64 | output = [] 65 | while p1 < p2 66 | nums1_square = nums[p1] * nums[p1] 67 | nums2_square = nums[p2] * nums[p2] 68 | if nums1_square < nums2_square 69 | output[max_index] = nums2_square 70 | p2 -= 1 71 | elsif nums1_square > nums2_square 72 | output[max_index] = nums1_square 73 | p1 += 1 74 | else 75 | output[max_index] = nums1_square 76 | max_index -= 1 77 | output[max_index] = nums2_square 78 | p1 += 1 79 | p2 -= 1 80 | end 81 | max_index -= 1 82 | end 83 | # to account for any remaining value left in the input array 84 | output[max_index] = nums[p1] * nums[p2] if p1 == p2 85 | output 86 | end 87 | 88 | print(sorted_squares([4, -1, -9, 2])) 89 | -------------------------------------------------------------------------------- /data_structures/arrays/sorted_arrays_intersection.rb: -------------------------------------------------------------------------------- 1 | # Given three integer arrays arr1, arr2 and arr3 sorted in strictly increasing order, return a sorted array of only the integers that appeared in all three arrays. 2 | # 3 | # Example 1: 4 | # 5 | # Input: arr1 = [1,2,3,4,5], arr2 = [1,2,5,7,9], arr3 = [1,3,4,5,8] 6 | # Output: [1,5] 7 | # Explanation: Only 1 and 5 appeared in the three arrays. 8 | # 9 | # Example 2: 10 | # 11 | # Input: arr1 = [197,418,523,876,1356], arr2 = [501,880,1593,1710,1870], arr3 = [521,682,1337,1395,1764] 12 | # Output: [] 13 | # 14 | # 15 | 16 | # 17 | # Approach: Two-pointers 18 | # 19 | 20 | # Complexity Analysis 21 | # 22 | # Time Complexity: O(n), where n is the total length of all of the 23 | # input arrays. 24 | # Space Complexity: O(1), as we only initiate three integer variables 25 | # using constant space. 26 | 27 | # @param {Integer[]} arr1 28 | # @param {Integer[]} arr2 29 | # @param {Integer[]} arr3 30 | # @return {Integer[]} 31 | def sorted_arrays_intersection(arr1, arr2, arr3) 32 | result = [] 33 | 34 | # prepare three pointers to iterate through three arrays 35 | # p1, p2, and p3 point to the beginning of arr1, arr2, and arr3 accordingly 36 | p1 = p2 = p3 = 0 37 | 38 | while (p1 < arr1.count) && (p2 < arr2.count) && (p3 < arr3.count) 39 | if arr1[p1] == arr2[p2] && arr1[p1] == arr3[p3] 40 | result.push(arr1[p1]) 41 | 42 | p1 += 1 43 | p2 += 1 44 | p3 += 1 45 | elsif arr1[p1] < arr2[p2] 46 | p1 += 1 47 | elsif arr2[p2] < arr3[p3] 48 | p2 += 1 49 | else 50 | p3 += 1 51 | end 52 | end 53 | 54 | result 55 | end 56 | 57 | arr1 = [1, 2, 3, 4, 5] 58 | arr2 = [1, 2, 5, 7, 9] 59 | arr3 = [1, 3, 4, 5, 8] 60 | print(sorted_arrays_intersection(arr1, arr2, arr3)) 61 | # Output: [1,5] 62 | 63 | arr1 = [197, 418, 523, 876, 1356] 64 | arr2 = [501, 880, 1593, 1710, 1870] 65 | arr3 = [521, 682, 1337, 1395, 1764] 66 | print(sorted_arrays_intersection(arr1, arr2, arr3)) 67 | # Output: [] 68 | -------------------------------------------------------------------------------- /data_structures/arrays/strings/almost_palindrome_checker.rb: -------------------------------------------------------------------------------- 1 | # Challenge name: Almost Palindrome 2 | # 3 | # Given a string s, return true if the s can be palindrome after deleting at most one character from it. 4 | # 5 | # Example 1: 6 | # Input: s = "aba" 7 | # Output: true 8 | # 9 | # Example 2: 10 | # Input: s = "abca" 11 | # Output: true 12 | # Explanation: You could delete the character 'c'. 13 | # 14 | # Example 3: 15 | # Input: s = "abc" 16 | # Output: false 17 | # 18 | # Constraints: 19 | # 1 <= s.length <= 105 20 | # s consists of lowercase English letters. 21 | 22 | # 23 | # Approach 1: Two Pointers 24 | # 25 | 26 | # Complexity Analysis: 27 | # 28 | # Time Complexity: O(n) 29 | # Space Complexity: O(1) 30 | 31 | def almost_palindrome_checker(string) 32 | p1 = 0 33 | p2 = string.length - 1 34 | array = string.split('') 35 | 36 | while p1 < p2 37 | return palindrome_checker(array, p1, p2 - 1) || palindrome_checker(array, p1 + 1, p2) if array[p1] != array[p2] 38 | 39 | p1 += 1 40 | p2 -= 1 41 | end 42 | 43 | true 44 | end 45 | 46 | def palindrome_checker(array, p1, p2) 47 | while p1 < p2 48 | return false if array[p1] != array[p2] 49 | 50 | p1 += 1 51 | p2 -= 1 52 | end 53 | 54 | true 55 | end 56 | 57 | puts almost_palindrome_checker('aba') 58 | # => true 59 | 60 | puts almost_palindrome_checker('abca') 61 | # => true 62 | 63 | puts almost_palindrome_checker('abc') 64 | # => false 65 | -------------------------------------------------------------------------------- /data_structures/arrays/strings/anagram_checker.rb: -------------------------------------------------------------------------------- 1 | # Challenge name: Is anagram 2 | # 3 | # Given two strings s and t , write a function to determine 4 | # if t is an anagram of s. 5 | # 6 | # Note: 7 | # You may assume the string contains only lowercase alphabets. 8 | # 9 | # Follow up: 10 | # What if the inputs contain unicode characters? 11 | # How would you adapt your solution to such case? 12 | # 13 | # @param {String} s 14 | # @param {String} t 15 | # @return {Boolean} 16 | 17 | # 18 | # Approach: Sort and Compare 19 | # 20 | # Complexity analysis: 21 | # 22 | # Time Complexity: O(n log n). Assume that n is the length of s, sorting costs O(n log n), and comparing two strings costs O(n). Sorting time dominates and the overall time complexity is O(n log n). 23 | # Space Complexity: O(1). Space depends on the sorting implementation which, usually, costs O(1) auxiliary space if heapsort is used. 24 | # 25 | def is_anagram(s, t) 26 | return false if s.length != t.length 27 | 28 | arr1 = s.split('').sort 29 | arr2 = t.split('').sort 30 | 31 | arr1 == arr2 32 | end 33 | 34 | s = 'anagram' 35 | t = 'nagaram' 36 | puts(is_anagram(s, t)) 37 | # => true 38 | s = 'rat' 39 | t = 'car' 40 | puts(is_anagram(s, t)) 41 | # => false 42 | s = 'a' 43 | t = 'ab' 44 | puts(is_anagram(s, t)) 45 | # => false 46 | -------------------------------------------------------------------------------- /data_structures/arrays/strings/jewels_and_stones.rb: -------------------------------------------------------------------------------- 1 | # Challenge name: Jewels and Stones 2 | # 3 | # You're given strings jewels representing the types of stones that are jewels, 4 | # and stones representing the stones you have. Each character in stones is a type 5 | # of stone you have. You want to know how many of the stones you have are also 6 | # jewels. 7 | # 8 | # Letters are case sensitive, so "a" is considered a different type of stone from "A". 9 | # 10 | # Example 1: 11 | # 12 | # Input: jewels = "aA", stones = "aAAbbbb" 13 | # Output: 3 14 | # 15 | # Example 2: 16 | # 17 | # Input: jewels = "z", stones = "ZZ" 18 | # Output: 0 19 | # 20 | # 21 | # Constraints: 22 | # 23 | # 1 <= jewels.length, stones.length <= 50 24 | # jewels and stones consist of only English letters. 25 | # All the characters of jewels are unique. 26 | 27 | # 28 | # Approach 1: Brute Force 29 | # 30 | # Time Complexity: O(n^2) 31 | # 32 | 33 | def find_jewels(jewels, stones) 34 | jewels_array = jewels.split('') 35 | stones_array = stones.split('') 36 | result = 0 37 | jewels_array.each do |jewel| 38 | stones_array.each do |stone| 39 | result += 1 if jewel == stone 40 | end 41 | end 42 | result 43 | end 44 | 45 | puts find_jewels('aA', 'aAAbbbb') 46 | # => 3 47 | puts find_jewels('z', 'ZZ') 48 | # => 0 49 | 50 | # 51 | # Approach 2: Hash 52 | # 53 | # Time Complexity: O(n) 54 | # 55 | 56 | def find_jewels(jewels, stones) 57 | jewels_array = jewels.split('') 58 | stones_array = stones.split('') 59 | result_hash = {} 60 | result = 0 61 | 62 | stones_array.each do |stone| 63 | if result_hash[stone] 64 | result_hash[stone] += 1 65 | else 66 | result_hash[stone] = 1 67 | end 68 | end 69 | 70 | jewels_array.each do |jewel| 71 | if result_hash[jewel] 72 | result += result_hash[jewel] 73 | else 74 | result 75 | end 76 | end 77 | 78 | result 79 | end 80 | 81 | puts find_jewels('aA', 'aAAbbbb') 82 | # => 3 83 | puts find_jewels('z', 'ZZ') 84 | # => 0 85 | -------------------------------------------------------------------------------- /data_structures/arrays/strings/remove_vowels.rb: -------------------------------------------------------------------------------- 1 | # Challenge name: Remove vowels from a string 2 | # 3 | # Given a string s, remove the vowels 'a', 'e', 'i', 'o', and 'u' 4 | # from it, and return the new string. 5 | # 6 | # Example 1: 7 | # Input: s = "leetcodeisacommunityforcoders" 8 | # Output: "ltcdscmmntyfrcdrs" 9 | # 10 | # Example 2: 11 | # Input: s = "aeiou" 12 | # Output: "" 13 | # 14 | # @param {String} s 15 | # @return {String} 16 | 17 | # 18 | # Approach 1: Brute Force 19 | # 20 | # Time Complexity: O(n) 21 | # 22 | 23 | def remove_vowels(s) 24 | result_array = [] 25 | s.downcase! 26 | start_array = s.split('') 27 | 28 | start_array.each do |letter| 29 | result_array.push(letter) if letter != 'a' && letter != 'e' && letter != 'i' && letter != 'o' && letter != 'u' 30 | end 31 | 32 | result_array.join('') 33 | end 34 | 35 | s = 'leetcodeisacommunityforcoders' 36 | puts(remove_vowels(s)) 37 | # => "ltcdscmmntyfrcdrs" 38 | s = 'aeiou' 39 | puts(remove_vowels(s)) 40 | # => "" 41 | 42 | # 43 | # Approach 2: Regex 44 | # 45 | # Time Complexity: O(n) 46 | # 47 | def remove_vowels(s) 48 | vowels = /[aeiou]/i 49 | s.gsub!(vowels, '') 50 | s 51 | end 52 | 53 | s = 'leetcodeisacommunityforcoders' 54 | puts(remove_vowels(s)) 55 | # => "ltcdscmmntyfrcdrs" 56 | s = 'aeiou' 57 | puts(remove_vowels(s)) 58 | # => "" 59 | 60 | # 61 | # Approach 3: Using Ruby .delete() method 62 | # 63 | # Time Complexity: O(n) 64 | # 65 | def remove_vowels(s) 66 | s.downcase.delete('aeiou') 67 | end 68 | 69 | s = 'leetcodeisacommunityforcoders' 70 | puts(remove_vowels(s)) 71 | # => "ltcdscmmntyfrcdrs" 72 | s = 'aeiou' 73 | puts(remove_vowels(s)) 74 | # => "" 75 | -------------------------------------------------------------------------------- /data_structures/arrays/two_sum.rb: -------------------------------------------------------------------------------- 1 | # Challenge name: Two Sum 2 | # 3 | # Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target. 4 | # 5 | # You may assume that each input would have exactly one solution, and you may not use the same element twice. 6 | # 7 | # You can return the answer in any order. 8 | # 9 | # 10 | # Examples 11 | # 12 | # Input: nums = [2, 7, 11, 15], target = 9 13 | # Output: [0,1] 14 | # Explanation: Because nums[0] + nums[1] == 9, we return [0, 1]. 15 | # 16 | # Input: nums = [3, 2, 4], target = 6 17 | # Output: [1,2] 18 | # 19 | # Input: nums = [3, 3], target = 6 20 | # Output: [0,1] 21 | # Explanation: Because nums[0] + nums[1] == 9, we return [0, 1]. 22 | # 23 | # @param {Integer[]} nums 24 | # @param {Integer} target 25 | # @return {Integer[]} 26 | 27 | # 28 | # Approach 1: Brute Force with Addition 29 | # 30 | 31 | # Complexity analysis 32 | 33 | # Time Complexity: O(n^2). For each element, we try to find its complement 34 | # by looping through the rest of the array which takes O(n) time. 35 | # Therefore, the time complexity is O(n^2) 36 | 37 | # Space complexity: O(1) 38 | 39 | def two_sum(nums, target) 40 | result_array = [] 41 | 42 | nums.count.times do |i| 43 | nums.count.times do |j| 44 | next unless i != j && i < j 45 | 46 | current_sum = nums[i] + nums[j] 47 | 48 | return [i, j] if current_sum == target 49 | end 50 | end 51 | end 52 | 53 | print(two_sum([2, 7, 11, 15], 9)) 54 | # => [0,1] 55 | 56 | print(two_sum([3, 2, 4], 6)) 57 | # => [1,2] 58 | 59 | print(two_sum([3, 3], 6)) 60 | # => [0,1] 61 | 62 | # 63 | # Approach 2: Brute Force with Difference 64 | # 65 | # Complexity analysis 66 | # 67 | # Time Complexity: O(N^2), where N is the length of the array 68 | # 69 | def two_sum(nums, target) 70 | nums.each_with_index do |num, i| 71 | target_difference = target - num 72 | 73 | nums.each_with_index do |num, j| 74 | return [i, j] if i != j && num == target_difference 75 | end 76 | end 77 | end 78 | 79 | print(two_sum([2, 7, 11, 15], 9)) 80 | # => [0,1] 81 | 82 | print(two_sum([3, 2, 4], 6)) 83 | # => [1,2] 84 | 85 | print(two_sum([3, 3], 6)) 86 | # => [0,1] 87 | -------------------------------------------------------------------------------- /data_structures/arrays/two_sum_ii.rb: -------------------------------------------------------------------------------- 1 | # Given an array of integers numbers that is already sorted in ascending order, find two numbers such that they add up to a specific target number. 2 | 3 | # Return the indices of the two numbers (1-indexed) as an integer array answer of size 2, where 1 <= answer[0] < answer[1] <= numbers.length. 4 | 5 | # You may assume that each input would have exactly one solution and you may not use the same element twice. 6 | 7 | # 8 | # Approach 1: Two pointers 9 | # 10 | 11 | # Complexity analysis 12 | 13 | # Time complexity: O(n). Each of the n elements is visited at 14 | # most once, thus the time complexity is O(n). 15 | 16 | # Space complexity: O(1). We only use two indexes, the space 17 | # complexity is O(1). 18 | 19 | def two_sum(numbers, target) 20 | i = 0 21 | j = numbers.length - 1 22 | 23 | while i < j 24 | sum = numbers[i] + numbers[j] 25 | 26 | if target < sum 27 | j -= 1 28 | elsif target > sum 29 | i += 1 30 | else 31 | return [i + 1, j + 1] 32 | end 33 | end 34 | end 35 | 36 | nums = [2, 7, 11, 15] 37 | target = 9 38 | print(two_sum(nums, target)) 39 | # => [1,2] 40 | 41 | nums = [2, 3, 4] 42 | target = 6 43 | print(two_sum(nums, target)) 44 | # => [1,3] 45 | 46 | nums = [-1, 0] 47 | target = -1 48 | print(two_sum(nums, target)) 49 | # => [1,2] 50 | -------------------------------------------------------------------------------- /data_structures/binary_trees/inorder_traversal.rb: -------------------------------------------------------------------------------- 1 | # Definition for a binary tree node. 2 | # class TreeNode 3 | # attr_accessor :val, :left, :right 4 | # def initialize(val) 5 | # @val = val 6 | # @left, @right = nil, nil 7 | # end 8 | # end 9 | 10 | # @param {TreeNode} root 11 | # @return {Integer[]} 12 | def inorder_traversal(root) 13 | ans = [] 14 | def traverse(node, ans) 15 | unless node.nil? 16 | traverse(node.left, ans) 17 | ans.push(node.val) 18 | traverse(node.right, ans) 19 | end 20 | end 21 | traverse(root, ans) 22 | ans 23 | end 24 | -------------------------------------------------------------------------------- /data_structures/binary_trees/invert.rb: -------------------------------------------------------------------------------- 1 | # Definition for a binary tree node. 2 | # class TreeNode 3 | # attr_accessor :val, :left, :right 4 | # def initialize(val) 5 | # @val = val 6 | # @left, @right = nil, nil 7 | # end 8 | # end 9 | 10 | # @param {TreeNode} root 11 | # @return {TreeNode} 12 | def invert_tree(root) 13 | return nil if root.nil? 14 | 15 | temp = root.left 16 | root.left = invert_tree(root.right) 17 | root.right = invert_tree(temp) 18 | root 19 | end 20 | -------------------------------------------------------------------------------- /data_structures/binary_trees/postorder_traversal.rb: -------------------------------------------------------------------------------- 1 | # Definition for a binary tree node. 2 | # class TreeNode 3 | # attr_accessor :val, :left, :right 4 | # def initialize(val) 5 | # @val = val 6 | # @left, @right = nil, nil 7 | # end 8 | # end 9 | 10 | # @param {TreeNode} root 11 | # @return {Integer[]} 12 | def postorder_traversal(root) 13 | ans = [] 14 | def traverse(node, ans) 15 | unless node.nil? 16 | traverse(node.left, ans) 17 | traverse(node.right, ans) 18 | ans.push(node.val) 19 | end 20 | end 21 | traverse(root, ans) 22 | ans 23 | end 24 | -------------------------------------------------------------------------------- /data_structures/binary_trees/preorder_traversal.rb: -------------------------------------------------------------------------------- 1 | # Definition for a binary tree node. 2 | # class TreeNode 3 | # attr_accessor :val, :left, :right 4 | # def initialize(val) 5 | # @val = val 6 | # @left, @right = nil, nil 7 | # end 8 | # end 9 | 10 | # @param {TreeNode} root 11 | # @return {Integer[]} 12 | def preorder_traversal(root) 13 | ans = [] 14 | def traverse(node, ans) 15 | unless node.nil? 16 | ans.push(node.val) 17 | traverse(node.left, ans) 18 | traverse(node.right, ans) 19 | end 20 | end 21 | traverse(root, ans) 22 | ans 23 | end 24 | -------------------------------------------------------------------------------- /data_structures/disjoint_sets/disjoint_sets.rb: -------------------------------------------------------------------------------- 1 | class Node 2 | attr_accessor :data, :parent, :rank, :parent, :rank 3 | 4 | def initialize(data) 5 | @data = data 6 | @parent = self 7 | @rank = 0 8 | end 9 | end 10 | 11 | class DisjointSets 12 | def make_set(d) 13 | Node.new(d) 14 | end 15 | 16 | def find_set(x) 17 | raise ArgumentError unless x.class <= Node 18 | 19 | x.parent = (find_set(x.parent)) unless x.parent == x 20 | x.parent 21 | end 22 | 23 | def union_set(x, y) 24 | px = find_set(x) 25 | py = find_set(y) 26 | return if px == py 27 | 28 | if px.rank > py.rank 29 | py.parent = px 30 | elsif py.rank > px.rank 31 | px.parent = py 32 | else 33 | px.parent = py 34 | py.rank += 1 35 | end 36 | end 37 | end 38 | 39 | ds = DisjointSets.new 40 | one = ds.make_set(1) 41 | two = ds.make_set(2) 42 | three = ds.make_set(3) 43 | ds.union_set(one, two) 44 | puts ds.find_set(one) == ds.find_set(two) # should be true 45 | ds.union_set(one, three) 46 | puts ds.find_set(two) == ds.find_set(three) # should be true 47 | puts one.rank + two.rank + three.rank == 1 # should be true 48 | -------------------------------------------------------------------------------- /data_structures/graphs/bfs.rb: -------------------------------------------------------------------------------- 1 | require 'set' 2 | 3 | ## 4 | # This class represents the result of a breadth-first search performed on an unweighted graph. 5 | # 6 | # It exposes: 7 | # - the set of visited nodes 8 | # - a hash of distances by node from the search root node 9 | # (only for visited nodes, 0 for the search root node); 10 | # - a hash of parent nodes by node 11 | # (only for visited nodes, nil for the search root node). 12 | 13 | class GraphBfsResult 14 | attr_reader :visited 15 | attr_reader :parents 16 | attr_reader :distances 17 | 18 | def initialize(visited, parents, distances) 19 | @visited = visited 20 | @parents = parents 21 | @distances = distances 22 | end 23 | end 24 | 25 | ## 26 | # Performs a breadth-first search for the provided graph, starting at the given node. 27 | # Returns the search result (see GraphBfsResult). 28 | # Nodes are consumed using the provided consumers upon being first seen, or being completely visited 29 | # (nothing, by default). 30 | # 31 | # The algorithm has a time complexity of O(|V| + |E|), where: 32 | # - |V| is the number of nodes in the graph; 33 | # - |E| is the number of edges in the graph. 34 | 35 | def bfs(graph, start_node, seen_node_consumer: method(:do_nothing_on_node), visited_node_consumer: method(:do_nothing_on_node)) 36 | seen = Set[] 37 | visited = Set[] 38 | parents = { start_node => nil } 39 | distances = { start_node => 0 } 40 | 41 | seen.add(start_node) 42 | seen_node_consumer.call(start_node) 43 | q = Queue.new 44 | q.push(start_node) 45 | until q.empty? 46 | node = q.pop 47 | for neighbor in graph.neighbors(node) 48 | unless seen.include?(neighbor) 49 | seen.add(neighbor) 50 | distances[neighbor] = distances[node] + 1 51 | parents[neighbor] = node 52 | seen_node_consumer.call(neighbor) 53 | q.push(neighbor) 54 | end 55 | end 56 | visited.add(node) 57 | visited_node_consumer.call(node) 58 | end 59 | 60 | GraphBfsResult.new(visited, parents, distances) 61 | end 62 | 63 | private 64 | def do_nothing_on_node(node) 65 | end 66 | -------------------------------------------------------------------------------- /data_structures/graphs/bfs_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require_relative 'bfs' 3 | require_relative 'unweighted_graph' 4 | 5 | class TestBfs < Minitest::Test 6 | def test_bfs_visits_single_graph_node 7 | graph = UnweightedGraph.new(nodes: [:u, :v, :w], directed: false) 8 | graph.add_edge(:u, :v) 9 | 10 | bfs_result = bfs(graph, :w) 11 | 12 | assert bfs_result.visited.to_set == [:w].to_set 13 | assert bfs_result.parents == { 14 | :w => nil 15 | } 16 | assert bfs_result.distances == { 17 | :w => 0 18 | } 19 | end 20 | 21 | def test_bfs_visits_graph_fully 22 | graph = UnweightedGraph.new(nodes: [:u, :v, :w, :x], directed: false) 23 | graph.add_edge(:u, :v) 24 | graph.add_edge(:u, :w) 25 | graph.add_edge(:w, :x) 26 | 27 | bfs_result = bfs(graph, :u) 28 | 29 | assert bfs_result.visited.to_set == [:u, :v, :w, :x].to_set 30 | assert bfs_result.parents == { 31 | :u => nil, 32 | :v => :u, 33 | :w => :u, 34 | :x => :w 35 | } 36 | assert bfs_result.distances == { 37 | :u => 0, 38 | :v => 1, 39 | :w => 1, 40 | :x => 2 41 | } 42 | end 43 | 44 | def test_bfs_visits_graph_partially 45 | graph = UnweightedGraph.new(nodes: [:u, :v, :w, :x, :y, :z], directed: false) 46 | graph.add_edge(:u, :v) 47 | graph.add_edge(:w, :x) 48 | graph.add_edge(:x, :y) 49 | graph.add_edge(:y, :z) 50 | 51 | bfs_result = bfs(graph, :x) 52 | 53 | assert bfs_result.visited.to_set == [:w, :x, :y, :z].to_set 54 | assert bfs_result.parents == { 55 | :w => :x, 56 | :x => nil, 57 | :y => :x, 58 | :z => :y 59 | } 60 | assert bfs_result.distances == { 61 | :w => 1, 62 | :x => 0, 63 | :y => 1, 64 | :z => 2 65 | } 66 | end 67 | 68 | def test_bfs_visits_with_seen_node_consumer 69 | graph = UnweightedGraph.new(nodes: [:u, :v, :w], directed: false) 70 | graph.add_edge(:u, :v) 71 | graph.add_edge(:u, :w) 72 | 73 | seen_order = [] 74 | bfs(graph, :w, seen_node_consumer: ->(node) { seen_order.append(node) }) 75 | 76 | assert seen_order == [:w, :u, :v] 77 | end 78 | 79 | def test_bfs_visits_with_visited_node_consumer 80 | graph = UnweightedGraph.new(nodes: [:u, :v, :w], directed: false) 81 | graph.add_edge(:u, :v) 82 | graph.add_edge(:u, :w) 83 | 84 | visited_order = [] 85 | bfs(graph, :w, visited_node_consumer: ->(node) { visited_order.append(node) }) 86 | 87 | assert visited_order == [:w, :u, :v] 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /data_structures/graphs/topological_sort.rb: -------------------------------------------------------------------------------- 1 | require 'set' 2 | 3 | ## 4 | # This class aims to provide topological sorting capabilities for directed acyclic graphs. 5 | # 6 | # Topological sorting runs in O(|V|), where |V| is the number of graph nodes. 7 | 8 | class TopologicalSorter 9 | attr_reader :graph 10 | 11 | def initialize(graph) 12 | raise ArgumentError, "Topological sort is only applicable to directed graphs!" unless graph.directed 13 | @graph = graph 14 | end 15 | 16 | def topological_sort 17 | @sorted_nodes = [] 18 | @seen = Set[] 19 | @visited = Set[] 20 | for node in graph.nodes 21 | dfs_visit(node) 22 | end 23 | @sorted_nodes 24 | end 25 | 26 | private 27 | def dfs_visit(node) 28 | return if @visited.include?(node) 29 | raise ArgumentError, "Cycle in graph detected on node #{node}!" if @seen.include?(node) 30 | @seen.add(node) 31 | for neighbor in graph.neighbors(node) 32 | dfs_visit(neighbor) 33 | end 34 | @visited.add(node) 35 | @sorted_nodes.unshift(node) 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /data_structures/graphs/topological_sort_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require_relative 'topological_sort' 3 | require_relative 'unweighted_graph' 4 | 5 | class TestTopologicalSort < Minitest::Test 6 | def test_topological_sort_returns_valid_order_for_acyclic_graph 7 | wardrobe_items = [:underwear, :trousers, :belt, :shirt, :tie, :jacket, :socks, :shoes, :watch] 8 | wardrobe_graph = UnweightedGraph.new(nodes: wardrobe_items, directed: true) 9 | wardrobe_graph.add_edge(:underwear, :trousers) 10 | wardrobe_graph.add_edge(:underwear, :shoes) 11 | wardrobe_graph.add_edge(:socks, :shoes) 12 | wardrobe_graph.add_edge(:trousers, :shoes) 13 | wardrobe_graph.add_edge(:trousers, :belt) 14 | wardrobe_graph.add_edge(:shirt, :belt) 15 | wardrobe_graph.add_edge(:belt, :jacket) 16 | wardrobe_graph.add_edge(:shirt, :tie) 17 | wardrobe_graph.add_edge(:tie, :jacket) 18 | 19 | sorted_items = TopologicalSorter.new(wardrobe_graph).topological_sort 20 | 21 | assert sorted_items.index(:underwear) < sorted_items.index(:trousers) 22 | assert sorted_items.index(:underwear) < sorted_items.index(:shoes) 23 | assert sorted_items.index(:socks) < sorted_items.index(:shoes) 24 | assert sorted_items.index(:trousers) < sorted_items.index(:shoes) 25 | assert sorted_items.index(:trousers) < sorted_items.index(:belt) 26 | assert sorted_items.index(:shirt) < sorted_items.index(:belt) 27 | assert sorted_items.index(:belt) < sorted_items.index(:jacket) 28 | assert sorted_items.index(:shirt) < sorted_items.index(:tie) 29 | assert sorted_items.index(:tie) < sorted_items.index(:jacket) 30 | end 31 | 32 | def test_topological_sort_raises_exception_for_undirected_graph 33 | nodes = [:u, :v] 34 | graph = UnweightedGraph.new(nodes: nodes, directed: false) 35 | graph.add_edge(:u, :v) 36 | 37 | assert_raises ArgumentError do 38 | TopologicalSorter.new(graph).topological_sort 39 | end 40 | end 41 | 42 | def test_topological_sort_raises_exception_for_cyclic_graph 43 | nodes = [:u, :v] 44 | graph = UnweightedGraph.new(nodes: nodes, directed: true) 45 | graph.add_edge(:u, :v) 46 | graph.add_edge(:v, :u) 47 | 48 | assert_raises ArgumentError do 49 | TopologicalSorter.new(graph).topological_sort 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /data_structures/graphs/unweighted_graph.rb: -------------------------------------------------------------------------------- 1 | require 'set' 2 | 3 | ## 4 | # This class aims to represent unweighted graphs 5 | # (i.e. graphs for which edges between nodes have no specific weight associated to them). 6 | # 7 | # Both directed (i.e. an edge between node U and node V does not imply an edge in the opposite direction) 8 | # and undirected graphs are supported, depending on the constructor invocation. 9 | 10 | class UnweightedGraph 11 | attr_reader :nodes 12 | attr_reader :directed 13 | 14 | def initialize(nodes: [], neighbors: {}, directed: true) 15 | @nodes = Set[] 16 | @neighbors = {} 17 | @directed = directed 18 | for node in nodes 19 | add_node(node) 20 | end 21 | neighbors.each do |node, neighbors| 22 | for neighbor in neighbors 23 | add_edge(node, neighbor) 24 | end 25 | end 26 | end 27 | 28 | def add_node(node) 29 | if include?(node) 30 | raise ArgumentError, "node #{node} already exists in this graph!" 31 | end 32 | @nodes.add(node) 33 | @neighbors[node] = Set[] 34 | end 35 | 36 | def add_edge(start_node, end_node) 37 | if has_neighbor?(start_node, end_node) 38 | raise ArgumentError, "node #{start_node} already has an edge to #{end_node} in this graph!" 39 | end 40 | @neighbors[start_node].add(end_node) 41 | @neighbors[end_node].add(start_node) unless directed 42 | end 43 | 44 | def neighbors(node) 45 | unless include?(node) 46 | raise ArgumentError, "node #{node} does not exist in this graph!" 47 | end 48 | @neighbors[node] 49 | end 50 | 51 | def empty? 52 | nodes.empty? 53 | end 54 | 55 | def include?(node) 56 | nodes.include?(node) 57 | end 58 | 59 | def has_neighbor?(start_node, end_node) 60 | neighbors(start_node).include?(end_node) 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /data_structures/graphs/unweighted_graph_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require 'set' 3 | require_relative 'unweighted_graph' 4 | 5 | class TestUnweightedGraph < Minitest::Test 6 | def test_directed_unweighted_graph_creation 7 | graph = UnweightedGraph.new(nodes: [:u, :v, :w], neighbors: {:u => [:v]}, directed: true) 8 | 9 | assert graph.nodes.to_set == Set[:u, :v, :w] 10 | assert graph.neighbors(:u).to_set == Set[:v] 11 | assert graph.neighbors(:v).empty? 12 | assert graph.neighbors(:w).empty? 13 | end 14 | 15 | def test_undirected_unweighted_graph_creation 16 | graph = UnweightedGraph.new(nodes: [:u, :v, :w], neighbors: {:u => [:v]}, directed: false) 17 | 18 | assert graph.nodes.to_set == Set[:u, :v, :w] 19 | assert graph.neighbors(:u).to_set == Set[:v] 20 | assert graph.neighbors(:v).to_set == Set[:u] 21 | assert graph.neighbors(:w).empty? 22 | end 23 | 24 | def test_empty_returns_true_for_empty_graph 25 | graph = UnweightedGraph.new 26 | 27 | assert graph.empty? 28 | end 29 | 30 | def test_empty_returns_false_for_non_empty_graph 31 | graph = UnweightedGraph.new(nodes: [:u]) 32 | 33 | assert !graph.empty? 34 | end 35 | 36 | def test_include_returns_true_for_graph_nodes 37 | graph = UnweightedGraph.new(nodes: [:u]) 38 | 39 | assert graph.include?(:u) 40 | end 41 | 42 | def test_include_returns_false_for_non_graph_nodes 43 | graph = UnweightedGraph.new 44 | 45 | assert !graph.include?(:u) 46 | end 47 | 48 | def test_has_neighbor_returns_true_for_graph_node_neighbors 49 | graph = UnweightedGraph.new(nodes: [:u, :v], neighbors: {:u => [:v]}) 50 | 51 | assert graph.has_neighbor?(:u, :v) 52 | end 53 | 54 | def test_has_neighbor_returns_false_for_non_graph_node_neighbors 55 | graph = UnweightedGraph.new(nodes: [:u, :v]) 56 | 57 | assert !graph.has_neighbor?(:u, :v) 58 | end 59 | 60 | def test_add_node_adds_node_to_graph 61 | graph = UnweightedGraph.new 62 | graph.add_node(:u) 63 | 64 | assert graph.nodes.to_set == Set[:u] 65 | end 66 | 67 | def test_add_edge_adds_edge_to_directed_unweighted_graph 68 | graph = UnweightedGraph.new(nodes: [:u, :v], directed: true) 69 | graph.add_edge(:u, :v) 70 | 71 | assert graph.neighbors(:u).to_set == Set[:v] 72 | assert graph.neighbors(:v).empty? 73 | end 74 | 75 | def test_add_edge_adds_edge_to_undirected_unweighted_graph 76 | graph = UnweightedGraph.new(nodes: [:u, :v], directed: false) 77 | graph.add_edge(:u, :v) 78 | 79 | assert graph.neighbors(:u).to_set == Set[:v] 80 | assert graph.neighbors(:v).to_set == Set[:u] 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /data_structures/graphs/weighted_graph.rb: -------------------------------------------------------------------------------- 1 | require 'set' 2 | 3 | ## 4 | # This class aims to represent weighted graphs 5 | # (i.e. graphs for which edges between nodes have specific weights associated to them). 6 | # 7 | # Both directed (i.e. an edge between node U and node V does not imply an edge in the opposite direction) 8 | # and undirected graphs are supported, depending on the constructor invocation. 9 | 10 | class WeightedGraph 11 | attr_reader :nodes 12 | attr_reader :directed 13 | 14 | def initialize(nodes: [], edges: {}, directed: true) 15 | @nodes = Set[] 16 | @edges = {} 17 | @directed = directed 18 | for node in nodes 19 | add_node(node) 20 | end 21 | edges.each do |node, edges| 22 | for neighbor, weight in edges 23 | add_edge(node, neighbor, weight) 24 | end 25 | end 26 | end 27 | 28 | def add_node(node) 29 | if include?(node) 30 | raise ArgumentError, "node #{node} already exists in this graph!" 31 | end 32 | @nodes.add(node) 33 | @edges[node] = {} 34 | end 35 | 36 | def add_edge(start_node, end_node, weight) 37 | if has_neighbor?(start_node, end_node) 38 | raise ArgumentError, "node #{start_node} already has an edge to #{end_node} in this graph!" 39 | end 40 | @edges[start_node][end_node] = weight 41 | @edges[end_node][start_node] = weight unless directed 42 | end 43 | 44 | def edges(node) 45 | unless include?(node) 46 | raise ArgumentError, "node #{node} does not exist in this graph!" 47 | end 48 | @edges[node] 49 | end 50 | 51 | def empty? 52 | nodes.empty? 53 | end 54 | 55 | def include?(node) 56 | nodes.include?(node) 57 | end 58 | 59 | def has_neighbor?(start_node, end_node) 60 | edges(start_node).key?(end_node) 61 | end 62 | 63 | def edge_weight(start_node, end_node) 64 | edges(start_node)[end_node] 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /data_structures/graphs/weighted_graph_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require 'set' 3 | require_relative 'weighted_graph' 4 | 5 | class TestWeightedGraph < Minitest::Test 6 | def test_directed_weighted_graph_creation 7 | graph = WeightedGraph.new(nodes: [:u, :v, :w], edges: {:u => [[:v, 1]]}, directed: true) 8 | 9 | assert graph.nodes.to_set == Set[:u, :v, :w] 10 | assert graph.edges(:u) == {:v => 1} 11 | assert graph.edges(:v).empty? 12 | assert graph.edges(:w).empty? 13 | end 14 | 15 | def test_undirected_weighted_graph_creation 16 | graph = WeightedGraph.new(nodes: [:u, :v, :w], edges: {:u => [[:v, 1]]}, directed: false) 17 | 18 | assert graph.nodes.to_set == Set[:u, :v, :w] 19 | assert graph.edges(:u) == {:v => 1} 20 | assert graph.edges(:v) == {:u => 1} 21 | assert graph.edges(:w).empty? 22 | end 23 | 24 | def test_empty_returns_true_for_empty_graph 25 | graph = WeightedGraph.new 26 | 27 | assert graph.empty? 28 | end 29 | 30 | def test_empty_returns_false_for_non_empty_graph 31 | graph = WeightedGraph.new(nodes: [:u]) 32 | 33 | assert !graph.empty? 34 | end 35 | 36 | def test_include_returns_true_for_graph_nodes 37 | graph = WeightedGraph.new(nodes: [:u]) 38 | 39 | assert graph.include?(:u) 40 | end 41 | 42 | def test_include_returns_false_for_non_graph_nodes 43 | graph = WeightedGraph.new 44 | 45 | assert !graph.include?(:u) 46 | end 47 | 48 | def test_has_neighbor_returns_true_for_graph_node_neighbors 49 | graph = WeightedGraph.new(nodes: [:u, :v], edges: {:u => [[:v, 1]]}) 50 | 51 | assert graph.has_neighbor?(:u, :v) 52 | end 53 | 54 | def test_has_neighbor_returns_false_for_non_graph_node_neighbors 55 | graph = WeightedGraph.new(nodes: [:u, :v]) 56 | 57 | assert !graph.has_neighbor?(:u, :v) 58 | end 59 | 60 | def test_edge_weight_returns_neighbor_edge_weight 61 | graph = WeightedGraph.new(nodes: [:u, :v], edges: {:u => [[:v, 4]]}) 62 | 63 | assert graph.edge_weight(:u, :v) == 4 64 | end 65 | 66 | def test_add_node_adds_node_to_graph 67 | graph = WeightedGraph.new 68 | graph.add_node(:u) 69 | 70 | assert graph.nodes.to_set == Set[:u] 71 | end 72 | 73 | def test_add_edge_adds_edge_to_directed_weighted_graph 74 | graph = WeightedGraph.new(nodes: [:u, :v], directed: true) 75 | graph.add_edge(:u, :v, 2) 76 | 77 | assert graph.edges(:u) == {:v => 2} 78 | assert graph.edges(:v).empty? 79 | end 80 | 81 | def test_add_edge_adds_edge_to_undirected_weighted_graph 82 | graph = WeightedGraph.new(nodes: [:u, :v], directed: false) 83 | graph.add_edge(:u, :v, 2) 84 | 85 | assert graph.edges(:u) == {:v => 2} 86 | assert graph.edges(:v) == {:u => 2} 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /data_structures/hash_table/arrays_intersection.rb: -------------------------------------------------------------------------------- 1 | # Given three integer arrays arr1, arr2 and arr3 sorted in strictly increasing order, return a sorted array of only the integers that appeared in all three arrays. 2 | # 3 | # Example 1: 4 | # 5 | # Input: arr1 = [1,2,3,4,5], arr2 = [1,2,5,7,9], arr3 = [1,3,4,5,8] 6 | # Output: [1,5] 7 | # Explanation: Only 1 and 5 appeared in the three arrays. 8 | # 9 | # Example 2: 10 | # 11 | # Input: arr1 = [197,418,523,876,1356], arr2 = [501,880,1593,1710,1870], arr3 = [521,682,1337,1395,1764] 12 | # Output: [] 13 | # 14 | # 15 | 16 | # 17 | # Approach: Hash table 18 | # 19 | 20 | # Complexity Analysis 21 | 22 | # Time Complexity: O(n) - n is the total length of 23 | # all of the input arrays. 24 | # Space Complexity: O(n) - n is the total length of all of the 25 | # input arrays. This is because we adopted a Hash table to store all 26 | # numbers and their number of appearances as values. 27 | 28 | def arrays_intersection(arr1, arr2, arr3) 29 | hash = Hash.new(0) 30 | 31 | add_to_hash(arr1, hash) 32 | add_to_hash(arr2, hash) 33 | add_to_hash(arr3, hash) 34 | 35 | hash.select { |_key, value| value == 3 }.keys 36 | end 37 | 38 | def add_to_hash(arr, hash) 39 | arr.count.times do |pointer| 40 | value = arr[pointer] 41 | hash[value] += 1 42 | end 43 | end 44 | 45 | arr1 = [1, 2, 3, 4, 5] 46 | arr2 = [1, 2, 5, 7, 9] 47 | arr3 = [1, 3, 4, 5, 8] 48 | print arrays_intersection(arr1, arr2, arr3) 49 | # Output: [1,5] 50 | # Explanation: Only 1 and 5 appeared in the three arrays. 51 | 52 | arr1 = [197, 418, 523, 876, 1356] 53 | arr2 = [501, 880, 1593, 1710, 1870] 54 | arr3 = [521, 682, 1337, 1395, 1764] 55 | print arrays_intersection(arr1, arr2, arr3) 56 | # Output: [] 57 | -------------------------------------------------------------------------------- /data_structures/hash_table/common_characters.rb: -------------------------------------------------------------------------------- 1 | # Challenge name: Find Common Characters 2 | # 3 | # Given an array A of strings made only from lowercase letters, return a list 4 | # of all characters that show up in all strings within the list 5 | # (including duplicates). For example, if a character occurs 3 times in all 6 | # strings but not 4 times, you need to include that character three times in 7 | # the final answer. 8 | # 9 | # You may return the answer in any order. 10 | # 11 | # Example 1: 12 | # Input: ["bella","label","roller"] 13 | # Output: ["e","l","l"] 14 | # 15 | # Example 2: 16 | # Input: ["cool","lock","cook"] 17 | # Output: ["c","o"] 18 | 19 | # 20 | # Approach 1: Hash 21 | # 22 | # Time Complexity: O(n) 23 | # 24 | def common_characters(arr) 25 | target_count = arr.count 26 | 27 | hash = Hash.new(0) 28 | (0...target_count).each do |i| 29 | arr[i].split('').each do |letter| 30 | hash[letter] += 1 31 | end 32 | end 33 | 34 | result = [] 35 | hash.each do |k, v| 36 | while v >= target_count 37 | if v >= target_count 38 | result << k 39 | v -= target_count 40 | end 41 | end 42 | end 43 | 44 | result 45 | end 46 | 47 | puts common_characters(%w[bella label roller]) 48 | # => ["e","l","l"] 49 | 50 | puts common_characters(%w[cool lock cook]) 51 | # => ["c","o"] 52 | -------------------------------------------------------------------------------- /data_structures/hash_table/find_all_duplicates_in_an_array.rb: -------------------------------------------------------------------------------- 1 | # Find All Duplicates in an Array 2 | # 3 | # Given an array of integers, 1 ≤ a[i] ≤ n (n = size of array), 4 | # some elements appear twice and others appear once. 5 | # 6 | # Find all the elements that appear twice in this array. 7 | # 8 | # Could you do it without extra space and in O(n) runtime? 9 | # 10 | # Example: 11 | # Input: 12 | # [4,3,2,7,8,2,3,1] 13 | # 14 | # Output: 15 | # [2,3] 16 | 17 | require 'benchmark' 18 | 19 | array = [4, 3, 2, 7, 8, 2, 3, 1] 20 | long_array = [4, 3, 2, 7, 8, 2, 3, 1] * 100 21 | 22 | # 23 | # Approach: Hash table 24 | # 25 | 26 | # 27 | # Complexity Analysis 28 | # 29 | # Time complexity: O(n) average case. 30 | # 31 | 32 | def find_duplicates(array) 33 | result_hash = {} 34 | result_array = [] 35 | 36 | # loop through array and build a hash with counters 37 | # where the key is the array element and the counter is the value 38 | # increase counter when duplicate is found 39 | array.each do |num| 40 | if result_hash[num].nil? 41 | result_hash[num] = 1 42 | else 43 | result_hash[num] += 1 44 | end 45 | end 46 | 47 | # loop through hash and look for values > 1 48 | result_hash.each do |k, v| 49 | result_array.push(k) if v > 1 50 | end 51 | 52 | # return keys 53 | result_array 54 | end 55 | 56 | Benchmark.bmbm do |x| 57 | x.report('execute algorithm 3') do 58 | print(find_duplicates(array)) 59 | print(find_duplicates(long_array)) 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /data_structures/hash_table/fizz_buzz.rb: -------------------------------------------------------------------------------- 1 | # Write a program that outputs the string representation of numbers 2 | # from 1 to n. But for multiples of three it should output “Fizz” 3 | # instead of the number and for the multiples of five output “Buzz”. 4 | # For numbers which are multiples of both three and five output 5 | # “FizzBuzz”. 6 | 7 | # 8 | # Approach 1: Hash it! 9 | # 10 | 11 | # Complexity Analysis 12 | 13 | # Time Complexity: O(N) 14 | # Space Complexity: O(1) 15 | 16 | # @param {Integer} n 17 | # @return {String[]} 18 | def fizz_buzz(n, fizz_buzz = { 3 => 'Fizz', 5 => 'Buzz' }) 19 | n.times.map do |i| 20 | i += 1 21 | num_str = '' 22 | 23 | fizz_buzz.each do |key, value| 24 | num_str += value if i % key == 0 25 | end 26 | 27 | num_str.empty? ? i.to_s : num_str 28 | end 29 | end 30 | 31 | n = 15 32 | puts(fizz_buzz(n)) 33 | -------------------------------------------------------------------------------- /data_structures/hash_table/good_pairs.rb: -------------------------------------------------------------------------------- 1 | # Challenge name: Number of good pairs 2 | # 3 | # Given an array of integers nums. 4 | # A pair (i,j) is called good if nums[i] == nums[j] and i < j. 5 | # Return the number of good pairs. 6 | # 7 | # @param {Integer[]} nums 8 | # @return {Integer} 9 | # 10 | 11 | # 12 | # Approach 1: Hash 13 | # 14 | # Time Complexity: O(n) 15 | def num_identical_pairs(nums) 16 | hash = Hash.new(0) 17 | 18 | nums.each do |num| 19 | hash[num] = hash[num] + 1 20 | end 21 | 22 | counter = 0 23 | # Count how many times each number appears. 24 | # If a number appears n times, then n * (n – 1) / 2 good pairs 25 | # can be made with this number. 26 | hash.values.each do |val| 27 | counter += (val * (val - 1) / 2) 28 | end 29 | 30 | counter 31 | end 32 | 33 | nums = [1, 2, 3, 1, 1, 3] 34 | puts(num_identical_pairs(nums)) 35 | # Output: 4 36 | 37 | nums = [1, 1, 1, 1] 38 | puts(num_identical_pairs(nums)) 39 | # Output: 6 40 | 41 | nums = [1, 2, 3] 42 | puts(num_identical_pairs(nums)) 43 | # Output: 0 44 | -------------------------------------------------------------------------------- /data_structures/hash_table/isomorphic_strings.rb: -------------------------------------------------------------------------------- 1 | # Challenge name: Isomorphic Strings 2 | # 3 | # Given two strings s and t, determine if they are isomorphic. 4 | # Two strings s and t are isomorphic if the characters in s can be replaced to get t. 5 | # All occurrences of a character must be replaced with another character while preserving the order of characters. 6 | # No two characters may map to the same character, but a character may map to itself. 7 | # 8 | # Example 1: 9 | # Input: s = "egg", t = "add" 10 | # Output: true 11 | # 12 | # Example 2: 13 | # Input: s = "foo", t = "bar" 14 | # Output: false 15 | # 16 | # Example 3: 17 | # Input: s = "paper", t = "title" 18 | # Output: true 19 | # 20 | # Constraints: 21 | # 1 <= s.length <= 5 * 104 22 | # t.length == s.length 23 | # s and t consist of any valid ascii character. 24 | 25 | # Approach 1: Hash Map 26 | # Time Complexity: O(N) 27 | # Space Complexity: O(N) 28 | 29 | def isomorphic_strings_check(s, t) 30 | # store character mappings 31 | map = {} 32 | # store already mapped characters 33 | set = [] 34 | 35 | (0..s.length - 1).each do |i| 36 | # store characters to compare 37 | char1 = s[i] 38 | char2 = t[i] 39 | 40 | # if char1 is mapped 41 | if map[char1] 42 | # return false if char1 is mapped to a different character than already present 43 | return false if map[char1] != char2 44 | # if char1 is not mapped 45 | else 46 | # return false if char2 is already mapped to a different character 47 | return false if set.include?(char2) 48 | 49 | # checks passed: add new character map and track that char2 has been mapped 50 | map[char1] = char2 51 | set << char2 52 | end 53 | end 54 | true 55 | end 56 | 57 | puts isomorphic_strings_check('egg', 'add') 58 | # => true 59 | 60 | puts isomorphic_strings_check('foo', 'bar') 61 | # => false 62 | 63 | puts isomorphic_strings_check('paper', 'title') 64 | # => true 65 | -------------------------------------------------------------------------------- /data_structures/hash_table/richest_customer_wealth.rb: -------------------------------------------------------------------------------- 1 | # Challenge name: Richest Customer Wealth 2 | # 3 | # You are given an m x n integer grid accounts where accounts[i][j] 4 | # is the amount of money the i​​​​​​​​​​​th​​​​ customer has in the j​​​​​​​​​​​th​​​​ bank. 5 | # 6 | # Return the wealth that the richest customer has. 7 | # A customer's wealth is the amount of money they have in all 8 | # their bank accounts. The richest customer is the customer that 9 | # has the maximum wealth. 10 | # 11 | # Example 1: 12 | # Input: accounts = [[1,2,3],[3,2,1]] 13 | # Output: 6 14 | # Explanation: 15 | # 1st customer has wealth = 1 + 2 + 3 = 6 16 | # 2nd customer has wealth = 3 + 2 + 1 = 6 17 | # Both customers are considered the richest with a wealth of 6 18 | # each, so return 6. 19 | # 20 | # Example 2: 21 | # Input: accounts = [[1,5],[7,3],[3,5]] 22 | # Output: 10 23 | # Explanation: 24 | # 1st customer has wealth = 6 25 | # 2nd customer has wealth = 10 26 | # 3rd customer has wealth = 8 27 | # The 2nd customer is the richest with a wealth of 10. 28 | # 29 | # Example 3: 30 | # Input: accounts = [[2,8,7],[7,1,3],[1,9,5]] 31 | # Output: 17 32 | 33 | # 34 | # Approach: Hash 35 | # 36 | # Time Complexity: O(n) 37 | # 38 | def find_richest_customer_wealth(accounts) 39 | result_hash = {} 40 | accounts.each_with_index do |customer, i| 41 | result_hash[i] = customer.sum 42 | end 43 | 44 | highest_value = 0 45 | result_hash.each do |_k, v| 46 | highest_value = v if v > highest_value 47 | end 48 | 49 | highest_value 50 | end 51 | 52 | puts find_richest_customer_wealth([[1, 2, 3], [3, 2, 1]]) 53 | # => 6 54 | puts find_richest_customer_wealth([[1, 5], [7, 3], [3, 5]]) 55 | # => 10 56 | puts find_richest_customer_wealth([[2, 8, 7], [7, 1, 3], [1, 9, 5]]) 57 | # => 17 58 | -------------------------------------------------------------------------------- /data_structures/hash_table/two_sum.rb: -------------------------------------------------------------------------------- 1 | # Challenge name: Two Sum 2 | # 3 | # Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target. 4 | # 5 | # You may assume that each input would have exactly one solution, and you may not use the same element twice. 6 | # 7 | # You can return the answer in any order. 8 | # 9 | # 10 | # Examples 11 | # 12 | # Input: nums = [2, 7, 11, 15], target = 9 13 | # Output: [0,1] 14 | # Explanation: Because nums[0] + nums[1] == 9, we return [0, 1]. 15 | # 16 | # Input: nums = [3, 2, 4], target = 6 17 | # Output: [1,2] 18 | # 19 | # Input: nums = [3, 3], target = 6 20 | # Output: [0,1] 21 | # Explanation: Because nums[0] + nums[1] == 9, we return [0, 1]. 22 | # 23 | # @param {Integer[]} nums 24 | # @param {Integer} target 25 | # @return {Integer[]} 26 | 27 | # 28 | # Approach: Using Hash table 29 | # 30 | 31 | # Complexity analysis 32 | 33 | # Time complexity: O(n). We traverse the list containing n elements exactly twice. 34 | # Since the hash table reduces the lookup time to O(1), the time complexity is O(n). 35 | 36 | # Space complexity: O(n). The extra space required depends on the number of items 37 | # stored in the hash table, which stores exactly n elements. 38 | 39 | def two_sum(nums, target) 40 | hash = {} 41 | 42 | # create a hash to store values and their indices 43 | nums.each_with_index do |num, i| 44 | hash[num] = i 45 | end 46 | 47 | # iterate over nums array to find the target (difference between sum target and num) 48 | nums.each_with_index do |num, i| 49 | difference_target = target - num 50 | 51 | return [i, hash[difference_target]] if hash[difference_target] && hash[difference_target] != i 52 | end 53 | end 54 | 55 | nums = [2, 7, 11, 15] 56 | target = 9 57 | print(two_sum(nums, target)) 58 | # => [0,1] 59 | 60 | nums = [3, 2, 4] 61 | target = 6 62 | print(two_sum(nums, target)) 63 | # => [1,2] 64 | 65 | nums = [3, 3] 66 | target = 6 67 | print(two_sum(nums, target)) 68 | # => [0,1] 69 | -------------------------------------------------------------------------------- /data_structures/hash_table/uncommon_words.rb: -------------------------------------------------------------------------------- 1 | # Challenge name: Uncommon words from two sentences 2 | # 3 | # We are given two sentences A and B. 4 | # (A sentence is a string of space separated words. 5 | # Each word consists only of lowercase letters.) 6 | # 7 | # A word is uncommon if it appears exactly once in one of the sentences, 8 | # and does not appear in the other sentence. 9 | # 10 | # Return a list of all uncommon words. 11 | # You may return the list in any order. 12 | # 13 | # Example 1: 14 | # Input: A = "this apple is sweet", B = "this apple is sour" 15 | # Output: ["sweet","sour"] 16 | # 17 | # Example 2: 18 | # Input: A = "apple apple", B = "banana" 19 | # Output: ["banana"] 20 | 21 | # 22 | # Approach 1: Hash 23 | # 24 | # Time Complexitiy: O(n) 25 | 26 | def find_uncommon_words(strA, strB) 27 | array = strA.concat(' ', strB).split(' ') 28 | hash = Hash.new(0) 29 | result = [] 30 | 31 | array.each do |word| 32 | hash[word] += 1 33 | end 34 | 35 | hash.each do |k, v| 36 | result.push(k) if v < 2 37 | end 38 | 39 | result 40 | end 41 | 42 | puts find_uncommon_words('this apple is sweet', 'this apple is sour') 43 | # => ["sweet", "sour"] 44 | 45 | puts find_uncommon_words('apple apple', 'banana') 46 | # => ["banana"] 47 | -------------------------------------------------------------------------------- /data_structures/heaps/max_heap.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # This class represents an array-backed max-heap. 3 | 4 | class MaxHeap 5 | 6 | attr_reader :arr 7 | attr_accessor :heap_size 8 | 9 | ## 10 | # Creates a new max-heap using the provided collection of initial values, if provided (empty by default). 11 | # Note: a clone of the input collection is created to avoid alterations to the input. 12 | 13 | def initialize(elements = []) 14 | @arr = [0] + elements.map(&:clone) 15 | @heap_size = arr.size - 1 16 | for i in ((arr.size / 2).floor).downto 1 17 | max_heapify(i) 18 | end 19 | end 20 | 21 | def to_array 22 | return arr[1..heap_size].map(&:clone) 23 | end 24 | 25 | def empty? 26 | return heap_size == 0 27 | end 28 | 29 | def max 30 | return nil if empty? 31 | return @arr[1] 32 | end 33 | 34 | def extract_max 35 | return nil if empty? 36 | m = max 37 | @arr[1] = @arr[heap_size] 38 | @heap_size -= 1 39 | max_heapify(1) 40 | return m 41 | end 42 | 43 | def insert(k) 44 | @heap_size += 1 45 | @arr[heap_size] = -Float::INFINITY 46 | increase_to(heap_size, k) 47 | end 48 | 49 | private 50 | def max_heapify(i) 51 | l = left(i) 52 | r = right(i) 53 | m = i 54 | if l <= heap_size && arr[l] > arr[i] 55 | m = l 56 | end 57 | if r <= heap_size && arr[r] > arr[m] 58 | m = r 59 | end 60 | if m != i 61 | arr[i], arr[m] = arr[m], arr[i] 62 | max_heapify(m) 63 | end 64 | end 65 | 66 | def increase_to(i, k) 67 | raise ArgumentError.new('MaxHeap#increase_to does not support lower values for the key') if arr[i] > k 68 | @arr[i] = k 69 | j = i 70 | while parent(j) > 0 && arr[parent(j)] < arr[j] 71 | arr[j], arr[parent(j)] = arr[parent(j)], arr[j] 72 | j = parent(j) 73 | end 74 | end 75 | 76 | def parent(i) 77 | return (i / 2).floor 78 | end 79 | 80 | def left(i) 81 | return 2*i 82 | end 83 | 84 | def right(i) 85 | return 2*i + 1 86 | end 87 | end -------------------------------------------------------------------------------- /data_structures/heaps/max_heap_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require_relative 'max_heap' 3 | 4 | class TestMaxHeap < Minitest::Test 5 | def test_to_array_returns_array_representation 6 | heap = MaxHeap.new([4, 1, 3, 3, 16, 9, 10, 14, 8, 7]) 7 | assert heap.to_array == [16, 14, 10, 8, 7, 9, 3, 3, 4, 1] 8 | end 9 | 10 | def test_empty_returns_true_for_empty_heap 11 | heap = MaxHeap.new 12 | assert heap.empty? 13 | end 14 | 15 | def test_empty_returns_false_for_non_empty_heap 16 | heap = MaxHeap.new([1]) 17 | assert !heap.empty? 18 | end 19 | 20 | def test_max_returns_maximum_heap_element 21 | heap = MaxHeap.new([4, 1, 3]) 22 | assert heap.max == 4 23 | end 24 | 25 | def test_max_returns_nil_if_empty_heap 26 | heap = MaxHeap.new 27 | assert heap.max.nil? 28 | end 29 | 30 | def test_extract_max_returns_and_removes_maximum_heap_element 31 | heap = MaxHeap.new([4, 1, 3]) 32 | assert heap.extract_max == 4 33 | assert heap.to_array == [3, 1] 34 | end 35 | 36 | def test_extract_max_returns_nil_if_empty_heap 37 | heap = MaxHeap.new 38 | assert heap.extract_max.nil? 39 | end 40 | 41 | def test_insert_adds_element_to_appropriate_position 42 | heap = MaxHeap.new([4, 1, 3]) 43 | heap.insert(2) 44 | assert heap.to_array == [4, 2, 3, 1] 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /data_structures/linked_lists/circular_linked_list.rb: -------------------------------------------------------------------------------- 1 | # Define a node for the list 2 | class Node 3 | attr_accessor :value, :next 4 | 5 | def initialize(value) 6 | @value = value 7 | @next = nil 8 | end 9 | end 10 | 11 | # Class for circular linked list (last node points to the head node) 12 | class CircularList 13 | attr_reader :head 14 | 15 | def initialize 16 | @head = nil 17 | end 18 | 19 | def insert_tail(value) 20 | newNode = Node.new(value) 21 | if @head.nil? 22 | @head = newNode 23 | @head.next = @head 24 | else 25 | tempNode = @head 26 | tempNode = tempNode.next while tempNode.next != @head 27 | newNode.next = @head 28 | tempNode.next = newNode 29 | end 30 | end 31 | 32 | def insert_head(value) 33 | newNode = Node.new(value) 34 | if @head.nil? 35 | @head = newNode 36 | @head.next = head 37 | else 38 | tempNode = @head 39 | tempNode = tempNode.next while tempNode.next != @head 40 | newNode.next = @head 41 | tempNode.next = newNode 42 | @head = newNode 43 | end 44 | end 45 | 46 | def print_list 47 | print '[' 48 | unless @head.nil? 49 | printNode = @head 50 | while printNode.next != @head 51 | print printNode.value.to_s 52 | print ', ' 53 | printNode = printNode.next 54 | end 55 | print printNode.value 56 | end 57 | print ']' 58 | 59 | puts(STDOUT.flush) 60 | end 61 | 62 | def delete_head 63 | return if @head.nil? 64 | 65 | if @head.next != @head 66 | newHead = @head.next 67 | tempNode = newHead 68 | tempNode = tempNode.next while tempNode.next != @head 69 | tempNode.next = newHead 70 | @head = newHead 71 | elsif !@head.nil? && (@head.next == @head) 72 | @head = nil 73 | end 74 | end 75 | 76 | def delete_tail 77 | return if @head.nil? 78 | 79 | if @head.next != @head 80 | tempNode = @head 81 | tempNode = tempNode.next while tempNode.next.next != @head 82 | tempNode.next = @head 83 | elsif !@head.nil? && (@head.next == @head) 84 | @head = nil 85 | end 86 | end 87 | 88 | def is_empty 89 | @head.nil? 90 | end 91 | end 92 | 93 | obj = CircularList.new 94 | 95 | obj.insert_tail(1) 96 | obj.insert_tail(2) 97 | obj.insert_tail(3) 98 | obj.insert_tail(4) 99 | obj.insert_tail(5) 100 | obj.print_list 101 | 102 | obj.insert_head(6) 103 | obj.print_list 104 | 105 | obj.delete_tail 106 | obj.print_list 107 | 108 | obj.delete_head 109 | obj.print_list 110 | -------------------------------------------------------------------------------- /data_structures/linked_lists/singly_linked_list.rb: -------------------------------------------------------------------------------- 1 | # Define a node in the list 2 | class Node 3 | # Initialize the data structure here. 4 | attr_accessor :value, :next 5 | 6 | def initialize(value) 7 | @value = value 8 | @next = nil 9 | end 10 | end 11 | 12 | # A Class for single linked lists (each element links to the next one, but not to the previous one) 13 | class SinglyLinkedList 14 | include Enumerable 15 | attr_accessor :head 16 | 17 | def initialize 18 | @head = nil 19 | end 20 | 21 | def insert_tail(value) 22 | newNode = Node.new(value) 23 | if @head.nil? 24 | @head = newNode 25 | else 26 | tempNode = @head 27 | tempNode = tempNode.next until tempNode.next.nil? 28 | tempNode.next = newNode 29 | end 30 | end 31 | 32 | def insert_head(value) 33 | newNode = Node.new(value) 34 | if @head.nil? 35 | @head = newNode 36 | else 37 | newNode.next = @head 38 | @head = newNode 39 | end 40 | end 41 | 42 | def each 43 | return if @head.nil? 44 | 45 | current = @head 46 | until current.nil? 47 | yield current.value 48 | current = current.next 49 | end 50 | end 51 | 52 | def print_list 53 | puts '[' + to_a.join(', ') + ']' 54 | end 55 | 56 | def delete_head 57 | if !@head.nil? && !@head.next.nil? 58 | newHead = @head.next 59 | @head = newHead 60 | elsif !@head.nil? && @head.next.nil? 61 | @head = nil 62 | end 63 | end 64 | 65 | def delete_tail 66 | return if @head.nil? 67 | 68 | tempNode = @head 69 | tempNode = tempNode.next until tempNode.next.next.nil? 70 | tempNode.next = nil 71 | end 72 | 73 | def empty? 74 | @head.nil? 75 | end 76 | end 77 | 78 | obj = SinglyLinkedList.new 79 | 80 | obj.insert_head(1) 81 | obj.insert_head(2) 82 | obj.insert_head(3) 83 | obj.insert_head(4) 84 | obj.insert_head(5) 85 | obj.print_list 86 | 87 | obj.insert_tail(6) 88 | obj.print_list 89 | 90 | obj.delete_head 91 | obj.print_list 92 | 93 | obj.delete_tail 94 | obj.print_list 95 | -------------------------------------------------------------------------------- /data_structures/queues/circular_queue.rb: -------------------------------------------------------------------------------- 1 | # Challenge name: Circular Queue 2 | # 3 | # Design the implementation of a circular queue. 4 | # The circular queue is a linear data structure in which the operations are performed based on FIFO (First In First Out) principle and 5 | # the last position is connected back to the first position to make a circle. It is also called "Ring Buffer". 6 | # 7 | 8 | # 9 | # Complexity Analysis 10 | # 11 | # Time complexity: O(1). 12 | # All of the methods in our circular data structure are of constant time complexity. 13 | # 14 | # Space Complexity: O(N). 15 | # The overall space complexity of the data structure is linear, where N is the pre-assigned capacity of the queue. 16 | # However, it is worth mentioning that the memory consumption of the data structure remains as its pre-assigned capacity during its entire life cycle. 17 | 18 | class CircularQueue 19 | def initialize(max_size) 20 | @max_size = max_size 21 | @queue = Array.new(max_size, nil) 22 | @front = 0 23 | @back = 0 24 | @size = 0 25 | end 26 | 27 | attr_accessor :front, :back, :size 28 | attr_reader :max_size, :queue 29 | 30 | def empty? 31 | size == 0 32 | end 33 | 34 | def peek 35 | return nil if empty? 36 | 37 | queue[front] 38 | end 39 | 40 | def add(x) 41 | raise 'Queue is at max capacity' if size == max_size 42 | 43 | queue[back] = x 44 | @back = (back + 1) % max_size 45 | @size += 1 46 | end 47 | 48 | def pop 49 | raise 'Queue is empty' if size == 0 50 | 51 | temp = queue[front] 52 | queue[front] = nil 53 | @front = (front + 1) % max_size 54 | @size -= 1 55 | 56 | temp 57 | end 58 | end 59 | 60 | queue = CircularQueue.new(3) 61 | 62 | begin 63 | queue.pop 64 | rescue StandardError => e 65 | puts e.message 66 | end 67 | 68 | queue.add(1) 69 | queue.add(2) 70 | queue.add(3) 71 | 72 | begin 73 | queue.add(4) 74 | rescue StandardError => e 75 | puts e.message 76 | end 77 | 78 | puts queue.inspect 79 | # => # 80 | 81 | puts queue.peek 82 | # => 1 83 | 84 | queue.pop 85 | 86 | puts queue.peek 87 | # => 2 88 | -------------------------------------------------------------------------------- /data_structures/stacks/stack.rb: -------------------------------------------------------------------------------- 1 | # A stack is an abstract data type that serves as a collection of 2 | # elements with two principal operations: push() and pop(). push() adds an 3 | # element to the top of the stack, and pop() removes an element from the top 4 | # of a stack. The order in which elements come off of a stack are 5 | # Last In, First Out (LIFO) 6 | 7 | class StackOverflowError < StandardError; end 8 | 9 | class Stack 10 | def initialize(limit, stack = []) 11 | @stack = stack 12 | @limit = limit 13 | end 14 | 15 | attr_accessor :stack, :limit 16 | 17 | def push(item) 18 | raise StackOverflowError unless stack.count < limit 19 | 20 | stack << item 21 | end 22 | 23 | def pop 24 | stack.pop 25 | end 26 | 27 | def peek 28 | stack.last 29 | end 30 | 31 | def empty? 32 | stack.count.zero? 33 | end 34 | 35 | def full? 36 | stack.count == limit 37 | end 38 | 39 | def size 40 | stack.count 41 | end 42 | 43 | def contains?(item) 44 | stack.include?(item) 45 | end 46 | end 47 | 48 | stack = Stack.new(10, []) 49 | 50 | puts stack.empty? 51 | # => true 52 | 53 | stack.push(3) 54 | stack.push(5) 55 | stack.push(7) 56 | stack.push(9) 57 | 58 | puts stack.full? 59 | # => false 60 | 61 | puts stack.contains?(5) 62 | # => true 63 | 64 | puts stack.pop 65 | # => 9 66 | 67 | puts stack.peek 68 | # => 7 69 | 70 | puts stack.size 71 | # => 3 72 | 73 | puts stack.inspect 74 | # => # 75 | 76 | stack.push(13) 77 | stack.push(15) 78 | stack.push(17) 79 | stack.push(19) 80 | stack.push(23) 81 | stack.push(25) 82 | stack.push(27) 83 | # At this point, the stack is full 84 | 85 | stack.push(29) 86 | # => data_structures/stacks/stack.rb:18:in `push': StackOverflowError (StackOverflowError) 87 | # from data_structures/stacks/stack.rb:83:in `
' 88 | -------------------------------------------------------------------------------- /data_structures/tries/trie.rb: -------------------------------------------------------------------------------- 1 | # A Trie (prefix tree) is a kind of search tree used to provide quick lookup 2 | # of words/patterns in a set of words. A basic Trie however has O(n^2) 3 | # space complexity making it impractical in practice. 4 | # It however provides O(max(search_string, length of longest word)) 5 | # lookup time making it an optimal approach when space is not an issue. 6 | 7 | class Node 8 | attr_reader :value, :next 9 | attr_accessor :word 10 | 11 | def initialize(value) 12 | @value = value 13 | @word = false 14 | @next = [] 15 | end 16 | end 17 | 18 | class Trie 19 | def initialize 20 | @root = Node.new('*') 21 | end 22 | 23 | def insert_many(word) 24 | letters = word.chars 25 | base = @root 26 | 27 | letters.each do |letter| 28 | base = insert(letter, base.next) 29 | end 30 | end 31 | 32 | def include?(word) 33 | letters = word.chars 34 | base = @root 35 | 36 | letters.all? do |letter| 37 | base = find(letter, base.next) 38 | end 39 | end 40 | 41 | private 42 | 43 | # check if it already exists 44 | # if not add character to node 45 | def insert(character, trie) 46 | found = trie.find do |n| 47 | n.value == character 48 | end 49 | 50 | add_node(character, trie) unless found 51 | end 52 | 53 | def add_node(character, trie) 54 | Node.new(character).tap do |new_node| 55 | trie << new_node 56 | end 57 | end 58 | 59 | def find(character, trie) 60 | trie.find do |n| 61 | n.value == character 62 | end 63 | end 64 | end 65 | 66 | trie = Trie.new 67 | trie.insert_many('Dogs') 68 | trie.insert_many('South') 69 | trie.insert_many('Cape Town') 70 | 71 | puts trie.include?('Cape Town') 72 | # => true 73 | 74 | puts trie.include?('not presented') 75 | # => false 76 | -------------------------------------------------------------------------------- /discrete_mathematics/euclidean_gcd.rb: -------------------------------------------------------------------------------- 1 | # https://en.wikipedia.org/wiki/Euclidean_algorithm 2 | 3 | def euclidean_gcd(a, b) 4 | while b != 0 5 | t = b 6 | b = a % b 7 | a = t 8 | end 9 | a 10 | end 11 | 12 | puts 'GCD(3, 5) = ' + euclidean_gcd(3, 5).to_s 13 | puts 'GCD(3, 6) = ' + euclidean_gcd(3, 6).to_s 14 | puts 'GCD(6, 3) = ' + euclidean_gcd(6, 3).to_s 15 | -------------------------------------------------------------------------------- /discrete_mathematics/exteded_euclidean_algorithm.rb: -------------------------------------------------------------------------------- 1 | # https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm 2 | 3 | def extended_euclidean_gcd(a, b) 4 | x0 = a 5 | x1 = b 6 | s = 1 7 | t = 0 8 | until x1.zero? 9 | q, x2 = x0.divmod(x1) 10 | x0 = x1 11 | x1 = x2 12 | s, t = t, s - q * t 13 | end 14 | x0 15 | end 16 | 17 | puts 'GCD(3, 5) = ' + extended_euclidean_gcd(3, 5).to_s 18 | # GCD(3, 5) = 1 19 | puts 'GCD(3, 6) = ' + extended_euclidean_gcd(3, 6).to_s 20 | # GCD(3, 6) = 3 21 | puts 'GCD(6, 3) = ' + extended_euclidean_gcd(6, 3).to_s 22 | # GCD(6, 3) = 3 23 | 24 | # 25 | # Dynamic driver code: 26 | # 27 | # a = gets.to_i 28 | # b = gets.to_i 29 | # puts "GCD (#{a}, #{b} ) = #{extended_euclidean_gcd(a, b)}" 30 | # 31 | -------------------------------------------------------------------------------- /discrete_mathematics/lcm.rb: -------------------------------------------------------------------------------- 1 | # LCM (Least Common Multiple) of two numbers is the smallest number which can be divided by both numbers. 2 | 3 | p 'Least Common Multiple' 4 | 5 | p 'Enter first number' 6 | value_one = gets.chomp.to_i 7 | 8 | p 'Enter second number' 9 | value_two = gets.chomp.to_i 10 | 11 | def gcd(first, second) 12 | if second != 0 13 | gcd(second, first % second) 14 | else 15 | first 16 | end 17 | end 18 | 19 | def lcm(first, second) 20 | (first * second) / gcd(first, second) 21 | end 22 | 23 | p "Least Common Multiple is: #{lcm(value_one, value_two)}" 24 | -------------------------------------------------------------------------------- /dynamic_programming/climbing_stairs.rb: -------------------------------------------------------------------------------- 1 | # You are climbing a staircase. It takes n steps to reach the top. 2 | # Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top? 3 | 4 | # Example 1: 5 | # Input: n = 2 6 | # Output: 2 7 | # Explanation: There are two ways to climb to the top. 8 | # 1. 1 step + 1 step 9 | # 2. 2 steps 10 | 11 | # Example 2: 12 | # Input: n = 3 13 | # Output: 3 14 | # Explanation: There are three ways to climb to the top. 15 | # 1. 1 step + 1 step + 1 step 16 | # 2. 1 step + 2 steps 17 | # 3. 2 steps + 1 step 18 | 19 | # Constraints: 20 | # 1 <= n <= 45 21 | 22 | # Dynamic Programming, Recursive Bottom Up Approach - O(n) Time / O(n) Space 23 | # Init memoization hash (only 1 parameter) 24 | # Set base cases which are memo[0] = 1 and memo[1] = 1, since there are only 1 way to get to each stair 25 | # Iterate from 2..n and call recurse(n, memo) for each value n. 26 | # Return memo[n]. 27 | 28 | # recurse(n, memo) - Recurrence Relation is n = (n - 1) + (n - 2) 29 | # return memo[n] if memo[n] exists. 30 | # otherwise, memo[n] = recurse(n - 1, memo) + recurse(n - 2, memo) 31 | 32 | # @param {Integer} n 33 | # @return {Integer} 34 | def climb_stairs(n) 35 | memo = {} 36 | 37 | memo[0] = 1 38 | memo[1] = 1 39 | 40 | return memo[n] if [0, 1].include?(n) 41 | 42 | (2..n).each do |n| 43 | recurse(n, memo) 44 | end 45 | 46 | memo[n] 47 | end 48 | 49 | def recurse(n, memo) 50 | return memo[n] if memo[n] 51 | 52 | memo[n] = recurse(n - 1, memo) + recurse(n - 2, memo) 53 | end 54 | 55 | puts climb_stairs(2) 56 | # => 2 57 | 58 | puts climb_stairs(4) 59 | # => 5 60 | 61 | puts climb_stairs(10) 62 | # => 89 63 | 64 | puts climb_stairs(45) 65 | # => 1836311903 66 | -------------------------------------------------------------------------------- /dynamic_programming/coin_change.rb: -------------------------------------------------------------------------------- 1 | def coin_change_minimum(coins, amount) 2 | dp = Array.new(amount + 1, -1) 3 | dp[0] = 0 4 | 5 | coins.each do |coin| 6 | (coin..amount).each do |i| 7 | if dp[i - coin] != -1 8 | dp[i] = -1 == dp[i] ? dp[i - coin] + 1 : [dp[i], dp[i - coin] + 1].min 9 | end 10 | end 11 | end 12 | 13 | dp[amount] 14 | end 15 | 16 | def coin_change_combinations(coins, amount) 17 | dp = Array.new(coins.length + 1) { Array.new(amount + 1, 0) } 18 | dp[0][0] = 1 19 | (1..coins.length).each do |i| 20 | (0..amount).each do |j| 21 | dp[i][j] = dp[i - 1][j] + (j < coins[i - 1] ? 0 : dp[i][j - coins[i - 1]]) 22 | end 23 | end 24 | dp[coins.length][amount] 25 | end 26 | 27 | coins = Array[2, 4, 5] 28 | amount = 12 29 | puts 'Number of combinations of getting change for ' + amount.to_s + ' is ' + coin_change_combinations(coins, 30 | amount).to_s + '.' 31 | puts 'Minimum number of coins required for ' + amount.to_s + ' is ' + coin_change_minimum(coins, amount).to_s + '.' 32 | -------------------------------------------------------------------------------- /dynamic_programming/editdistance.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | 3 | def editDistDP(str1, str2, m, n) 4 | rows, cols = m+1,n+1 5 | 6 | # Create a 2D array to store results of subproblems 7 | dp = Array.new(rows) { Array.new(cols) } 8 | 9 | #using bottom up approach 10 | for i in (0..m + 1-1) do 11 | for j in (0..n + 1-1) do 12 | 13 | #If the first string is empty, insert all the characters of the second string 14 | if i == 0 15 | dp[i][j] = j 16 | 17 | #If the second string is empty, insert all the characters of the first string 18 | elsif j == 0 19 | dp[i][j] = i 20 | 21 | #If the last character in both the strings are same, we can ignore the character and move to the next character in both the strings 22 | elsif str1[i-1] == str2[j-1] 23 | dp[i][j] = dp[i-1][j-1] 24 | 25 | #If the last character of both the strings are different, find out the minimum value of the three operations(insert, delete, replace) 26 | else 27 | dp[i][j] = 1 +[dp[i][j-1],dp[i-1][j],dp[i-1][j-1]].min() 28 | 29 | end 30 | 31 | end 32 | 33 | end 34 | 35 | return dp[m][n] 36 | end 37 | 38 | 39 | 40 | 41 | class Editdistancetest < Test::Unit::TestCase 42 | 43 | #Test1 44 | #Replace 'n' with 'r' 45 | #insert 'a' 46 | #insert 't' 47 | #No of total operations : 3 48 | def test_distance1 49 | assert_equal 3, editDistDP( "sunday","saturday",6,8), "Should return 3" 50 | end 51 | 52 | #Test2 53 | #Replace 'a' with 'u' 54 | #No of total operations : 1 55 | def test_distance2 56 | assert_equal 1, editDistDP("cat","cut",3,3), "editDistDpShould return 1" 57 | end 58 | 59 | #Test3 60 | #Insert 'a','p', 'p','l','e','p','i','e' into string 1 61 | #No of total operations : 8 62 | def test_distance3 63 | assert_equal 8, editDistDP("","applepie",0,8), "editDistDpShould return 1" 64 | end 65 | 66 | #Test4 67 | #Both the strings are equal, thus no operation needed 68 | #No of total operations : 0 69 | def test_distance4 70 | assert_equal 0, editDistDP("Hello","Hello",5,5), "editDistDpShould return 1" 71 | end 72 | 73 | end 74 | 75 | -------------------------------------------------------------------------------- /dynamic_programming/fibonacci.rb: -------------------------------------------------------------------------------- 1 | # The Fibonacci numbers, commonly denoted F(n) form a sequence, 2 | # called the Fibonacci sequence, such that # each number is the sum 3 | # of the two preceding ones, starting from 0 and 1. That is, 4 | # 5 | # F(0) = 0, F(1) = 1 6 | # F(n) = F(n - 1) + F(n - 2), for n > 1 7 | # 8 | # Given n, calculate F(n). 9 | 10 | # 11 | # Approach: Top-Down Approach using Memoization 12 | # 13 | 14 | # Complexity Analysis: 15 | # 16 | # Time complexity: O(n). Each number, starting at 2 up to and 17 | # including N, is visited, computed and then stored for O(1) access 18 | # later on. 19 | # 20 | # Space complexity: O(n). The size of the stack in memory is 21 | # proportionate to N. 22 | # 23 | def fibonacci(number, memo_hash = {}) 24 | return number if number <= 1 25 | 26 | memo_hash[0] = 0 27 | memo_hash[1] = 1 28 | 29 | memoize(number, memo_hash) 30 | end 31 | 32 | def memoize(number, memo_hash) 33 | return memo_hash[number] if memo_hash.key? number 34 | 35 | memo_hash[number] = memoize(number - 1, memo_hash) + memoize(number - 2, memo_hash) 36 | 37 | memoize(number, memo_hash) 38 | end 39 | 40 | n = 2 41 | fibonacci(n) 42 | # Output: 1 43 | # Explanation: F(2) = F(1) + F(0) = 1 + 0 = 1. 44 | 45 | n = 3 46 | fibonacci(n) 47 | # Output: 2 48 | # Explanation: F(3) = F(2) + F(1) = 1 + 1 = 2. 49 | 50 | n = 4 51 | fibonacci(n) 52 | # Output: 3 53 | # Explanation: F(4) = F(3) + F(2) = 2 + 1 = 3. 54 | -------------------------------------------------------------------------------- /dynamic_programming/house_robber.rb: -------------------------------------------------------------------------------- 1 | # You are a professional robber planning to rob houses along a street. 2 | # Each house has a certain amount of money stashed, the only constraint stopping you 3 | # from robbing each of them is that adjacent houses have security systems connected 4 | # and it will automatically contact the police if two adjacent houses 5 | # were broken into on the same night. 6 | # 7 | # Given an integer array nums representing the amount of money of each house, 8 | # return the maximum amount of money you can rob tonight without alerting the police. 9 | # 10 | # Example 1: 11 | # 12 | # Input: nums = [1,2,3,1] 13 | # Output: 4 14 | # Explanation: Rob house 1 (money = 1) and then rob house 3 (money = 3). 15 | # Total amount you can rob = 1 + 3 = 4. 16 | # 17 | # Example 2: 18 | # 19 | # Input: nums = [2,7,9,3,1] 20 | # Output: 12 21 | # Explanation: Rob house 1 (money = 2), rob house 3 (money = 9) and rob house 5 (money = 1). 22 | # Total amount you can rob = 2 + 9 + 1 = 12. 23 | 24 | # 25 | # Approach 1: Dynamic Programming 26 | # 27 | 28 | # Complexity Analysis 29 | # 30 | # Time Complexity: O(N) since we process at most N recursive calls, thanks to 31 | # caching, and during each of these calls, we make an O(1) computation which is 32 | # simply making two other recursive calls, finding their maximum, and populating 33 | # the cache based on that. 34 | # 35 | # Space Complexity: O(N) which is occupied by the cache and also by the recursion stack 36 | 37 | def rob(nums, i = nums.length - 1) 38 | return 0 if i < 0 39 | 40 | [rob(nums, i - 2) + nums[i], rob(nums, i - 1)].max 41 | end 42 | 43 | nums = [1, 2, 3, 1] 44 | puts rob(nums) 45 | # Output: 4 46 | 47 | nums = [2, 7, 9, 3, 1] 48 | puts rob(nums) 49 | # Output: 12 50 | 51 | # 52 | # Approach 2: Optimized Dynamic Programming 53 | # 54 | 55 | # Time Complexity 56 | # 57 | # Time Complexity: O(N) since we have a loop from N−2 and we use the precalculated 58 | # values of our dynamic programming table to calculate the current value in the table 59 | # which is a constant time operation. 60 | # 61 | # Space Complexity: O(1) since we are not using a table to store our values. 62 | # Simply using two variables will suffice for our calculations. 63 | # 64 | 65 | def rob(nums) 66 | dp = Array.new(nums.size + 1) 67 | 68 | (nums.size + 1).times do |i| 69 | dp[i] = if i == 0 70 | 0 71 | elsif i == 1 72 | nums[0] 73 | else 74 | [dp[i - 2] + nums[i - 1], dp[i - 1]].max 75 | end 76 | end 77 | 78 | dp[-1] 79 | end 80 | 81 | nums = [1, 2, 3, 1] 82 | puts rob(nums) 83 | # Output: 4 84 | 85 | nums = [2, 7, 9, 3, 1] 86 | puts rob(nums) 87 | # Output: 12 88 | -------------------------------------------------------------------------------- /dynamic_programming/knapsack.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | 3 | # 0-1 Knapsack problem 4 | # The function returns the maximum value that can be put in a knapsack of a given capacity 5 | 6 | def knapSack(weight, wt, val, n) 7 | 8 | rows, cols = n+1,weight+1 9 | # Create a 2D array to store results of subproblems 10 | dp = Array.new(rows) { Array.new(cols) } 11 | 12 | for i in (0..n + 1-1) 13 | for w in (0..weight + 1-1) 14 | # if the weight is 0 or value is zero, the corresponding cell in the 2D array is set to 0 15 | if i == 0 || w == 0 16 | dp[i][w] = 0 17 | 18 | #If the weight of an element is less than the capacity of the bag, the maximum value of the two cases is taken(Either the element is taken into consideration 19 | #or is ignored) 20 | elsif wt[i-1] <= w 21 | dp[i][w] = [ val[i-1] + dp[i-1][w-wt[i-1]],dp[i-1][w]].max() 22 | 23 | #If the weight of the element is greater than the capacity of the bag, the cell is set to the value of the previous cell 24 | else 25 | dp[i][w] = dp[i-1][w] 26 | end 27 | end 28 | end 29 | 30 | return dp[n][weight] 31 | end 32 | 33 | 34 | 35 | 36 | 37 | class Knapsacktest < Test::Unit::TestCase 38 | 39 | #Test1 40 | def test_knapsack1 41 | assert_equal 220, knapSack(50,[10,20,30],[60,100,120],3), "Should return 220" 42 | end 43 | 44 | 45 | #Test2 46 | def test_knapsack2 47 | assert_equal 500, knapSack(50,[50, 20, 30],[100, 200, 300],3), "Should return 500" 48 | end 49 | 50 | #Test3 51 | def test_knapsack3 52 | assert_equal 17, knapSack(10,[3,4,5, 2, 1],[10,2,3,4,0],5), "Should return 17" 53 | end 54 | 55 | #Test4 56 | def test_knapsack4 57 | assert_equal 0, knapSack(0,[23, 17, 12, 8, 20],[199,200,30,41,10],5), "Should return 0" 58 | end 59 | 60 | 61 | end 62 | 63 | 64 | -------------------------------------------------------------------------------- /dynamic_programming/ones_and_zeros.rb: -------------------------------------------------------------------------------- 1 | # You are given an array of binary strings strs and two integers m and n. 2 | # 3 | # Return the size of the largest subset of strs such that there are at most m 0's and n 1's in the subset. 4 | # 5 | # A set x is a subset of a set y if all elements of x are also elements of y. 6 | # 7 | # Example 1: 8 | # 9 | # Input: strs = ["10","0001","111001","1","0"], m = 5, n = 3 10 | # Output: 4 11 | # Explanation: The largest subset with at most 5 0's and 3 1's is {"10", "0001", "1", "0"}, so the answer is 4. 12 | # Other valid but smaller subsets include {"0001", "1"} and {"10", "1", "0"}. 13 | # {"111001"} is an invalid subset because it contains 4 1's, greater than the maximum of 3. 14 | # 15 | # Example 2: 16 | # 17 | # Input: strs = ["10","0","1"], m = 1, n = 1 18 | # Output: 2 19 | # Explanation: The largest subset is {"0", "1"}, so the answer is 2. 20 | 21 | # 22 | # Approach #1 Dynamic Programming 23 | # 24 | 25 | # @param {String[]} strs 26 | # @param {Integer} m 27 | # @param {Integer} n 28 | # @return {Integer} 29 | def find_max_form(strs, m, n) 30 | dp = (m + 1).times.map { [0] * (n + 1) } 31 | 32 | strs.each do |str| 33 | zeros = str.count('0') 34 | ones = str.count('1') 35 | 36 | m.downto(zeros) do |i| 37 | n.downto(ones) do |j| 38 | dp[i][j] = [dp[i][j], dp[i - zeros][j - ones] + 1].max 39 | end 40 | end 41 | end 42 | 43 | dp[m][n] 44 | end 45 | 46 | strs = %w[10 0001 111001 1 0] 47 | m = 5 48 | n = 3 49 | puts find_max_form(strs, m, n) 50 | # Output: 4 51 | 52 | strs = %w[10 0 1] 53 | m = 1 54 | n = 1 55 | puts find_max_form(strs, m, n) 56 | # Output: 2 57 | -------------------------------------------------------------------------------- /electronics/ohms_law.rb: -------------------------------------------------------------------------------- 1 | # A ruby program for Ohms Law, which is used to calculate Voltage for the given Resistance and Current. 2 | # Ohms Law -> V = I * R 3 | # Reference: https://en.wikipedia.org/wiki/Ohm's_law 4 | 5 | def ohms_law(i, r) 6 | if i > 0 && r > 0 7 | "The voltage for given #{i} ampheres current and #{r} ohms resistance is #{r * i} volts." 8 | else 9 | raise 10 | end 11 | rescue StandardError 12 | 'Error: Please provide valid inputs only!' 13 | end 14 | 15 | # Valid inputs 16 | puts(ohms_law(5, 10)) 17 | # The voltage for given 5 ampheres current and 10 ohms resistance is 50 volts. 18 | puts(ohms_law(2.5, 6.9)) 19 | # The voltage for given 2.5 ampheres current and 6.9 ohms resistance is 17.25 volts. 20 | puts(ohms_law(0.15, 0.84)) 21 | # The voltage for given 0.15 ampheres current and 0.84 ohms resistance is 0.126 volts. 22 | 23 | # Invalid inputs 24 | puts(ohms_law(5, -10)) 25 | # Error: Please provide valid inputs only! 26 | puts(ohms_law(-5, -10)) 27 | # Error: Please provide valid inputs only! 28 | puts(ohms_law(5, '10')) 29 | # Error: Please provide valid inputs only! 30 | puts(ohms_law('a', 10)) 31 | # Error: Please provide valid inputs only! 32 | -------------------------------------------------------------------------------- /maths/3nPlus1.rb: -------------------------------------------------------------------------------- 1 | # A ruby program for 3N plus 1 2 | # 3N plus 1 is also called as Collatz Conjecture 3 | # 4 | # f(n) => n / 2 (if n = 0 (mod 2)) 5 | # f(n) => (3n + 1) (if n = 1 (mod 2)) 6 | # 7 | # Wiki: https://en.wikipedia.org/wiki/Collatz_conjecture 8 | 9 | def collatz_conjecture(number) 10 | n = number 11 | nums = [] 12 | nums.push(number) 13 | 14 | while number > 1 15 | if number.even? 16 | number /= 2 17 | nums.push(number) 18 | else 19 | number = 3 * number + 1 20 | nums.push(number) 21 | end 22 | end 23 | 24 | "The 3N + 1 series of #{n} is #{nums}." 25 | rescue StandardError 26 | 'Error: Please provide number only!' 27 | end 28 | 29 | # Valid inputs 30 | puts collatz_conjecture(12) 31 | # The 3N + 1 series of 12 is [12, 6, 3, 10, 5, 16, 8, 4, 2, 1]. 32 | 33 | puts collatz_conjecture(6) 34 | # The 3N + 1 series of 6 is [6, 3, 10, 5, 16, 8, 4, 2, 1]. 35 | 36 | puts collatz_conjecture(100) 37 | # The 3N + 1 series of 100 is [100, 50, 25, 76, 38, 19, 58, 29, 88, 44, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1]. 38 | 39 | # Invalid inputs 40 | puts collatz_conjecture('12') 41 | # Error: Please provide number only! 42 | 43 | puts collatz_conjecture('a') 44 | # Error: Please provide number only! 45 | -------------------------------------------------------------------------------- /maths/abs.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Calculates the absolute value of a number 4 | class Abs 5 | def self.call(number) 6 | return -number if number.negative? 7 | 8 | number 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /maths/abs_max.rb: -------------------------------------------------------------------------------- 1 | # A ruby program to find absolute maximum 2 | # Mathematical representation of abs max = ((a + b + absoulte(a - b)) / 2) 3 | 4 | def abs_max(x, y) 5 | num = x - y 6 | max_value = ((x + y + num.abs) / 2) 7 | "The Abs Max of #{x} and #{y} is #{max_value}." 8 | rescue StandardError 9 | 'Error: Provide number only!' 10 | end 11 | 12 | # Valid inputs 13 | puts abs_max(10, 20) 14 | # The Abs Max of 10 and 20 is 20. 15 | 16 | puts abs_max(-10, -1) 17 | # The Abs Max of -10 and -1 is -1. 18 | 19 | puts abs_max(9, -121) 20 | # The Abs Max of 9 and -121 is 9. 21 | 22 | # Invalid inputs 23 | puts abs_max(2, '-1') 24 | # Error: Provide number only! 25 | 26 | puts abs_max('3', '5') 27 | # Error: Provide number only! 28 | 29 | puts abs_max('a', '5') 30 | # Error: Provide number only! 31 | -------------------------------------------------------------------------------- /maths/abs_min.rb: -------------------------------------------------------------------------------- 1 | # A ruby program to find absolute minimum 2 | # Mathematical representation of abs min = ((a + b - absoulte(a - b)) / 2) 3 | 4 | def abs_min(x, y) 5 | num = x - y 6 | min_value = ((x + y - num.abs) / 2) 7 | "The Abs Min of #{x} and #{y} is #{min_value}." 8 | rescue StandardError 9 | 'Error: Provide number only!' 10 | end 11 | 12 | # 13 | # Valid inputs 14 | # 15 | puts abs_min(10, 20) 16 | # The Abs Min of 10 and 20 is 10. 17 | 18 | puts abs_min(-10, -1) 19 | # The Abs Min of -10 and -1 is -10. 20 | 21 | puts abs_min(9, -121) 22 | # The Abs Min of 9 and -121 is -121. 23 | 24 | # 25 | # Invalid inputs 26 | # 27 | puts abs_min(2, '-1') 28 | # Error: Provide number only! 29 | 30 | puts abs_min('3', '5') 31 | # Error: Provide number only! 32 | 33 | puts abs_min('a', '5') 34 | # Error: Provide number only! 35 | -------------------------------------------------------------------------------- /maths/abs_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'minitest/autorun' 4 | require_relative './abs' 5 | 6 | class AbsTest < Minitest::Test 7 | def test_positive_number 8 | assert_equal Abs.call(4), 4 9 | end 10 | 11 | def test_zero 12 | assert_equal Abs.call(0), 0 13 | end 14 | 15 | def test_negative_number 16 | assert_equal Abs.call(-9), 9 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /maths/add.rb: -------------------------------------------------------------------------------- 1 | # A ruby program to add numbers 2 | # Addition or sum of numbers means adding each and every element of the inputs 3 | # Sum or addition of 1 and 3 is 1 + 3 = 4 4 | 5 | def add(*array) 6 | sum = 0 7 | array.each { |a| sum += a } 8 | puts "The sum of following elements #{array} is #{sum}" 9 | rescue StandardError 10 | puts 'Error: Please provide number only!' 11 | end 12 | 13 | # 14 | # Valid inputs 15 | # 16 | 17 | puts add(1) 18 | # The sum of following elements [1] is 1 19 | 20 | puts add(2, 5, -4) 21 | # The sum of following elements [2, 5, -4] is 3 22 | 23 | puts add(25, 45) 24 | # The sum of following elements [25, 45] is 70 25 | 26 | # 27 | # Invalid inputs 28 | # 29 | 30 | puts add('1', 2, 3) 31 | # Error: Please provide number only! 32 | 33 | puts add('a', 1) 34 | # Error: Please provide number only! 35 | -------------------------------------------------------------------------------- /maths/add_digits.rb: -------------------------------------------------------------------------------- 1 | # Challenge name: Add Digits 2 | # 3 | # Given a non-negative integer num, repeatedly add all its digits until the result has only one digit. 4 | # 5 | # Example: 6 | # 7 | # Input: 38 8 | # Output: 2 9 | # Explanation: 3 + 8 = 11, 1 + 1 = 2. 10 | 11 | # 12 | # Approach: Mathematical: Digital Root 13 | # 14 | # https://en.wikipedia.org/wiki/Digital_root 15 | # https://brilliant.org/wiki/digital-root/ 16 | # 17 | 18 | # 19 | # Complexity Analysis 20 | # 21 | # Time Complexity: O(1). 22 | # Space Complexity: O(1). 23 | 24 | def add_digits(num) 25 | return 0 if num == 0 26 | return 9 if num % 9 == 0 27 | 28 | num % 9 29 | end 30 | 31 | puts(add_digits(38)) 32 | # # => 2 33 | 34 | puts(add_digits(284)) 35 | # # => 5 36 | -------------------------------------------------------------------------------- /maths/aliquot_sum.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative './square_root' 4 | require_relative './ceil' 5 | 6 | # Calculates the aliquot sum of a number (the sum of all proper divisors of a number) 7 | class AliquotSum 8 | class << self 9 | def call(number) 10 | divisors(number).sum 11 | end 12 | 13 | private 14 | 15 | def divisors(number) 16 | low_divisors = (1..Ceil.call(SquareRoot.call(number))).select { |num| (number % num).zero? } 17 | high_divisors = low_divisors.map { |div| number / div } 18 | (low_divisors + high_divisors).uniq - [number] 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /maths/aliquot_sum_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'minitest/autorun' 4 | require_relative './aliquot_sum' 5 | 6 | class AliquotSumTest < Minitest::Test 7 | def test_zero 8 | assert_equal AliquotSum.call(0), 0 9 | end 10 | 11 | def test_one 12 | assert_equal Abs.call(1), 1 13 | end 14 | 15 | def test_many 16 | (2..100).each do |number| 17 | expected_aliquot_sum = (1...number).select { |num| (number % num).zero? }.sum 18 | assert_equal AliquotSum.call(number), expected_aliquot_sum 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /maths/armstrong_number.rb: -------------------------------------------------------------------------------- 1 | # A ruby program to determine whether a given number is an Armstrong number 2 | # Wiki url: https://en.wikipedia.org/wiki/Narcissistic_number 3 | # other resources: https://pages.mtu.edu/~shene/COURSES/cs201/NOTES/chap04/arms.html 4 | 5 | # 6 | # Examples: -> 153 = (1 * 1 * 1) + (5 * 5 * 5) + (3 * 3 * 3) [Armstrong number] and -> 125 != (1 * 1 * 1) + (2 * 2 * 2) + (5 * 5 * 5) 7 | # -> 1634 = (1 * 1 * 1 * 1) + (6 * 6 * 6 * 6) + (3 * 3 * 3 * 3) + (4 * 4 * 4 * 4) 8 | # 9 | 10 | def armstrong_number(number) 11 | num = number 12 | sum = 0 13 | len = number.digits.count 14 | while number != 0 15 | remainder = number % 10 16 | sum += remainder**len 17 | number /= 10 18 | end 19 | 20 | if num == sum 21 | "The number #{num} is an Armstrong number." 22 | else 23 | "The number #{num} is not an Armstrong number." 24 | end 25 | rescue StandardError 26 | 'Error: Please provide number only!' 27 | end 28 | 29 | # 30 | # Valid inputs 31 | # 32 | puts armstrong_number(153) 33 | # The number 153 is an Armstrong number. 34 | 35 | puts armstrong_number(0) 36 | # The number 0 is an Armstrong number. 37 | 38 | puts armstrong_number(370) 39 | # The number 370 is an Armstrong number. 40 | 41 | puts armstrong_number(10) 42 | # The number 10 is not an Armstrong number. 43 | 44 | puts armstrong_number(1634) 45 | # The number 1634 is an Armstrong number. 46 | 47 | puts armstrong_number(123) 48 | # The number 123 is not an Armstrong number. 49 | 50 | # 51 | # Invalid inputs 52 | # 53 | puts armstrong_number('153') 54 | # Error: Please provide number only! 55 | 56 | puts armstrong_number('a') 57 | # Error: Please provide number only! 58 | -------------------------------------------------------------------------------- /maths/average_mean.rb: -------------------------------------------------------------------------------- 1 | # A ruby program for finding average mean 2 | 3 | module AverageMean 4 | # Average mean = sum of elements / total number of elements 5 | def self.average_mean(n, *array) 6 | if n.instance_of? Integer 7 | if n == array.size 8 | puts "The average mean of the following elements #{array} is #{array.sum / array.size}." 9 | else 10 | puts "Provide the required #{n} elements properly!" 11 | end 12 | else 13 | raise 14 | end 15 | rescue StandardError 16 | puts 'Error: Please provide number only!' 17 | end 18 | end 19 | 20 | # Valid inputs 21 | AverageMean.average_mean(2, 3, 1) 22 | AverageMean.average_mean(5, 1, 2, 3, 4, 5) 23 | AverageMean.average_mean(3, 2, 2, 2) 24 | 25 | # Invalid inputs 26 | AverageMean.average_mean(2, 3, 1, 5) 27 | AverageMean.average_mean(2, 3, 'a') 28 | AverageMean.average_mean('a', 1, 2) 29 | -------------------------------------------------------------------------------- /maths/average_median.rb: -------------------------------------------------------------------------------- 1 | # A ruby program to find average median 2 | # Reference: https://dev.to/colerau/how-to-find-the-median-and-mean-of-an-array-in-ruby-4f04 3 | 4 | module AverageMedian 5 | def self.average_median(n, *array) 6 | if n.instance_of? Integer 7 | if n == array.size 8 | array.sort 9 | if array.size.even? 10 | mid_element_1 = array.size / 2 11 | mid_element_2 = mid_element_1 + 1 12 | puts "The average median of the following elements #{array} is #{(array[mid_element_1 - 1] + array[mid_element_2 - 1]) / 2}." 13 | else 14 | mid_element = (array.size + 1) / 2 15 | puts "The average median of the following elements #{array} is #{array[mid_element - 1]}." 16 | end 17 | else 18 | puts "Provide the required #{n} elements properly!" 19 | end 20 | else 21 | raise 22 | end 23 | rescue StandardError 24 | puts 'Error: Please provide number only!' 25 | end 26 | end 27 | 28 | # 29 | # Valid inputs 30 | # 31 | 32 | puts AverageMedian.average_median(2, 3, 1) 33 | # The average median of the following elements [3, 1] is 2. 34 | 35 | puts AverageMedian.average_median(5, 1, 2, 3, 4, 5) 36 | # The average median of the following elements [1, 2, 3, 4, 5] is 3. 37 | 38 | puts AverageMedian.average_median(3, 2, 2, 2) 39 | # The average median of the following elements [2, 2, 2] is 2. 40 | 41 | puts AverageMedian.average_median(1, 5) 42 | # The average median of the following elements [5] is 5. 43 | 44 | # 45 | # Invalid inputs 46 | # 47 | 48 | puts AverageMedian.average_median(2, 3, 1, 5) 49 | # Provide the required 2 elements properly! 50 | 51 | puts AverageMedian.average_median(2, 3, 'a') 52 | # Traceback (most recent call last): 53 | # 4: from /Users/voliveira/.rvm/rubies/ruby-2.7.0/bin/irb:23:in `
' 54 | # 3: from /Users/voliveira/.rvm/rubies/ruby-2.7.0/bin/irb:23:in `load' 55 | # 2: from /Users/voliveira/.rvm/rubies/ruby-2.7.0/lib/ruby/gems/2.7.0/gems/irb-1.2.1/exe/irb:11:in `' 56 | # 1: from (irb):30 57 | # NameError (undefined local variable or method `verageMedian' for main:Object) 58 | 59 | puts AverageMedian.average_median('a', 1, 2) 60 | # Error: Please provide number only! 61 | -------------------------------------------------------------------------------- /maths/binary_to_decimal.rb: -------------------------------------------------------------------------------- 1 | # 2 | # For any binary number of n digits i.e dn-1 ... d3 d2 d1 d0 3 | # The equivalent decimal number is equal to the sum of binary digits (dn) times their power of 2 (2n): 4 | # decimal = d0×2^0 + d1×2^1 + d2×2^2 + ... 5 | # 6 | 7 | def binary_to_decimal(n) 8 | decimal = 0 9 | base = 1 10 | until n.zero? 11 | x = n % 10 12 | n /= 10 13 | decimal += x * base 14 | base *= 2 15 | end 16 | decimal 17 | end 18 | 19 | puts 'Decimal value of 110011 is ' + binary_to_decimal(110_011).to_s 20 | # Decimal value of 110011 is 51 21 | puts 'Decimal value of 11110 is ' + binary_to_decimal(11_110).to_s 22 | # Decimal value of 11110 is 30 23 | puts 'Decimal value of 101 is ' + binary_to_decimal(101).to_s 24 | # Decimal value of 101 is 5 25 | -------------------------------------------------------------------------------- /maths/ceil.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Ceil 4 | class << self 5 | def call(number) 6 | return number if number.is_a?(Integer) || number == number.to_i 7 | 8 | number.to_i + 1 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /maths/ceil_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'minitest/autorun' 4 | require_relative './ceil' 5 | 6 | class CeilTest < Minitest::Test 7 | def test_integer 8 | assert_equal Ceil.call(4), 4 9 | end 10 | 11 | def test_float_at_integer 12 | assert_equal Ceil.call(4.0), 4 13 | end 14 | 15 | def test_float_not_at_integer 16 | assert_equal Ceil.call(4.01), 5 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /maths/count_sorted_vowel_strings.rb: -------------------------------------------------------------------------------- 1 | # Challenge name: Count Sorted Vowel Strings 2 | # 3 | # Given an integer n, return the number of strings of length n that consist only 4 | # of vowels (a, e, i, o, u) and are lexicographically sorted. 5 | # 6 | # A string s is lexicographically sorted if for all valid i, s[i] is the same as 7 | # or comes before s[i+1] in the alphabet. 8 | # 9 | # 10 | # Example 1: 11 | # 12 | # Input: n = 1 13 | # Output: 5 14 | # Explanation: The 5 sorted strings that consist of vowels only are ["a","e","i","o","u"]. 15 | # 16 | # Example 2: 17 | # 18 | # Input: n = 2 19 | # Output: 15 20 | # Explanation: The 15 sorted strings that consist of vowels only are 21 | # ["aa","ae","ai","ao","au","ee","ei","eo","eu","ii","io","iu","oo","ou","uu"]. 22 | # Note that "ea" is not a valid string since 'e' comes after 'a' in the alphabet. 23 | # 24 | # Example 3: 25 | # 26 | # Input: n = 33 27 | # Output: 66045 28 | 29 | # 30 | # Approach: Math 31 | # 32 | 33 | # 34 | # Intuition and Algorithm 35 | # 36 | # The problem is a variant of finding Combinations. 37 | # Mathematically, the problem can be described as, 38 | # given 5 vowels (let k = 5), we want to find the 39 | # number of combinations using only n vowels. Also, 40 | # we can repeat each of those vowels multiple times. 41 | # 42 | 43 | # Complexity Analysis 44 | # 45 | # Time Complexity: O(1), as the approach runs in constant time. 46 | # Space Complexity: O(1), as the approach uses constant extra space. 47 | 48 | # @param {Integer} n 49 | # @return {Integer} 50 | def count_vowel_strings(n) 51 | (n + 4) * (n + 3) * (n + 2) * (n + 1) / 24 52 | end 53 | 54 | n = 33 55 | puts count_vowel_strings(n) 56 | # Output: 66045 57 | 58 | n = 2 59 | puts count_vowel_strings(n) 60 | # Output: 15 61 | 62 | n = 1 63 | puts count_vowel_strings(n) 64 | # Output: 5 65 | -------------------------------------------------------------------------------- /maths/decimal_to_binary.rb: -------------------------------------------------------------------------------- 1 | # Convert a given decimal number into binary. 2 | 3 | # 4 | # Approach 1: Iterative 5 | # 6 | 7 | def decimal_to_binary(n) 8 | bin = [] 9 | 10 | until n.zero? 11 | bin << n % 2 12 | n /= 2 13 | end 14 | 15 | bin.reverse.join 16 | end 17 | 18 | puts 'Binary value of 4 is ' + decimal_to_binary(4).to_s 19 | # Binary value of 4 is 100 20 | 21 | puts 'Binary value of 31 is ' + decimal_to_binary(31).to_s 22 | # Binary value of 31 is 11111 23 | 24 | puts 'Binary value of 64 is ' + decimal_to_binary(64).to_s 25 | # Binary value of 64 is 1000000 26 | 27 | # 28 | # Approach 2: Recursive 29 | # 30 | 31 | def decimal_to_binary(d) 32 | binary = (d % 2).to_s 33 | 34 | return binary if d == 0 35 | return 1.to_s if d == 1 36 | 37 | decimal_to_binary(d / 2).to_s + binary 38 | end 39 | 40 | puts 'Binary value of 4 is ' + decimal_to_binary(4).to_s 41 | # Binary value of 4 is 100 42 | 43 | puts 'Binary value of 31 is ' + decimal_to_binary(31).to_s 44 | # Binary value of 31 is 11111 45 | 46 | puts 'Binary value of 64 is ' + decimal_to_binary(64).to_s 47 | # Binary value of 64 is 1000000 48 | -------------------------------------------------------------------------------- /maths/factorial.rb: -------------------------------------------------------------------------------- 1 | # A ruby program calculate factorial of a given number. 2 | # Mathematical Explanation: The factorial of a number is the product of all the integers from 1 to that number. 3 | # i.e: n! = n*(n-1)*(n-2)......*2*1 4 | 5 | # 6 | # Approach: Interative 7 | # 8 | 9 | def factorial(n) 10 | return nil if n < 0 11 | 12 | fac = 1 13 | 14 | while n > 0 15 | fac *= n 16 | n -= 1 17 | end 18 | 19 | fac 20 | end 21 | 22 | puts '4! = ' + factorial(4).to_s 23 | # 4! = 24 24 | 25 | puts '0! = ' + factorial(0).to_s 26 | # 0! = 1 27 | 28 | puts '10! = ' + factorial(10).to_s 29 | # 10! = 3628800 30 | -------------------------------------------------------------------------------- /maths/factorial_non_recursive_non_iterative.rb: -------------------------------------------------------------------------------- 1 | # A ruby program to find factorial of a given integer 2 | # Factorial of a given integer is defined as the product of all the positive integers less than or equal to the given integer 3 | # Mathematical representation: n! = n * (n - 1) * (n - 2) * ... * 1 4 | 5 | # 6 | # Non-recursive and non-iterative approach 7 | # 8 | 9 | def factorial(number) 10 | if number < 0 11 | 'Please check your input number! The given number is a negative number.' 12 | elsif number == 0 13 | "The factorial of #{number} is 1." 14 | else 15 | result = (1..number).inject(:*) 16 | "The factorial of #{number} is #{result}." 17 | end 18 | rescue StandardError 19 | 'Error: Please provide integer only!' 20 | end 21 | 22 | # Valid inputs 23 | puts factorial(0) 24 | # The factorial of 0 is 1. 25 | 26 | puts factorial(4) 27 | # The factorial of 4 is 24. 28 | 29 | puts factorial(10) 30 | # The factorial of 10 is 3628800. 31 | 32 | puts factorial(1) 33 | # The factorial of 1 is 1. 34 | 35 | puts factorial(-5) 36 | # Please check your input number! The given number is a negative number. 37 | 38 | # Invalid inputs 39 | puts factorial('a') 40 | # Error: Please provide integer only! 41 | 42 | puts factorial('2') 43 | # Error: Please provide integer only! 44 | -------------------------------------------------------------------------------- /maths/fibonacci.rb: -------------------------------------------------------------------------------- 1 | # The Fibonacci numbers, commonly denoted F(n) form a sequence, 2 | # called the Fibonacci sequence, such that each number is the sum 3 | # of the two preceding ones, starting from 0 and 1. That is, 4 | # 5 | # F(0) = 0, F(1) = 1 6 | # F(n) = F(n - 1) + F(n - 2), for n > 1. 7 | # 8 | # Given n, calculate F(n). 9 | 10 | # 11 | # Approach: Math 12 | # 13 | 14 | # Intuition: Using the golden ratio, a.k.a Binet's formula 15 | 16 | # Algorithm: Use the golden ratio formula to calculate the Nth Fibonacci number. 17 | # https://demonstrations.wolfram.com/GeneralizedFibonacciSequenceAndTheGoldenRatio/ 18 | 19 | # Complexity Analysis 20 | 21 | # Time complexity: O(1). Constant time complexity since we are using no loops or recursion 22 | # and the time is based on the result of performing the calculation using Binet's formula. 23 | # 24 | # Space complexity: O(1). The space used is the space needed to create the variable 25 | # to store the golden ratio formula. 26 | 27 | def fibonacci(n) 28 | golden_ratio = (1 + 5**0.5) / 2 29 | ((golden_ratio**n + 1) / 5**0.5).to_i 30 | end 31 | 32 | n = 2 33 | puts(fibonacci(n)) 34 | # Output: 1 35 | # Explanation: F(2) = F(1) + F(0) = 1 + 0 = 1. 36 | 37 | n = 3 38 | puts(fibonacci(n)) 39 | # Output: 2 40 | # Explanation: F(3) = F(2) + F(1) = 1 + 1 = 2. 41 | 42 | n = 4 43 | puts(fibonacci(n)) 44 | # Output: 3 45 | # Explanation: F(4) = F(3) + F(2) = 2 + 1 = 3. 46 | -------------------------------------------------------------------------------- /maths/find_max.rb: -------------------------------------------------------------------------------- 1 | # A ruby program to find max from a set of elements 2 | 3 | # This find_max method will return the max element out of the array 4 | def find_max(*array) 5 | max = array[0] 6 | array.each do |a| 7 | max = a if a >= max 8 | end 9 | "The Max of the following elements #{array} is #{max}." 10 | rescue StandardError 11 | 'Error: Please provide number only!' 12 | end 13 | 14 | # Max method will return the maximum element from the set of input elements provided 15 | def predefined_max(*array) 16 | "The Max of the following elements #{array} is #{array.max}." 17 | rescue StandardError 18 | 'Error: Please provide number only!' 19 | end 20 | 21 | # Sort method will sort the elements in ascending order. Last method will return the end element out of the array 22 | def predefined_sort_last_max(*array) 23 | "The Max of the following elements #{array} is #{array.max}." 24 | rescue StandardError 25 | 'Error: Please provide number only!' 26 | end 27 | 28 | # Using find_max 29 | # Valid inputs 30 | puts find_max(11, 29, 33) 31 | # The Max of the following elements [11, 29, 33] is 33. 32 | 33 | puts find_max(-221, -852, -1100, -10) 34 | # The Max of the following elements [-221, -852, -1100, -10] is -10. 35 | 36 | # Invalid inputs 37 | puts find_max(5, '95', 2) 38 | # Error: Please provide number only! 39 | 40 | # Using predefined_max 41 | # Valid inputs 42 | puts predefined_max(51, 82, 39) 43 | # The Max of the following elements [51, 82, 39] is 82. 44 | 45 | puts predefined_max(-11, -51, -10, -10) 46 | # The Max of the following elements [-11, -51, -10, -10] is -10. 47 | 48 | # Invalid inputs 49 | puts predefined_max('x', 5, 95, 2) 50 | # Error: Please provide number only! 51 | 52 | # Using predefined_sort_last_max 53 | # Valid inputs 54 | puts predefined_sort_last_max(1, 2, 3) 55 | # The Max of the following elements [1, 2, 3] is 3. 56 | 57 | puts predefined_sort_last_max(-21, -52, -100, -1) 58 | # The Max of the following elements [-21, -52, -100, -1] is -1. 59 | 60 | # Invalid inputs 61 | puts predefined_sort_last_max(5, 95, 2, 'a') 62 | # Error: Please provide number only! 63 | -------------------------------------------------------------------------------- /maths/find_min.rb: -------------------------------------------------------------------------------- 1 | # A ruby program to find min from a set of elements 2 | 3 | # This find_min method will return the min element out of the array 4 | def find_min(*array) 5 | min = array[0] 6 | array.each do |a| 7 | min = a if a <= min 8 | end 9 | "The Min of the following elements #{array} is #{min}." 10 | rescue StandardError 11 | 'Error: Please provide number only!' 12 | end 13 | 14 | # Min method will return the minimum element from the set of input elements provided 15 | def predefined_min(*array) 16 | "The Min of the following elements #{array} is #{array.min}." 17 | rescue StandardError 18 | 'Error: Please provide number only!' 19 | end 20 | 21 | # Sort method will sort the elements in ascending order. First method will return the beginning element out of the array 22 | def predefined_sort_first_min(*array) 23 | "The Min of the following elements #{array} is #{array.min}." 24 | rescue StandardError 25 | 'Error: Please provide number only!' 26 | end 27 | 28 | # Using find_min 29 | # Valid inputs 30 | puts find_min(11, 29, 33) 31 | # The Min of the following elements [11, 29, 33] is 33. 32 | 33 | puts find_min(-221, -852, -1100, -10) 34 | # The Min of the following elements [-221, -852, -1100, -10] is -10. 35 | 36 | # Invalid inputs 37 | puts find_min(5, '95', 2) 38 | # Error: Please provide number only! 39 | 40 | # Using predefined_min 41 | # Valid inputs 42 | puts predefined_min(51, 82, 39) 43 | # The Min of the following elements [51, 82, 39] is 82. 44 | 45 | puts predefined_min(-11, -51, -10, -10) 46 | # The Min of the following elements [-11, -51, -10, -10] is -10. 47 | 48 | # Invalid inputs 49 | puts predefined_min('x', 5, 95, 2) 50 | # Error: Please provide number only! 51 | 52 | # Using predefined_sort_first_min 53 | # Valid inputs 54 | puts predefined_sort_first_min(1, 2, 3) 55 | # The Min of the following elements [1, 2, 3] is 3. 56 | 57 | puts predefined_sort_first_min(-21, -52, -100, -1) 58 | # The Min of the following elements [-21, -52, -100, -1] is -1. 59 | 60 | # Invalid inputs 61 | puts predefined_sort_first_min(5, 95, 2, 'a') 62 | # Error: Please provide number only! 63 | -------------------------------------------------------------------------------- /maths/lucas_series.rb: -------------------------------------------------------------------------------- 1 | # A ruby program for Lucas series 2 | # 3 | # The Lucas numbers, commonly denoted L(n) form a sequence, 4 | # called the Lucas series, such that each number is the sum 5 | # of the two preceding ones, starting from 2 and 1. That is, 6 | # 7 | # L(0) = 2, L(1) = 1 8 | # L(n) = L(n - 1) + L(n - 2), for n > 1. 9 | # 10 | # Given n, calculate L(n). 11 | # Example: 2 1 3 4 7 11 18... 12 | # Resource: https://en.wikipedia.org/wiki/Lucas_number 13 | 14 | def lucas(number) 15 | golden_ratio = (1 + 5**0.5) / 2 16 | (golden_ratio**number).round.to_i 17 | rescue StandardError 18 | 'Error: Provide number only!' 19 | end 20 | 21 | puts lucas(4) 22 | # 7 23 | 24 | puts lucas(3) 25 | # 4 26 | 27 | puts lucas('3') 28 | # Error: Provide number only! 29 | 30 | puts lucas(2) 31 | # 3 32 | -------------------------------------------------------------------------------- /maths/number_of_digits.rb: -------------------------------------------------------------------------------- 1 | # Given a number, find number of digits in it. 2 | 3 | def count_digits(n) 4 | count = 0 5 | temp = n 6 | 7 | return 1 if n == 0 8 | 9 | until temp.zero? 10 | count += 1 11 | temp /= 10 12 | end 13 | 14 | count 15 | end 16 | 17 | puts 'Number of digits in 8732 is ' + count_digits(8732).to_s 18 | # Number of digits in 8732 is 4 19 | puts 'Number of digits in 112233 is ' + count_digits(112_233).to_s 20 | # Number of digits in 112233 is 6 21 | puts 'Number of digits in 0 is ' + count_digits(0).to_s 22 | # Number of digits in 0 is 1 23 | -------------------------------------------------------------------------------- /maths/pascal_triangle_ii.rb: -------------------------------------------------------------------------------- 1 | # Given an integer row_index, return the rowIndexth (0-indexed) row of the Pascal's triangle. 2 | 3 | # Example 1: 4 | # 5 | # Input: row_index = 3 6 | # Output: [1,3,3,1] 7 | # 8 | # Example 2: 9 | # 10 | # Input: row_index = 0 11 | # Output: [1] 12 | # 13 | # Example 3: 14 | # 15 | # Input: row_index = 1 16 | # Output: [1,1] 17 | 18 | # Complexity Analysis 19 | # 20 | # Time complexity: O(k). 21 | # Space complexity: O(k). 22 | 23 | def get_row(row_index) 24 | (0..row_index).map { |num| combination(row_index, num) } 25 | end 26 | 27 | def combination(num1, num2) 28 | factorial(num1) / (factorial(num2) * factorial(num1 - num2)) 29 | end 30 | 31 | def factorial(num) 32 | (1..num).inject(1) { |res, i| res * i } 33 | end 34 | 35 | row_index = 3 36 | print(get_row(row_index)) 37 | # => [1,3,3,1] 38 | 39 | row_index = 0 40 | print(get_row(row_index)) 41 | # => [1] 42 | 43 | row_index = 1 44 | print(get_row(row_index)) 45 | # => [1,1] 46 | -------------------------------------------------------------------------------- /maths/power_of_two.rb: -------------------------------------------------------------------------------- 1 | # Power of 2 2 | # 3 | # Given an integer n, return true if it is a power of two. Otherwise, return false. 4 | # 5 | # An integer n is a power of two, if there exists an integer x such that n == 2^x. 6 | # 7 | # Example 1: 8 | # Input: n = 1 9 | # Output: true 10 | # Explanation: 2^0 = 1 11 | # 12 | # Example 2: 13 | # Input: n = 16 14 | # Output: true 15 | # Explanation: 2^4 = 16 16 | # 17 | # Example 3: 18 | # Input: n = 3 19 | # Output: false 20 | # 21 | # Example 4: 22 | # Input: n = 4 23 | # Output: true 24 | # 25 | # Example 5: 26 | # Input: n = 5 27 | # Output: false 28 | # 29 | # Constraints: -231 <= n <= 231 - 1 30 | # @param {Integer} n 31 | # @return {Boolean} 32 | # 33 | 34 | # Approach 1: Recursion 35 | # 36 | # Time Complexity: O(logn) 37 | # 38 | def is_power_of_two(n) 39 | if n == 1 40 | true 41 | elsif n.even? 42 | is_power_of_two(n / 2) 43 | else 44 | false 45 | end 46 | end 47 | 48 | n = 1 49 | # Output: true 50 | puts is_power_of_two(n) 51 | n = 16 52 | # Output: true 53 | puts is_power_of_two(n) 54 | n = 3 55 | # Output: false 56 | puts is_power_of_two(n) 57 | n = 4 58 | # Output: true 59 | puts is_power_of_two(n) 60 | n = 5 61 | # Output: false 62 | puts is_power_of_two(n) 63 | 64 | # 65 | # Approach 2: Without recursion 66 | # 67 | # Time Complexity: O(n) 68 | # 69 | def is_power_of_two(n) 70 | n /= 2 while n.even? && n != 0 71 | n == 1 72 | end 73 | 74 | n = 1 75 | # Output: true 76 | puts is_power_of_two(n) 77 | n = 16 78 | # Output: true 79 | puts is_power_of_two(n) 80 | n = 3 81 | # Output: false 82 | puts is_power_of_two(n) 83 | n = 4 84 | # Output: true 85 | puts is_power_of_two(n) 86 | n = 5 87 | # Output: false 88 | puts is_power_of_two(n) 89 | 90 | # 91 | # Approach 3: Using Math library 92 | # 93 | # Time Complexity: O(1) 94 | # 95 | def is_power_of_two(n) 96 | result_exponent = Math.log(n) / Math.log(2) 97 | result_exponent % 1 == 0 98 | end 99 | 100 | n = 1 101 | # Output: true 102 | puts is_power_of_two(n) 103 | n = 16 104 | # Output: true 105 | puts is_power_of_two(n) 106 | n = 3 107 | # Output: false 108 | puts is_power_of_two(n) 109 | n = 4 110 | # Output: true 111 | puts is_power_of_two(n) 112 | n = 5 113 | # Output: false 114 | puts is_power_of_two(n) 115 | -------------------------------------------------------------------------------- /maths/prime_number.rb: -------------------------------------------------------------------------------- 1 | # A ruby program to check a given number is prime or not 2 | # Mathematical explanation: A number which has only 2 factors i.e., 1 (one) and itself 3 | 4 | # Prime number check function 5 | def prime_number(number) 6 | non_prime_flag = if number <= 1 7 | true 8 | elsif number == 2 9 | false 10 | elsif number.even? 11 | true 12 | else 13 | (2..Math.sqrt(number)).any? { |i| number % i == 0 } 14 | end 15 | 16 | if !non_prime_flag 17 | puts "The given number #{number} is a Prime." 18 | else 19 | puts "The given number #{number} is not a Prime." 20 | end 21 | end 22 | 23 | # Non-prime input 24 | prime_number(1) 25 | 26 | # prime input 27 | # Number 2 is an even prime number 28 | prime_number(2) 29 | 30 | # Non-prime input 31 | prime_number(20) 32 | 33 | # Negative input 34 | prime_number(-21) 35 | -------------------------------------------------------------------------------- /maths/square_root.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Calculates the square root of a number 4 | class SquareRoot 5 | class << self 6 | EPSILON = 1E-10 7 | 8 | def call(number) 9 | raise DomainError, 'Cannot find square root of negative number' if number.negative? 10 | return 0 if number.zero? 11 | 12 | find_root(number) 13 | end 14 | 15 | private 16 | 17 | def find_root(x0, xn = x0) 18 | xn1 = xn - ((xn * xn - x0) / (2.0 * xn)) 19 | return xn1 if (xn1 - xn).abs <= EPSILON 20 | 21 | find_root(x0, xn1) 22 | end 23 | end 24 | end 25 | 26 | class DomainError < StandardError; end 27 | -------------------------------------------------------------------------------- /maths/square_root_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'minitest/autorun' 4 | require_relative './square_root' 5 | 6 | class SquareRootTest < Minitest::Test 7 | def test_negative_number 8 | assert_raises DomainError do 9 | SquareRoot.call(-1) 10 | end 11 | end 12 | 13 | def test_zero 14 | assert_equal 0, SquareRoot.call(0) 15 | end 16 | 17 | def test_all_numbers_below_1024 18 | (1...1024).each do |num| 19 | assert_in_delta SquareRoot.call(num), Math.sqrt(num), 1E-12 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /maths/sum_of_digits.rb: -------------------------------------------------------------------------------- 1 | # Given a number, find sum of its digits. 2 | 3 | def digits_sum(n) 4 | a = 0 5 | sum = 0 6 | until n.zero? 7 | a = n % 10 8 | sum += a 9 | n /= 10 10 | end 11 | sum 12 | end 13 | 14 | puts 'Sum of digits of 3456 is ' + digits_sum(3456).to_s 15 | # Sum of digits of 3456 is 18 16 | puts 'Sum of digits of 1234 is ' + digits_sum(1234).to_s 17 | # Sum of digits of 1234 is 10 18 | puts 'Sum of digits of 9251321 is ' + digits_sum(9_251_321).to_s 19 | # Sum of digits of 9251321 is 23 20 | -------------------------------------------------------------------------------- /other/fisher_yates.rb: -------------------------------------------------------------------------------- 1 | # Fisher and Yates Shuffle is one of the simplest and most popular shuffling algorithm 2 | def fisher_yates_shuffle(array) 3 | n = array.length 4 | while n > 0 5 | i = rand(n -= 1) 6 | array[i], array[n] = array[n], array[i] 7 | end 8 | array 9 | end 10 | 11 | arr = [1, 2, 40, 30, 20, 15, 323, 12, 3, 4] 12 | puts fisher_yates_shuffle(arr) 13 | -------------------------------------------------------------------------------- /other/number_of_days.rb: -------------------------------------------------------------------------------- 1 | # Challenge name: Number of Days Between Two Dates 2 | # 3 | # Write a program to count the number of days between two dates. 4 | # 5 | # The two dates are given as strings, their format is YYYY-MM-DD as shown in the examples. 6 | # Example 1: 7 | # Input: date1 = "2019-06-29", date2 = "2019-06-30" 8 | # Output: 1 9 | # 10 | # Example 2: 11 | # Input: date1 = "2020-01-15", date2 = "2019-12-31" 12 | # Output: 15 13 | # 14 | # Constraints: The given dates are valid dates between the years 1971 and 2100. 15 | 16 | # 17 | # Approach 1: Using Ruby built-in feature Date.parse 18 | # Time complexity: O(1) 19 | # Space complexity: O(1) 20 | # 21 | 22 | require 'date' 23 | 24 | def number_of_days(date1, date2) 25 | beginning_date = Date.parse(date1) 26 | end_date = Date.parse(date2) 27 | (end_date - beginning_date).to_i.abs 28 | end 29 | 30 | puts number_of_days('2019-06-29', '2019-06-30') 31 | # => 1 32 | 33 | puts number_of_days('2020-01-15', '2019-12-31') 34 | # => 15 35 | -------------------------------------------------------------------------------- /project_euler/README.md: -------------------------------------------------------------------------------- 1 | # Project Euler 2 | 3 | Problems are taken from https://projecteuler.net/, the Project Euler. [Problems are licensed under CC BY-NC-SA 4.0](https://projecteuler.net/copyright). 4 | 5 | Project Euler is a series of challenging mathematical/computer programming problems that require more than just mathematical 6 | insights to solve. Project Euler is ideal for mathematicians who are learning to code. 7 | 8 | ## Solution Guidelines 9 | 10 | Welcome to [TheAlgorithms/Ruby](https://github.com/TheAlgorithms/Ruby)! Before reading the solution guidelines, make sure you read the whole [Contributing Guidelines](https://github.com/TheAlgorithms/Ruby/blob/master/CONTRIBUTING.md) as it won't be repeated in here. If you have any doubt on the guidelines, please feel free to [state it clearly in an issue](https://github.com/TheAlgorithms/Ruby/issues/new) or ask the community in [Gitter](https://gitter.im/TheAlgorithms). Be sure to read the [Coding Style](https://github.com/TheAlgorithms/Ruby/blob/master/project_euler/README.md#coding-style) before starting solution. 11 | 12 | ### Coding Style 13 | 14 | * Please maintain consistency in project directory and solution file names. Keep the following points in mind: 15 | * Create a new directory only for the problems which do not exist yet. 16 | * Please name the project **directory** as `problem_` where `problem_number` should be filled with 0s so as to occupy 3 digits. Example: `problem_001`, `problem_002`, `problem_067`, `problem_145`, and so on. 17 | 18 | * You can have as many helper functions as you want but there should be one main function called `solution` which should satisfy the conditions as stated below: 19 | * It should contain positional argument(s) whose default value is the question input. Example: Please take a look at [Problem 1](https://projecteuler.net/problem=1) where the question is to *Find the sum of all the multiples of 3 or 5 below 1000.* In this case the main solution function will be `solution(limit = 1000)`. 20 | * When the `solution` function is called without any arguments like so: `solution()`, it should return the answer to the problem. 21 | -------------------------------------------------------------------------------- /project_euler/problem_001/sol1.rb: -------------------------------------------------------------------------------- 1 | # If we list all the natural numbers below 10 that are multiples of 3 or 5, 2 | # we get 3, 5, 6 and 9. The sum of these multiples is 23. 3 | # Find the sum of all the multiples of 3 or 5 below 1000. 4 | 5 | def divisible_by_three_or_five?(number) 6 | (number % 3).zero? || (number % 5).zero? 7 | end 8 | 9 | sum = 0 10 | (1...1000).each do |i| 11 | sum += i if divisible_by_three_or_five?(i) 12 | end 13 | 14 | p sum 15 | -------------------------------------------------------------------------------- /project_euler/problem_002/sol1.rb: -------------------------------------------------------------------------------- 1 | # Each new term in the Fibonacci sequence is generated by adding the previous two terms. 2 | # By starting with 1 and 2, the first 10 terms will be: 3 | # 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ... 4 | # By considering the terms in the Fibonacci sequence whose values do not exceed four million, 5 | # find the sum of the even-valued terms. 6 | 7 | even_fib_sum = 0 8 | fib_first = 1 9 | fib_second = 2 10 | 11 | while fib_second < 4_000_000 12 | even_fib_sum += fib_second if fib_second.even? 13 | fib_second += fib_first 14 | fib_first = fib_second - fib_first 15 | end 16 | 17 | p even_fib_sum 18 | -------------------------------------------------------------------------------- /project_euler/problem_003/sol1.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # The prime factors of 13195 are 5, 7, 13 and 29. 4 | # What is the largest prime factor of the number 600851475143 5 | 6 | # find all factors of the given number 7 | def get_factors(number) 8 | factors = [] 9 | (1..Math.sqrt(number).to_i).each do |num| 10 | if (number % num).zero? 11 | factors << num 12 | factors << number / num 13 | end 14 | end 15 | factors 16 | end 17 | 18 | # determine if a given number is a prime number 19 | def prime?(number) 20 | get_factors(number).length == 2 21 | end 22 | 23 | # find the largest prime 24 | def largest_prime_factor(number) 25 | prime_factors = get_factors(number).select { |factor| prime?(factor) } 26 | prime_factors.max 27 | end 28 | 29 | puts largest_prime_factor(600_851_475_143) 30 | -------------------------------------------------------------------------------- /project_euler/problem_003/sol2.rb: -------------------------------------------------------------------------------- 1 | # The prime factors of 13195 are 5, 7, 13 and 29. 2 | # What is the largest prime factor of the number 600851475143 ? 3 | 4 | def solution(n) 5 | prime = 1 6 | i = 2 7 | while i * i <= n 8 | while (n % i).zero? 9 | prime = i 10 | n = n.fdiv i 11 | end 12 | i += 1 13 | end 14 | prime = n if n > 1 15 | prime.to_i 16 | end 17 | 18 | puts solution(600_851_475_143) 19 | -------------------------------------------------------------------------------- /project_euler/problem_004/sol1.rb: -------------------------------------------------------------------------------- 1 | # A palindromic number reads the same both ways. The largest palindrome made from the product of two 2-digit numbers is 9009 = 91 × 99. 2 | # Find the largest palindrome made from the product of two 3-digit numbers. 3 | 4 | answer = 0 5 | 999.downto(99) do |i| 6 | 999.downto(99) do |j| 7 | t = (i * j) 8 | answer = i * j if (t.to_s == t.to_s.reverse) && (t > answer) && (t > answer) 9 | end 10 | end 11 | puts answer 12 | -------------------------------------------------------------------------------- /project_euler/problem_004/sol2.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # A palindromic number reads the same both ways. The largest palindrome made from the product of two 2-digit numbers is 9009 = 91 × 99. 4 | # Find the largest palindrome made from the product of two 3-digit numbers. 5 | 6 | class Integer 7 | def parindrome? 8 | self == reverse 9 | end 10 | 11 | # 123.reverse == 321 12 | # 100.reverse == 1 13 | def reverse 14 | result = 0 15 | n = self 16 | loop do 17 | result = result * 10 + n % 10 18 | break if (n /= 10).zero? 19 | end 20 | result 21 | end 22 | end 23 | 24 | factors = (100..999).to_a 25 | products = factors.product(factors).map { _1 * _2 } 26 | puts products.select(&:parindrome?).max 27 | -------------------------------------------------------------------------------- /project_euler/problem_005/sol1.rb: -------------------------------------------------------------------------------- 1 | # 2520 is the smallest number that can be divided 2 | # by each of the numbers from 1 to 10 without any remainder. 3 | # What is the smallest positive number that is evenly 4 | # divisible by all of the numbers from 1 to 20? 5 | 6 | # Euclid's algorithm for the greatest common divisor 7 | def gcd(a, b) 8 | b.zero? ? a : gcd(b, a % b) 9 | end 10 | 11 | # Calculate the LCM using GCD 12 | def lcm(a, b) 13 | (a * b) / gcd(a, b) 14 | end 15 | 16 | result = 1 17 | 18 | 20.times do |i| 19 | result = lcm(result, i + 1) 20 | end 21 | 22 | p result 23 | -------------------------------------------------------------------------------- /project_euler/problem_006/sol1.rb: -------------------------------------------------------------------------------- 1 | #Project Euler Problem 6: #https://projecteuler.net/problem=6 2 | 3 | #Sum square difference 4 | 5 | #The sum of the squares of the first ten natural numbers #is, 6 | # 1^2 + 2^2 + ... + 10^2 = 385 7 | #The square of the sum of the first ten natural numbers #is, 8 | # (1 + 2 + ... + 10)^2 = 55^2 = 3025 9 | #Hence the difference between the sum of the squares of #the first ten 10 | #natural numbers and the square of the sum is 3025 - 385 = 2640. 11 | #Find the difference between the sum of the squares of the first one 12 | #hundred natural numbers and the square of the sum. 13 | 14 | def solution(num=10) 15 | x = 1 16 | y = 1 17 | result = 1 18 | gap = 3 19 | while y < num 20 | x += gap 21 | gap += 2 22 | y += 1 23 | result += x 24 | end 25 | r_n_pow2_plus_n_pow2 = result 26 | r_sum_n_pow2 = (((num / 2) + 0.5) * num) ** 2 27 | 28 | r_sum_n_pow2 - r_n_pow2_plus_n_pow2 29 | end 30 | 31 | answer = solution() 32 | p answer 33 | -------------------------------------------------------------------------------- /project_euler/problem_007/sol1.rb: -------------------------------------------------------------------------------- 1 | #Project Euler Problem 7: https://projecteuler.net/problem=7 2 | #10001st prime 3 | #By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13, we 4 | #can see that the 6th prime is 13. 5 | #What is the 10001st prime number? 6 | #References: https://en.wikipedia.org/wiki/Prime_number 7 | 8 | def is_prime?(number) 9 | value = true 10 | if number > 1 and number < 4 11 | # 2 and 3 are primes 12 | value = true 13 | elsif number < 2 or number % 2 == 0 or number % 3 == 0 14 | # Negatives, 0, 1, all even numbers, all multiples of 3 are not primes 15 | value = false 16 | end 17 | end_range = (Math.sqrt(number) + 1).to_i 18 | # All primes number are in format of 6k +/- 1 19 | for i in (5..end_range).step(6) 20 | if number % i == 0 or number % (i + 2) == 0 21 | value = false 22 | end 23 | end 24 | result = value 25 | end 26 | 27 | def solution(nth = 10001) 28 | primes = Array.new() 29 | num = 2 30 | while primes.length < nth 31 | if is_prime?(num) 32 | primes.append(num) 33 | end 34 | num += 1 35 | end 36 | primes[primes.length - 1] 37 | end 38 | 39 | answer = solution() 40 | p answer -------------------------------------------------------------------------------- /project_euler/problem_010/sol1.rb: -------------------------------------------------------------------------------- 1 | #Project Euler Problem 10: https://projecteuler.net/problem=10 2 | #Summation of primes 3 | #The sum of the primes below 10 is 2 + 3 + 5 + 7 = 17. 4 | #Find the sum of all the primes below two million. 5 | #References: https://en.wikipedia.org/wiki/Prime_number 6 | def is_prime?(number) 7 | value = true 8 | if number > 1 and number < 4 9 | # 2 and 3 are primes 10 | value = true 11 | elsif number < 2 or number % 2 == 0 or number % 3 == 0 12 | # Negatives, 0, 1, all even numbers, all multiples of 3 are not primes 13 | value = false 14 | end 15 | end_range = (Math.sqrt(number) + 1).to_i 16 | # All primes number are in format of 6k +/- 1 17 | for i in (5..end_range).step(6) 18 | if number % i == 0 or number % (i + 2) == 0 19 | value = false 20 | end 21 | end 22 | result = value 23 | end 24 | 25 | def solution(max_total = 2000000) 26 | sum = 1 27 | num = 2 28 | value = 1 29 | while num < max_total and value < max_total 30 | if is_prime?(num) 31 | value += num 32 | if value < max_total 33 | sum = value 34 | end 35 | end 36 | num += 1 37 | end 38 | result = sum 39 | end 40 | 41 | answer = solution() 42 | p answer -------------------------------------------------------------------------------- /project_euler/problem_014/sol1.rb: -------------------------------------------------------------------------------- 1 | #Problem 14: https://projecteuler.net/problem=14 2 | 3 | #Problem Statement: 4 | #The following iterative sequence is defined for the set of positive integers: 5 | # 6 | # n → n/2 (n is even) 7 | # n → 3n + 1 (n is odd) 8 | # 9 | #Using the rule above and starting with 13, we generate the following sequence: 10 | # 11 | # 13 → 40 → 20 → 10 → 5 → 16 → 8 → 4 → 2 → 1 12 | # 13 | #It can be seen that this sequence (starting at 13 and finishing at 1) contains 14 | #10 terms. Although it has not been proved yet (Collatz Problem), it is thought 15 | #that all starting numbers finish at 1. 16 | 17 | #Which starting number, under one million, produces the longest chain? 18 | 19 | def solution() 20 | index_best_result = 0 21 | for num in 2..1000000 22 | index_candidate = 0 23 | n = num 24 | while n > 1 25 | if n%2 == 0 26 | n = n / 2 27 | else 28 | n = (3*n) + 1 29 | end 30 | index_candidate +=1 31 | end 32 | if index_best_result < index_candidate 33 | index_best_result = index_candidate 34 | value = num 35 | end 36 | end 37 | result = value 38 | end 39 | 40 | answer = solution() 41 | p answer -------------------------------------------------------------------------------- /project_euler/problem_020/sol1.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # n! means n x (n - 1) x ... x 3 x 2 x 1 4 | 5 | # For example, 10! = 10 x 9 x ... x 3 x 2 x 1 = 3628800, 6 | # and the sum of the digits in the number 10! is 3 + 6 + 2 + 8 + 8 + 0 + 0 = 27. 7 | # 8 | # Find the sum of the digits in the number 100! 9 | 10 | # method to calculate factorial of a number 11 | def factorial(number) 12 | number.downto(1).reduce(:*) 13 | end 14 | 15 | # fetch digits of factorial of `number` and find 16 | # sum of all those digits, and prints the result on the console 17 | number = 100 18 | puts factorial(number).digits.sum 19 | -------------------------------------------------------------------------------- /project_euler/problem_021/sol1.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Let d(n) be defined as the sum of proper divisors of n 4 | # (numbers less than n which divide evenly into n). 5 | # If d(a) = b and d(b) = a, where a & b, then a and b are an amicable pair. 6 | # and each of a and b are called amicable numbers. 7 | # 8 | # For example, 9 | # 10 | # The proper divisors of 220 are 1, 2, 4, 5, 10, 11, 20, 22, 44, 55 and 110; 11 | # therefore d(220) = 284. 12 | # 13 | # The proper divisors of 284 are 1, 2, 4, 71 and 142; so d(284) = 220. 14 | # 15 | # Evaluate the sum of all the amicable numbers under 10000. 16 | 17 | # get list of all divisors of `number` 18 | def get_divisors(number) 19 | divisors = [] 20 | (1..(Math.sqrt(number).to_i)).each do |num| 21 | if (number % num).zero? 22 | divisors << num 23 | divisors << number / num 24 | end 25 | end 26 | divisors 27 | end 28 | 29 | # get list of all proper divisors of `number` i.e. removing `number` from 30 | # the list of divisors 31 | def get_proper_divisors(number) 32 | divisors = get_divisors(number) 33 | divisors.delete(number) 34 | divisors 35 | end 36 | 37 | # implementation of a method `d` as mentioned in the question 38 | # i.e. finding sum of all proper divisors of `number` 39 | def d(number) 40 | get_proper_divisors(number).sum 41 | end 42 | 43 | # given an upper `limit`, this method finds all amicable numbers 44 | # under this `limit` 45 | def find_amicable_numbers(limit) 46 | result = [] 47 | (1...limit).each do |a| 48 | b = d(a) 49 | res = d(b) 50 | result.push(a) if (a == res) && (a != b) 51 | end 52 | result 53 | end 54 | 55 | # calling `find_amicable_numbers` method and finding sum of all such numbers 56 | # below 10000, and printing the result on the console 57 | puts find_amicable_numbers(10_000).sum 58 | -------------------------------------------------------------------------------- /project_euler/problem_022/sol1.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Problem 22 4 | # Using names.txt (right click and 'Save Link/Target As...'), 5 | # a 46K text file containing over five-thousand first names, 6 | # begin by sorting it into alphabetical order. 7 | # Then working out the alphabetical value for each name, 8 | # multiply this value by its alphabetical position in the list to obtain a name score. 9 | 10 | # For example, when the list is sorted into alphabetical order, 11 | # COLIN, which is worth 3 + 15 + 12 + 9 + 14 = 53, 12 | # is the 938th name in the list. So, COLIN would obtain a score of 938 * 53 = 49714. 13 | 14 | # What is the total of all the name scores in the file? 15 | 16 | # reading the contents of the file 17 | file_contents = File.read('p022_names.txt') 18 | 19 | # replacing the occuerance of \" to '' and spliting the result by ',' 20 | # to get an array of sorted words 21 | words = file_contents.tr('\"', '').split(',').sort 22 | 23 | # this method calculates the worth of a word based on the ASCII 24 | # values of the characters 25 | def word_worth(word) 26 | word.chars.sum { |char| char.ord - 'A'.ord + 1 } 27 | end 28 | 29 | # this method takes the words as an input 30 | # calls `word_worth` method on each word 31 | # to that value multiply that with the index of the word in the array 32 | # add the same to the result 33 | def total_rank(words) 34 | result = 0 35 | words.each_with_index { |word, index| result += word_worth(word) * (index + 1) } 36 | result 37 | end 38 | 39 | # outputs total rank on the console 40 | puts total_rank(words) 41 | -------------------------------------------------------------------------------- /project_euler/problem_025/sol1.rb: -------------------------------------------------------------------------------- 1 | #The Fibonacci sequence is defined by the recurrence relation: 2 | # Fn = Fn−1 + Fn−2, where F1 = 1 and F2 = 1. 3 | #Hence the first 12 terms will be: 4 | # 5 | # F1 = 1 6 | # F2 = 1 7 | # F3 = 2 8 | # F4 = 3 9 | # F5 = 5 10 | # F7 = 13 11 | # F8 = 21 12 | # F6 = 8 13 | # F9 = 34 14 | # F10 = 55 15 | # F11 = 89 16 | # F12 = 144 17 | # 18 | #The 12th term, F12, is the first term to contain three digits. 19 | #What is the index of the first term in the Fibonacci sequence to contain 1000 digits? 20 | 21 | def solution(num_digits = 1000) 22 | #Fn = Fn−1 + Fn−2, where F1 = 1 and F2 = 1. 23 | resultn1 = 1 24 | resultn2 = 1 25 | result = 2 26 | index = 3 27 | value = true 28 | while value 29 | resultn2 = resultn1 30 | resultn1 = result 31 | if (resultn1 + resultn2).abs.digits.length < num_digits 32 | value = true 33 | else 34 | value = false 35 | end 36 | result = resultn1 + resultn2 37 | index += 1 38 | end 39 | res = index 40 | end 41 | 42 | answer = solution() 43 | p answer 44 | -------------------------------------------------------------------------------- /searches/binary_search.rb: -------------------------------------------------------------------------------- 1 | # Searches through a list for a value in O(log(n)) time. 2 | # The list must be sorted. 3 | def binary_search(array, key) 4 | front = 0 5 | back = array.length - 1 6 | while front <= back 7 | middle = (front + back) / 2 8 | return middle if array[middle] == key 9 | 10 | if key < array[middle] 11 | back = middle - 1 12 | else 13 | front = middle + 1 14 | end 15 | end 16 | 17 | nil 18 | end 19 | 20 | puts "Enter a sorted space-separated list:" 21 | arr = gets.chomp.split(' ').map(&:to_i) 22 | 23 | puts "Enter the value to be searched:" 24 | value = gets.chomp.to_i 25 | 26 | puts if binary_search(arr, value) != nil 27 | "Found at index #{binary_search(arr, value)}" 28 | else 29 | "Not found" 30 | end 31 | -------------------------------------------------------------------------------- /searches/depth_first_search.rb: -------------------------------------------------------------------------------- 1 | # @param [Integer] start 2 | # @param [Integer] target 3 | # @param [Array] adjacency_list 4 | # @return [Array] routes 5 | def dfs(start, target, adjacency_list) 6 | is_visited = Hash.new(false) 7 | parent = {} 8 | stack = [start] 9 | loop do 10 | break if stack.empty? 11 | 12 | current_node = stack.pop 13 | is_visited[current_node] = true 14 | 15 | return get_path(parent, target) if current_node == target 16 | 17 | adjacency_list[current_node].each do |neighbor| 18 | next if is_visited[neighbor] 19 | 20 | stack << neighbor 21 | is_visited[neighbor] = true 22 | parent[neighbor] = current_node 23 | end 24 | end 25 | [] 26 | end 27 | 28 | # @param [Hash] parent 29 | # @param [Integer] dest 30 | # @return [Array] path 31 | def get_path(parent, dest) 32 | iterator = dest 33 | path = [dest] 34 | while parent.has_key?(iterator) 35 | path << parent[iterator] 36 | iterator = parent[iterator] 37 | end 38 | path.reverse 39 | end 40 | 41 | def main 42 | adjacency_list = [ 43 | [1, 2], # 0 44 | [0, 3], # 1 45 | [0, 3], # 2 46 | [1, 2, 4], # 3 47 | [3, 5], # 4 48 | [4] # 5 49 | ] 50 | p dfs(0, 5, adjacency_list) 51 | end 52 | 53 | main 54 | -------------------------------------------------------------------------------- /searches/double_linear_search.rb: -------------------------------------------------------------------------------- 1 | # Iterate through the array from both sides to find the index of search_item. 2 | 3 | def double_linear_search(array, search_item) 4 | start_ind = 0 5 | end_ind = array.length - 1 6 | 7 | while start_ind <= end_ind 8 | return start_ind if array[start_ind] == search_item 9 | return end_ind if array[end_ind] == search_item 10 | 11 | start_ind += 1 12 | end_ind -= 1 13 | end 14 | 15 | # returns -1 if search_item is not found in array 16 | -1 17 | end 18 | 19 | puts(double_linear_search([1, 5, 5, 10], 1)) 20 | # => 0 21 | 22 | puts(double_linear_search([1, 5, 5, 10], 5)) 23 | # => 1 24 | 25 | puts(double_linear_search([1, 5, 5, 10], 100)) 26 | # => -1 27 | 28 | puts(double_linear_search([1, 5, 5, 10], 10)) 29 | # => 3 30 | -------------------------------------------------------------------------------- /searches/fibonacci_search.rb: -------------------------------------------------------------------------------- 1 | def fibonacci_search int arr, int element 2 | n = n.size 3 | f2 = 0 4 | f1 = 1 5 | f = f2 + f1 6 | offset = -1 7 | 8 | while f < n do 9 | f2 = f1; 10 | f1 = f; 11 | f = f2 + f1; 12 | end 13 | 14 | while f > 1 do 15 | i = [offset+f2, n-1].min 16 | 17 | if arr[i] < element 18 | f = f1 19 | f1 = f2 20 | f2 = f - f1 21 | offset = i 22 | elsif arr[i] > element 23 | f = f2 24 | f1 = f1 - f2 25 | f2 = f - f1 26 | else 27 | return i 28 | end 29 | end 30 | 31 | return offset + 1 if f1 && arr[offset + 1] == element 32 | 33 | -1 34 | end 35 | -------------------------------------------------------------------------------- /searches/jump_search.rb: -------------------------------------------------------------------------------- 1 | # Works only on sorted arrays. 2 | # Finding element by creating step in array and jump ahead by fixed steps and finding element using linear search inside that steped array. 3 | # Time Complexity: O(√n) 4 | 5 | def jump_search(arr, x) 6 | n = arr.length 7 | 8 | # Finding block size to be jumped 9 | step = Math.sqrt(n) 10 | prev = 0 11 | 12 | # Finding the block where element is 13 | # present (if it is present) 14 | while arr[[step, n].min - 1] < x 15 | prev = step 16 | step += Math.sqrt(n) 17 | return -1 if prev >= n 18 | end 19 | 20 | # Doing a linear search for x in block 21 | # beginning with prev. 22 | while arr[prev] < x 23 | prev += 1 24 | # If we reached next block or end of 25 | # array, element is not present. 26 | return -1 if prev == [step, n].min 27 | end 28 | 29 | # If element is found 30 | return prev if arr[prev] == x 31 | 32 | -1 33 | end 34 | 35 | puts 'Enter a sorted space-separated list:' 36 | arr = gets.chomp.split(' ').map(&:to_i) 37 | puts 'Enter the value to be searched:' 38 | value = gets.chomp.to_i 39 | 40 | index = jump_search(arr, value) 41 | 42 | puts index == -1 ? 'Element not found' : "Number #{value} is at #{index}" 43 | -------------------------------------------------------------------------------- /searches/linear_search.rb: -------------------------------------------------------------------------------- 1 | # Looks through array for a value in O(n) time. 2 | # Array does not need to be sorted. 3 | def linear_search(array, key) 4 | array.each_with_index do |current, index| 5 | return index if current == key 6 | end 7 | nil 8 | end 9 | 10 | puts "Enter a space-separated list:" 11 | arr = gets.chomp.split(' ').map(&:to_i) 12 | puts "Enter a value to be searched:" 13 | key = gets.chomp.to_i 14 | puts if linear_search(arr, key) != nil 15 | "Found at index #{linear_search(arr, key)}" 16 | else 17 | "Not found" 18 | end 19 | -------------------------------------------------------------------------------- /searches/number_of_islands.rb: -------------------------------------------------------------------------------- 1 | # Given an m x n 2D binary grid grid which represents a map of '1's (land) and '0's (water), return the number of islands. 2 | # An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water. 3 | 4 | # Example 1: 5 | # Input: grid = [ 6 | # ["1","1","1","1","0"], 7 | # ["1","1","0","1","0"], 8 | # ["1","1","0","0","0"], 9 | # ["0","0","0","0","0"] 10 | # ] 11 | # Output: 1 12 | 13 | # Example 2: 14 | # Input: grid = [ 15 | # ["1","1","0","0","0"], 16 | # ["1","1","0","0","0"], 17 | # ["0","0","1","0","0"], 18 | # ["0","0","0","1","1"] 19 | # ] 20 | # Output: 3 21 | 22 | # Constraints: 23 | # m == grid.length 24 | # n == grid[i].length 25 | # 1 <= m, n <= 300 26 | # grid[i][j] is '0' or '1'. 27 | 28 | # DFS, Recursive Bottom Up Approach - O(n*m) Time / O(1) Space 29 | # Init num_of_islands = 0, return if the grid is empty 30 | # Start a double loop with index to iterate through each plot (each value is a plot of either water or land in this case) 31 | # if the plot is land, dfs(grid, x, y) 32 | # num_of_islands += 1 33 | # Return num_of_islands 34 | 35 | # dfs(grid, x, y) 36 | # Return if x or y are out of bounds, or if the plot is water 37 | # Make the current plot water 38 | # Call dfs again for up, down, left, and right 39 | 40 | # @param {Character[][]} grid 41 | # @return {Integer} 42 | def num_islands(grid) 43 | return 0 if grid.empty? 44 | 45 | # init num of islands 46 | islands = 0 47 | 48 | # loop through each element (plot) in the 2d array 49 | grid.each_with_index do |row, x| 50 | row.each_with_index do |plot, y| 51 | # if the plot is water, start a dfs 52 | next unless plot == '1' 53 | 54 | dfs(grid, x, y) 55 | # add 1 to islands once all connected land plots are searched 56 | islands += 1 57 | end 58 | end 59 | 60 | # return ans 61 | islands 62 | end 63 | 64 | def dfs(grid, x, y) 65 | # don't search if out of bounds, or if it's already water 66 | return if x < 0 || x >= grid.length || y < 0 || y >= grid[0].length || grid[x][y] == '0' 67 | 68 | # set the plot to water 69 | grid[x][y] = '0' 70 | 71 | # search each adjacent plot 72 | dfs(grid, x - 1, y) # up 73 | dfs(grid, x + 1, y) # down 74 | dfs(grid, x, y - 1) # left 75 | dfs(grid, x, y + 1) # right 76 | end 77 | -------------------------------------------------------------------------------- /searches/recursive_double_linear_search.rb: -------------------------------------------------------------------------------- 1 | # Iterate through the array to find the index of key using recursion. 2 | 3 | def recursive_double_linear_search(data, key, left = 0, right = 0) 4 | right &&= data.length - 1 5 | 6 | return -1 if left > right 7 | 8 | return left if data[left] == key 9 | return right if data[right] == key 10 | 11 | recursive_double_linear_search(data, key, left + 1, right - 1) 12 | end 13 | 14 | puts(recursive_double_linear_search([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 5)) 15 | # => 5 16 | 17 | puts(recursive_double_linear_search([1, 2, 4, 5, 3], 4)) 18 | # => 2 19 | 20 | puts(recursive_double_linear_search([1, 2, 4, 5, 3], 6)) 21 | # => -1 22 | 23 | puts(recursive_double_linear_search([5], 5)) 24 | # => 0 25 | 26 | puts(recursive_double_linear_search([], 1)) 27 | # => -1 28 | -------------------------------------------------------------------------------- /searches/recursive_linear_search.rb: -------------------------------------------------------------------------------- 1 | # A pure Ruby implementation of a recursive linear search algorithm 2 | 3 | def rec_linear_search(sequence, low, high, target) 4 | raise Exception('Invalid upper or lower bound!') unless high < sequence.length && low < sequence.length 5 | 6 | return -1 if high < low 7 | 8 | return low if sequence[low] == target 9 | 10 | return high if sequence[high] == target 11 | 12 | rec_linear_search(sequence, low + 1, high - 1, target) 13 | end 14 | 15 | puts(rec_linear_search([0, 30, 500, 100, 700], 0, 4, 0)) 16 | # => 0 17 | 18 | puts(rec_linear_search([0, 30, 500, 100, 700], 0, 4, 700)) 19 | # => 4 20 | 21 | puts(rec_linear_search([0, 30, 500, 100, 700], 0, 4, 30)) 22 | # => 1 23 | 24 | puts(rec_linear_search([0, 30, 500, 100, 700], 0, 4, -6)) 25 | # => -1 26 | -------------------------------------------------------------------------------- /searches/ternary_search.rb: -------------------------------------------------------------------------------- 1 | # Ternary Search 2 | # ------------------------------- 3 | # Ternary search is a searching technique that is used to search the position of a specific value in an array. 4 | # Ternary search is a divide-and-conquer algorithm. 5 | # It is mandatory for the array to be sorted (in which you will search for an element). 6 | # The array is divided into three parts and then we determine in which part the element exists. 7 | # In this search, after each iteration it neglects 1/3 part of the array and repeats the same operations on the remaining ⅔. 8 | # Time Complexity: O(log3 n) 9 | # Space Complexity: O(1) 10 | 11 | def ternary_search(l, r, key, arr) 12 | # l is the starting index and r is the ending index of the array/sub-array. 13 | if r >= l 14 | # find mid1 and mid2 15 | mid1 = l + (r - l) / 3 16 | mid2 = r - (r - l) / 3 17 | # check if key is equal to mid1 18 | if arr[mid1] == key 19 | mid1 20 | # check if key is equal to mid2 21 | elsif arr[mid2] == key 22 | mid2 23 | # Since key is not present at mid, check in which region it is present 24 | # then repeat the Search operation in that region 25 | elsif key < arr[mid1] 26 | ternary_search(l, mid1 - 1, key, arr) 27 | elsif key > arr[mid2] 28 | ternary_search(mid2 + 1, r, key, arr) 29 | else 30 | ternary_search(mid1 + 1, mid2 - 1, key, arr) 31 | end 32 | end 33 | end 34 | 35 | puts "Enter a space-separated list:" 36 | arr = gets.chomp.split(' ').map(&:to_i) 37 | puts "Enter a value to be searched:" 38 | key = gets.chomp.to_i 39 | puts if ternary_search(0, arr.length - 1, key, arr) != nil 40 | "Found at index #{ternary_search(0, arr.length - 1, key, arr)}" 41 | else 42 | "Not found" 43 | end 44 | -------------------------------------------------------------------------------- /sorting/bead_sort.rb: -------------------------------------------------------------------------------- 1 | class Array 2 | def columns 3 | x = map(&:length).max 4 | Array.new(x) do |row| 5 | Array.new(length) { |column| self[column][row] }.compact 6 | end 7 | end 8 | end 9 | 10 | def bead_sort(array) 11 | array 12 | .map { |element| [1] * element } 13 | .columns 14 | .columns 15 | .map(&:length) 16 | .reverse 17 | end 18 | 19 | if $0 == __FILE__ 20 | puts 'Enter a list of numbers separated by space' 21 | 22 | list = gets.split.map(&:to_i) 23 | p bead_sort(list) 24 | end 25 | -------------------------------------------------------------------------------- /sorting/bead_sort_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require_relative './sort_tests' 3 | require_relative './bead_sort' 4 | 5 | class TestInsertionSort < Minitest::Test 6 | include SortTests 7 | 8 | def sort(input) 9 | bead_sort(input) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /sorting/binary_insertion_sort.rb: -------------------------------------------------------------------------------- 1 | # Ruby implementation of binary insertion sort algorithm 2 | 3 | def binary_search(arr, val, start, stop) 4 | while start <= stop 5 | 6 | mid = (start + stop) / 2 7 | 8 | if val == arr[mid] # val is in the middle 9 | return mid 10 | elsif val > arr[mid] # val is on the right side 11 | start = mid + 1 12 | else 13 | stop = mid - 1 # val is on the left side 14 | end 15 | end 16 | 17 | start 18 | end 19 | 20 | def binary_insertion_sort(arr) 21 | n = arr.size 22 | 23 | (0...n).each do |index| 24 | j = index - 1 25 | selected = arr[index] 26 | 27 | # find location where selected value should be inserted 28 | location = binary_search(arr, selected, 0, j) 29 | 30 | # move all elements after location to make space 31 | while j >= location 32 | arr[j + 1] = arr[j] 33 | j -= 1 34 | arr[j + 1] = selected 35 | end 36 | end 37 | 38 | arr 39 | end 40 | 41 | if $0 == __FILE__ 42 | puts 'Enter a list of numbers separated by space' 43 | 44 | list = gets.split.map(&:to_i) 45 | p binary_insertion_sort(list) 46 | end -------------------------------------------------------------------------------- /sorting/binary_insertion_sort_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require_relative './sort_tests' 3 | require_relative './binary_insertion_sort' 4 | 5 | class TestBinaryInsertionSort < Minitest::Test 6 | include SortTests 7 | 8 | def sort(input) 9 | binary_insertion_sort(input) 10 | end 11 | end -------------------------------------------------------------------------------- /sorting/bogo_sort.rb: -------------------------------------------------------------------------------- 1 | class Array 2 | def sorted? 3 | ### goes thru array and checks if all elements are in order 4 | (1...length).each do |i| 5 | return false if self[i - 1] > self[i] 6 | end 7 | true 8 | end 9 | 10 | def bogosort 11 | ### randomly shuffles until sorted 12 | shuffle! until sorted? 13 | self # return sorted array 14 | end 15 | end 16 | 17 | if $0 == __FILE__ 18 | puts 'Enter a list of numbers separated by space' 19 | str = gets.chomp.split('') 20 | puts str.bogosort.join('') 21 | end 22 | -------------------------------------------------------------------------------- /sorting/bogo_sort_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require_relative './sort_tests' 3 | require_relative './bogo_sort' 4 | 5 | class TestBogoSort < Minitest::Test 6 | include SortTests 7 | 8 | def sort(input) 9 | input.bogosort 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /sorting/bubble_sort.rb: -------------------------------------------------------------------------------- 1 | def bubble_sort(array) 2 | array_length = array.length 3 | return array if array_length <= 1 4 | unsorted_until_index = array_length - 1 5 | sorted = false 6 | until sorted 7 | sorted = true 8 | 0.upto(unsorted_until_index - 1) do |i| 9 | if array[i] > array[i+1] 10 | array[i], array[i+1] = array[i+1], array[i] 11 | sorted = false 12 | end 13 | end 14 | unsorted_until_index -= 1 15 | end 16 | return array 17 | end 18 | 19 | if $0 == __FILE__ 20 | puts 'Enter a list of numbers separated by space' 21 | 22 | list = gets 23 | bubble_sort(list) 24 | print list 25 | end 26 | -------------------------------------------------------------------------------- /sorting/bubble_sort_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require_relative './sort_tests' 3 | require_relative './bubble_sort' 4 | 5 | class TestBubbleSort < Minitest::Test 6 | include SortTests 7 | 8 | def sort(input) 9 | bubble_sort(input) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /sorting/bucket_sort.rb: -------------------------------------------------------------------------------- 1 | DEFAULT_BUCKET_SIZE = 5 2 | 3 | def bucket_sort(array, bucket_size = DEFAULT_BUCKET_SIZE) 4 | bucket_count = ((array.max - array.min) / bucket_size).floor + 1 5 | 6 | # create buckets 7 | buckets = [] 8 | bucket_count.times { buckets.push [] } 9 | 10 | # fill buckets 11 | array.each do |item| 12 | buckets[((item - array.min) / bucket_size).floor].push(item) 13 | end 14 | 15 | # sort buckets 16 | buckets.each do |bucket| 17 | bucket.sort! 18 | end 19 | 20 | buckets.flatten 21 | end 22 | 23 | if $0 == __FILE__ 24 | puts 'Enter a list of numbers separated by space' 25 | 26 | list = gets.split.map(&:to_i) 27 | p bucket_sort(list) 28 | end 29 | -------------------------------------------------------------------------------- /sorting/bucket_sort_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require_relative './sort_tests' 3 | require_relative './bucket_sort' 4 | 5 | class TestBucketSort < Minitest::Test 6 | include SortTests 7 | 8 | def sort(input) 9 | bucket_sort(input) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /sorting/cocktail_sort.rb: -------------------------------------------------------------------------------- 1 | def cocktail_sort(array) 2 | start = 0 3 | finish = array.length - 1 4 | way = 1 5 | loop do 6 | swapped = false 7 | start.step(finish - way, way) do |i| 8 | if (array[i] <=> array[i + way]) == way 9 | array[i], array[i + way] = array[i + way], array[i] 10 | swapped = i 11 | end 12 | end 13 | break unless swapped 14 | 15 | finish = start 16 | start = swapped 17 | way = -way 18 | end 19 | array 20 | end 21 | 22 | if $0 == __FILE__ 23 | puts 'Enter a list of numbers separated by space' 24 | 25 | list = gets.split.map(&:to_i) 26 | p cocktail_sort(list) 27 | end 28 | -------------------------------------------------------------------------------- /sorting/cocktail_sort_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require_relative './sort_tests' 3 | require_relative './cocktail_sort' 4 | 5 | class TestInsertionSort < Minitest::Test 6 | include SortTests 7 | 8 | def sort(input) 9 | cocktail_sort(input) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /sorting/comb_sort.rb: -------------------------------------------------------------------------------- 1 | def comb_sort(array) 2 | gap = array.length 3 | swaps = true 4 | while (gap > 1) || swaps 5 | gap = [1, (gap / 1.25).to_i].max 6 | swaps = false 7 | 0.upto(array.length - gap - 1) do |i| 8 | if array[i] > array[i + gap] 9 | array[i], array[i + gap] = array[i + gap], array[i] 10 | swaps = true 11 | end 12 | end 13 | end 14 | array 15 | end 16 | 17 | if $0 == __FILE__ 18 | puts 'Enter a list of numbers separated by space' 19 | 20 | list = gets.split.map(&:to_i) 21 | p insertion_sort(list) 22 | end 23 | -------------------------------------------------------------------------------- /sorting/comb_sort_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require_relative './sort_tests' 3 | require_relative './comb_sort' 4 | 5 | class TestInsertionSort < Minitest::Test 6 | include SortTests 7 | 8 | def sort(input) 9 | comb_sort(input) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /sorting/counting_sort.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # Given a non-negative integer value_upper_bound and an array of integers arr with values between 0 and value_upper_bound, 3 | # returns a sorted copy of the input array. 4 | # When value_upper_bound = O(arr.length), sorting runs in O(arr.length). 5 | 6 | def counting_sort(arr, value_upper_bound) 7 | if !value_upper_bound.integer? || value_upper_bound < 0 8 | raise ArgumentError.new("counting_sort must be invoked with integer value_upper_bound >= 0") 9 | end 10 | if !arr.all? { |elem| elem.integer? && elem.between?(0, value_upper_bound) } 11 | raise ArgumentError.new("counting_sort must be invoked with integer array elements in (0..value_upper_bound)") 12 | end 13 | sorted_arr = Array.new(arr.length) { 0 } 14 | tmp_arr = Array.new(value_upper_bound+1) { 0 } 15 | for elem in arr 16 | tmp_arr[elem] += 1 17 | end 18 | for i in 1..value_upper_bound 19 | tmp_arr[i] += tmp_arr[i-1] 20 | end 21 | arr.reverse_each do |elem| 22 | sorted_arr[tmp_arr[elem]-1] = elem 23 | tmp_arr[elem] -= 1 24 | end 25 | sorted_arr 26 | end 27 | -------------------------------------------------------------------------------- /sorting/counting_sort_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require_relative 'counting_sort' 3 | 4 | class TestCountingSort < Minitest::Test 5 | def test_empty_array_given_empty_array 6 | assert counting_sort([], 1).empty? 7 | end 8 | 9 | def test_array_sorted_correctly 10 | assert counting_sort([1, 5, 3, 0, 4, 2, 4], 5) == [0, 1, 2, 3, 4, 4, 5] 11 | end 12 | 13 | def test_exception_given_non_integer_upper_bound 14 | assert_raises ArgumentError do 15 | counting_sort([1, 3, 2], 5.5) 16 | end 17 | end 18 | 19 | def test_exception_given_negative_upper_bound 20 | assert_raises ArgumentError do 21 | counting_sort([1, 3, 2], -1) 22 | end 23 | end 24 | 25 | def test_exception_given_non_integer_elements 26 | assert_raises ArgumentError do 27 | counting_sort([1, 3, 2.5], 5) 28 | end 29 | end 30 | 31 | def test_exception_given_negative_elements 32 | assert_raises ArgumentError do 33 | counting_sort([1, 3, -2], 5) 34 | end 35 | end 36 | 37 | def test_exception_given_elements_above_upper_bound 38 | assert_raises ArgumentError do 39 | counting_sort([1, 3, 6], 5) 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /sorting/gnome_sort.rb: -------------------------------------------------------------------------------- 1 | def gnome_sort(arr) 2 | i = 0 3 | while i < arr.length 4 | if i == 0 || arr[i] >= arr[i - 1] 5 | i += 1 6 | else 7 | arr[i], arr[i - 1] = arr[i - 1], arr[i] 8 | i -= 1 9 | end 10 | end 11 | arr 12 | end 13 | -------------------------------------------------------------------------------- /sorting/gnome_sort_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require_relative './sort_tests' 3 | require_relative './gnome_sort' 4 | 5 | class TestGnomeSort < Minitest::Test 6 | include SortTests 7 | 8 | def sort(input) 9 | gnome_sort(input) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /sorting/heap_sort.rb: -------------------------------------------------------------------------------- 1 | # Algorithm: Heap-Sort 2 | # Time-Complexity: O(nlogn) 3 | def heap_sort(array) 4 | array_size = array.size 5 | adjusted_array = [nil] + array 6 | (array_size / 2).downto(1) do |i| 7 | adjusted_down(adjusted_array, i, array_size) 8 | end 9 | while array_size > 1 10 | adjusted_array[1], adjusted_array[array_size] = adjusted_array[array_size], adjusted_array[1] 11 | array_size -= 1 12 | adjusted_down(adjusted_array, 1, array_size) 13 | end 14 | adjusted_array.drop(1) 15 | end 16 | 17 | # Method to adjust heap in downward manner 18 | def adjusted_down(adjusted_array, parent, limit) 19 | top = adjusted_array[parent] 20 | while (child = 2 * parent) <= limit 21 | child += 1 if (child < limit) && (adjusted_array[child] < adjusted_array[child + 1]) 22 | break if top >= adjusted_array[child] 23 | 24 | adjusted_array[parent] = adjusted_array[child] 25 | parent = child 26 | end 27 | adjusted_array[parent] = top 28 | end 29 | 30 | if $0 == __FILE__ 31 | puts 'Enter a list of numbers separated by space' 32 | 33 | list = gets.split.map(&:to_i) 34 | p heap_sort(list) 35 | end 36 | -------------------------------------------------------------------------------- /sorting/heap_sort_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require_relative './sort_tests' 3 | require_relative './heap_sort' 4 | 5 | class TestHeapSort < Minitest::Test 6 | include SortTests 7 | 8 | def sort(input) 9 | heap_sort(input) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /sorting/insertion_sort.rb: -------------------------------------------------------------------------------- 1 | def insertion_sort(array) 2 | 0.upto(array.length - 1).each do |index| 3 | element = array[index] 4 | position = index 5 | while element < array[position - 1] && position > 0 6 | array[position] = array[position - 1] 7 | array[position - 1] = element 8 | position -= 1 9 | end 10 | end 11 | array 12 | end 13 | 14 | if $0 == __FILE__ 15 | puts 'Enter a list of numbers separated by space' 16 | 17 | list = gets.split.map(&:to_i) 18 | p insertion_sort(list) 19 | end 20 | -------------------------------------------------------------------------------- /sorting/insertion_sort_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require_relative './sort_tests' 3 | require_relative './insertion_sort' 4 | 5 | class TestInsertionSort < Minitest::Test 6 | include SortTests 7 | 8 | def sort(input) 9 | insertion_sort(input) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /sorting/merge_sort.rb: -------------------------------------------------------------------------------- 1 | def merge_sort(array) 2 | return array if array.length <= 1 3 | 4 | mid = array.length / 2 5 | first_array = array.slice(0..mid - 1) 6 | second_array = array.slice(mid..-1) 7 | 8 | first_array = merge_sort first_array 9 | second_array = merge_sort second_array 10 | 11 | # merge 12 | result = [] 13 | until first_array.empty? && second_array.empty? 14 | if first_array.empty? 15 | result.concat(second_array) 16 | second_array.clear 17 | elsif second_array.empty? 18 | result.concat(first_array) 19 | first_array.clear 20 | else 21 | result << if first_array.first < second_array.first 22 | first_array.shift 23 | else 24 | second_array.shift 25 | end 26 | end 27 | end 28 | result 29 | end 30 | 31 | if $0 == __FILE__ 32 | puts 'Enter a list of numbers separated by space' 33 | 34 | list = gets.split.map(&:to_i) 35 | p merge_sort(list) 36 | end 37 | -------------------------------------------------------------------------------- /sorting/merge_sort_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require_relative './sort_tests' 3 | require_relative './merge_sort' 4 | 5 | class TestMergeSort < Minitest::Test 6 | include SortTests 7 | 8 | def sort(input) 9 | merge_sort(input) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /sorting/pancake_sort.rb: -------------------------------------------------------------------------------- 1 | def pancake_sort(array) 2 | return array if array.length <= 1 3 | 4 | (array.length - 1).downto(1) do |index| 5 | max_index = array[0..index].index(array[0..index].max) 6 | next if max_index == index 7 | 8 | array[0..max_index] = array[0..max_index].reverse if max_index > 0 9 | array[0..index] = array[0..index].reverse 10 | end 11 | array 12 | end 13 | 14 | if $0 == __FILE__ 15 | puts 'Enter a list of numbers separated by space' 16 | 17 | list = gets.split.map(&:to_i) 18 | p pancake_sort(list) 19 | end 20 | -------------------------------------------------------------------------------- /sorting/pancake_sort_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require_relative './sort_tests' 3 | require_relative './pancake_sort' 4 | 5 | class TestInsertionSort < Minitest::Test 6 | include SortTests 7 | 8 | def sort(input) 9 | pancake_sort(input) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /sorting/quicksort.rb: -------------------------------------------------------------------------------- 1 | def quicksort(arr) 2 | return [] if arr.empty? 3 | 4 | # chose a random pivot value 5 | pivot = arr.delete_at(rand(arr.size)) 6 | # partition array into 2 arrays and comparing them to each other and eventually returning 7 | # array with the pivot value sorted 8 | left, right = arr.partition(&pivot.method(:>)) 9 | 10 | # recursively calling the quicksort method on itself 11 | [*quicksort(left), pivot, *quicksort(right)] 12 | end 13 | 14 | if $0 == __FILE__ 15 | puts 'Enter a list of numbers separated by space' 16 | 17 | list = gets.split.map(&:to_i) 18 | p quicksort(list) 19 | end 20 | -------------------------------------------------------------------------------- /sorting/quicksort_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require_relative './sort_tests' 3 | require_relative './quicksort' 4 | 5 | class TestQuicksort < Minitest::Test 6 | include SortTests 7 | 8 | def sort(input) 9 | quicksort(input) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /sorting/radix_sort.rb: -------------------------------------------------------------------------------- 1 | # This is a pure ruby implementation of the radix sort algorithm 2 | # the function returns collection ordered by ascending 3 | # Example: 4 | # pry(main)> radix_sort([6, 22, 43, 16, 0, 15, 9]) 5 | # => [0, 6, 9, 15, 16, 22, 43] 6 | 7 | def radix_sort(array, base = 10) 8 | # passes count equals to the number of digits in the longest number 9 | passes = (Math.log(array.minmax.map(&:abs).max) / Math.log(base)).floor + 1 10 | passes.times do |i| 11 | buckets = Array.new(2 * base) { [] } 12 | base_i = base**i 13 | 14 | # elements are added to buckets 15 | # according to the current place-value digit 16 | array.each do |j| 17 | n = (j / base_i) % base 18 | n += base if j >= 0 19 | buckets[n] << j 20 | end 21 | array = buckets.flatten 22 | end 23 | 24 | array 25 | end 26 | -------------------------------------------------------------------------------- /sorting/radix_sort_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require_relative './sort_tests' 3 | require_relative './radix_sort' 4 | 5 | class TestRadixSort < Minitest::Test 6 | include SortTests 7 | 8 | def sort(input) 9 | radix_sort(input) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /sorting/selection_sort.rb: -------------------------------------------------------------------------------- 1 | def selection_sort(array) 2 | n = array.length - 1 3 | i = 0 4 | while i <= n - 1 5 | smallest = i 6 | j = i + 1 7 | while j <= n 8 | smallest = j if array[j] < array[smallest] 9 | j += 1 10 | end 11 | array[i], array[smallest] = array[smallest], array[i] if i != smallest 12 | i += 1 13 | end 14 | array 15 | end 16 | 17 | if $0 == __FILE__ 18 | puts 'Enter a list of numbers separated by space' 19 | 20 | list = gets.split.map(&:to_i) 21 | p selection_sort(list) 22 | end 23 | -------------------------------------------------------------------------------- /sorting/selection_sort_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require_relative './sort_tests' 3 | require_relative './selection_sort' 4 | 5 | class TestSelectionSort < Minitest::Test 6 | include SortTests 7 | 8 | def sort(input) 9 | selection_sort(input) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /sorting/shell_sort.rb: -------------------------------------------------------------------------------- 1 | def shell_sort(a) 2 | n = a.length 3 | h = 1 4 | 5 | h = (3 * h) + 1 while h < n / 3 6 | 7 | while h >= 1 8 | # Logic of insertion sort with inrement steps of "h" 9 | (h...n).each do |i| 10 | j = i 11 | while j >= h 12 | if a[j - h] > a[j] 13 | temp = a[j] 14 | a[j] = a[j - h] 15 | a[j - h] = temp 16 | end 17 | j -= h 18 | end 19 | end 20 | h /= 3 21 | end 22 | 23 | a 24 | end 25 | -------------------------------------------------------------------------------- /sorting/shell_sort_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require_relative './sort_tests' 3 | require_relative './shell_sort' 4 | 5 | class TestShellSort < Minitest::Test 6 | include SortTests 7 | 8 | def sort(input) 9 | shell_sort(input) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /sorting/sort_color.rb: -------------------------------------------------------------------------------- 1 | # Given an array nums with n objects colored red, white, or blue, sort them in-place so that objects of the same color are adjacent, with the colors in the order red, white, and blue. 2 | # 3 | # We will use the integers 0, 1, and 2 to represent the color red, white, and blue, respectively. 4 | # 5 | # Example 1: 6 | # 7 | # Input: nums = [2,0,2,1,1,0] 8 | # Output: [0,0,1,1,2,2] 9 | # 10 | # Example 2: 11 | # 12 | # Input: nums = [2,0,1] 13 | # Output: [0,1,2] 14 | # 15 | # Example 3: 16 | # 17 | # Input: nums = [0] 18 | # Output: [0] 19 | # 20 | # Example 4: 21 | # 22 | # Input: nums = [1] 23 | # Output: [1] 24 | 25 | # @param {Integer[]} nums 26 | # @return {Void} Do not return anything, modify nums in-place instead. 27 | def sort_colors(nums) 28 | bubble_sort(nums) 29 | end 30 | 31 | def bubble_sort(array) 32 | array_length = array.size 33 | return array if array_length <= 1 34 | 35 | loop do 36 | swapped = false 37 | 38 | (array_length - 1).times do |i| 39 | if array[i] > array[i + 1] 40 | array[i], array[i + 1] = array[i + 1], array[i] 41 | swapped = true 42 | end 43 | end 44 | 45 | break unless swapped 46 | end 47 | 48 | array 49 | end 50 | 51 | nums = [2, 0, 2, 1, 1, 0] 52 | puts sort_colors(nums) 53 | # Output: [0,0,1,1,2,2] 54 | 55 | nums = [2, 0, 1] 56 | puts sort_colors(nums) 57 | # Output: [0,1,2] 58 | 59 | nums = [0] 60 | puts sort_colors(nums) 61 | # Output: [0] 62 | 63 | nums = [1] 64 | puts sort_colors(nums) 65 | # Output: [1] 66 | -------------------------------------------------------------------------------- /sorting/sort_tests.rb: -------------------------------------------------------------------------------- 1 | # SortTests provides general test cases for sorting function. 2 | # By using this module, tests can be implemented like this: 3 | # 4 | # class TestBuiltinSort < Minitest::Test 5 | # # SortTests adds some test_* methods. 6 | # include SortTests 7 | # 8 | # # SortTests requires sort method which receives an array of integers 9 | # # and returns a sorted one. 10 | # def sort(input) 11 | # input.sort 12 | # end 13 | # end 14 | 15 | module SortTests 16 | def sort(input) 17 | raise NotImplementedError 18 | end 19 | 20 | def test_sorted_array 21 | input = [1, 2, 3, 4, 5] 22 | expected = input.dup 23 | assert_equal expected, sort(input) 24 | end 25 | 26 | def test_reversed_array 27 | input = [5, 4, 3, 2, 1] 28 | expected = [1, 2, 3, 4, 5] 29 | assert_equal expected, sort(input) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /strings/boyer_moore_horspool_search.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # This class represents a table of {bad_match_character => slide_offset} 3 | # to be used in Boyer-Moore-Horspool substring finding algorithm. 4 | 5 | class BadMatchTable 6 | 7 | attr_reader :pattern 8 | attr_reader :table 9 | 10 | def initialize(pattern) 11 | @pattern = pattern 12 | @table = {} 13 | for i in 0...pattern.size 14 | @table[pattern[i]] = pattern.size - 1 - i 15 | end 16 | end 17 | 18 | ## 19 | # Given a mismatch character belonging to the search string, returns 20 | # the offset to be used when sliding the pattern towards the right. 21 | 22 | def slide_offset(mismatch_char) 23 | table.fetch(mismatch_char, pattern.size) 24 | end 25 | end 26 | 27 | ## 28 | # Returns the first starting index of the given pattern's occurrence (as a substring) 29 | # in the provided search string if a match is found, -1 otherwise. 30 | 31 | def first_match_index(search_string, pattern) 32 | matches = matches_indices(search_string, pattern, true) 33 | matches.empty? ? -1 : matches[0] 34 | end 35 | 36 | ## 37 | # Returns the list of starting indices of the given pattern's occurrences (as a substring) 38 | # in the provided search string. 39 | # If no match is found, an empty list is returned. 40 | # If `stop_at_first_match` is provided as `true`, the returned list will contain at most one element, 41 | # being the leftmost encountered match in the search string. 42 | 43 | def matches_indices(search_string, pattern, stop_at_first_match=false) 44 | table = BadMatchTable.new(pattern) 45 | i = pattern.size - 1 46 | indices = [] 47 | while i < search_string.size 48 | for j in 0...pattern.size 49 | if search_string[i-j] != pattern[pattern.size-1-j] 50 | i += table.slide_offset(search_string[i-j]) 51 | break 52 | elsif j == pattern.size-1 53 | indices.append(i-j) 54 | return indices if stop_at_first_match 55 | i += 1 56 | end 57 | end 58 | end 59 | indices 60 | end 61 | -------------------------------------------------------------------------------- /strings/boyer_moore_horspool_search_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require_relative 'boyer_moore_horspool_search' 3 | 4 | class TestBoyerMooreHorspoolSearch < Minitest::Test 5 | def test_first_match_returns_negative_index_if_no_match 6 | assert first_match_index('abcdefghijk', 'defz') < 0 7 | end 8 | 9 | def test_first_match_returns_first_match_index 10 | assert first_match_index('abcdefghijkghilmno', 'ghi') == 6 11 | end 12 | 13 | def test_match_indices_returns_empty_list_if_no_match 14 | assert matches_indices('abcdefghijk', 'defz').empty? 15 | end 16 | 17 | def test_match_indices_returns_list_of_match_indices 18 | assert matches_indices('abcdefghijkghilmno', 'ghi') == [6, 11] 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /strings/hamming_distance.rb: -------------------------------------------------------------------------------- 1 | # https://en.wikipedia.org/wiki/Hamming_distance 2 | 3 | def hamming_distance(str1, str2) 4 | abort 'Strings must be of the same length' unless str1.length == str2.length 5 | 6 | str1.chars.zip(str2.chars).sum { |chr1, chr2| chr1 == chr2 ? 0 : 1 } 7 | end 8 | 9 | if $0 == __FILE__ 10 | # Valid inputs 11 | puts hamming_distance 'ruby', 'rust' 12 | # => 2 13 | puts hamming_distance 'karolin', 'kathrin' 14 | # => 3 15 | puts hamming_distance 'kathrin', 'kerstin' 16 | # => 4 17 | puts hamming_distance '0000', '1111' 18 | # => 4 19 | 20 | # Invalid inputs 21 | puts hamming_distance 'ruby', 'foobar' 22 | # => Strings must be of the same length 23 | end 24 | -------------------------------------------------------------------------------- /strings/max_k_most_frequent_words.rb: -------------------------------------------------------------------------------- 1 | require_relative '../data_structures/heaps/max_heap' 2 | 3 | ## 4 | # This class represents a word count information 5 | # (i.e. how many occurrences for a word). 6 | 7 | class WordCount 8 | include Comparable 9 | 10 | attr_reader :word 11 | attr_reader :occurrences 12 | 13 | def <=>(other) 14 | occurrences <=> other.occurrences 15 | end 16 | 17 | def initialize(word, occurrences) 18 | @word = word 19 | @occurrences = occurrences 20 | end 21 | end 22 | 23 | ## 24 | # Returns the `k` most frequently occurring words, in non-increasing order of occurrence. 25 | # In this context, a word is defined as an element in the provided list. 26 | # 27 | # In case `k` is greater than the number of distinct words, a value of `k` equal 28 | # to the number of distinct words will be considered, instead. 29 | 30 | def max_k_most_frequent_words(words, k) 31 | count_by_word = words.tally 32 | heap = MaxHeap.new(count_by_word.map { |w, c| WordCount.new(w, c) }) 33 | most_frequent_words = [] 34 | [k, count_by_word.size].min.times { most_frequent_words.append(heap.extract_max.word) } 35 | most_frequent_words 36 | end -------------------------------------------------------------------------------- /strings/max_k_most_frequent_words_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require_relative 'max_k_most_frequent_words' 3 | 4 | class TestMaxKMostFrequentWords < Minitest::Test 5 | def test_top_3_frequent_words 6 | assert max_k_most_frequent_words(['a', 'b', 'c', 'a', 'c', 'c'], 3) == ['c', 'a', 'b'] 7 | end 8 | 9 | def test_top_2_frequent_words 10 | assert max_k_most_frequent_words(['a', 'b', 'c', 'a', 'c', 'c'], 2) == ['c', 'a'] 11 | end 12 | 13 | def test_top_frequent_word 14 | assert max_k_most_frequent_words(['a', 'b', 'c', 'a', 'c', 'c'], 1) == ['c'] 15 | end 16 | 17 | def test_no_frequent_word_given_zero_k 18 | assert max_k_most_frequent_words(['a', 'b', 'c', 'a', 'c', 'c'], 0) == [] 19 | end 20 | 21 | def test_no_frequent_word_given_empty_word_list 22 | assert max_k_most_frequent_words([], 1) == [] 23 | end 24 | 25 | def test_all_frequent_words_given_k_too_large 26 | assert max_k_most_frequent_words(['a', 'a'], 2) == ['a'] 27 | end 28 | end 29 | --------------------------------------------------------------------------------