├── 0-Start-Here └── README.md ├── README.md ├── list_problems_and_solutions.rb ├── partners ├── SiteGround.png └── coderunners.png ├── week01 ├── 1-Warmup-Problems │ ├── README.md │ ├── good_solutions.rb │ └── procedural_solutions.rb ├── 2-List-and-Hashes │ ├── README.md │ └── solution.rb ├── 3-I-Know-Everything │ ├── README.md │ └── nokia.jpg ├── README.md └── hello.rb ├── week02 ├── 1-Points │ ├── README.md │ └── plane.rb ├── 2-Vector │ └── README.md ├── 3-Resolve-with-each │ └── README.md ├── README.md ├── classes.rb └── position.rb ├── week03 ├── 1-fmi-tribute │ ├── .rubocop.yml │ ├── Gemfile │ ├── Gemfile.lock │ ├── README.md │ ├── Rakefile │ ├── patch_array.rb │ └── patch_array_test.rb ├── 2-Patch-Hash │ ├── README.md │ └── solution.rb ├── 3-My-Enumerable │ ├── Gemfile │ ├── Gemfile.lock │ ├── README.md │ ├── Rakefile │ ├── solution.rb │ └── solution_test.rb ├── 4-FMI-Tribute-Number-Set │ └── README.md ├── README.md ├── block_magic.rb └── collection.rb ├── week04 ├── 1-Game-Of-Life │ ├── .rubocop.yml │ ├── Gemfile │ ├── README.md │ ├── Rakefile │ ├── game_of_life.rb │ └── game_of_life_test.rb ├── 2-Polynoms-And-Derivates │ ├── README.md │ ├── solution.rb │ ├── solution_test.rb │ └── start.rb ├── README.md ├── private.rb ├── protected.rb ├── symbol.rb └── to_proc.rb ├── week05 ├── 1-Shopping-Cart │ ├── README.md │ └── shopping_cart_test.rb └── README.md ├── week06 ├── 1-Panda-Social-Network │ ├── README.md │ ├── group.rb │ ├── groups │ └── students ├── README.md ├── bfs.rb └── git.md ├── week07 └── 1-Roman-Numerals │ └── README.md ├── week08 ├── 2-meta │ ├── .rubocop.yml │ ├── Gemfile │ ├── README.md │ ├── Rakefile │ ├── solution.rb │ └── solution_test.rb └── 3-active-support │ ├── .rubocop.yml │ ├── Gemfile │ ├── README.md │ ├── Rakefile │ ├── solution.rb │ └── solution_test.rb ├── week09 ├── 1-Micro-Blog │ ├── .rubocop.yml │ ├── Gemfile │ ├── Gemfile.lock │ └── README.md ├── 1-micro-blog │ └── README.md └── README.md ├── week10 └── 1-online-store │ └── README.md ├── week11 └── 1-course-management │ └── README.md ├── week12 └── 1-authentication │ └── README.md ├── week13 └── 1-semantic-tweets │ └── README.md └── week14 └── 1-testrospective ├── README.md ├── meta.rb ├── meta_test.rb └── test.rb /0-Start-Here/README.md: -------------------------------------------------------------------------------- 1 | # Course Starting Pack 2 | 3 | In order to be prepared for the course & start solving problems from day 1, you should: 4 | 5 | * Install a text editor for start. You can pick [Sublime 3](http://www.sublimetext.com/3) or [Atom](https://atom.io/) 6 | * [Read the entire about page from the official Ruby lang website](https://www.ruby-lang.org/en/about/) 7 | * If you have knowledge in other languages, you can read [Ruby From Other Languages section](https://www.ruby-lang.org/en/documentation/ruby-from-other-languages/) 8 | * [Try Ruby!](http://tryruby.org/) - This is an interactive tutorial to get you started with the basic language constructs. Get a feel of Ruby before the first excercise in HackBulgaria! 9 | 10 | ## Quick Start With Some Programming Concepts 11 | 12 | * [What is a function in more mathematical terms by Khan Academy](https://www.khanacademy.org/math/algebra/algebra-functions/evaluating-functions/v/what-is-a-function) 13 | * [A more formal understanding of functions by Khan Academy](https://www.khanacademy.org/math/linear-algebra/matrix_transformations/linear-transformations/v/a-more-formal-understanding-of-functions) 14 | * [What is a function parameter?](https://en.wikipedia.org/wiki/Parameter_(computer_programming)) 15 | * [Algorithms and Asymptotic Notation by Khan Academy](https://www.khanacademy.org/computing/computer-science/algorithms/asymptotic-notation/a/asymptotic-notation) 16 | 17 | ## Install Ruby 18 | 19 | Our recommended setting for installing Ruby is to use rbenv + ruby-build: 20 | 21 | You are going to need git. In order to install git, run the following command: 22 | 23 | ``` 24 | $ sudo apt-get install git 25 | ``` 26 | 27 | * [Guide for how to install rbenv](https://github.com/sstephenson/rbenv#installation) 28 | * [Guide for how to install ruby-build](https://github.com/sstephenson/ruby-build#installation) 29 | 30 | After you have `rbenv` and `ruby-build`, you can do the following: 31 | 32 | ``` 33 | $ rbenv install 2.2.3 34 | $ rbenv global 2.2.3 35 | $ ruby --version 36 | ``` 37 | 38 | You should see Ruby's version 2.2.3 39 | 40 | If you happen to be running on Windows, you have two options: 41 | 42 | 1. Install some Linux distribution. [Ubuntu is good for beginners](http://www.ubuntu.com/) 43 | 2. Use [ruby installer for Windows](http://rubyinstaller.org/). And after this, switch to Linux. 44 | 45 | ## Installing Linux 46 | 47 | Before moving on and installing any Linux, **PLEASE BACKUP EVERYTHING THAT IS IMPORTANT TO YOU**. 48 | 49 | Don't leave memories only on the hard drive of your PC / laptop. It will eventually fail and you will eventually lose everything that is not backed up. 50 | 51 | Our suggestion is, use one or more of the following: 52 | 53 | * [pCloud](https://www.pcloud.com/) 54 | * [Dropbox](https://www.dropbox.com/) 55 | * [ownCloud](https://owncloud.org/) 56 | * Other similar cloud services 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Programming101-Ruby 2 | 3 | The repository for Programming 101 with Ruby. 4 | 5 | ## Partners 6 | 7 | This course is happening, thanks to: 8 | 9 | [![](partners/coderunners.png)](https://code-runners.com/about) 10 | 11 | ## Hosting Partners 12 | 13 | [![SiteGround](partners/SiteGround.png)](https://www.siteground.com/) 14 | 15 | ## Problems & Solutions 16 | 17 | | Week | Problem | Solution | 18 | | ---- |:-------:| --------:| 19 | | week01 | [1-Warmup-Problems](week01/1-Warmup-Problems) | [procedural_solutions.rb](week01/1-Warmup-Problems/procedural_solutions.rb), [good_solutions.rb](week01/1-Warmup-Problems/good_solutions.rb) | 20 | | week01 | [2-List-and-Hashes](week01/2-List-and-Hashes) | [solutions.rb](week01/2-List-and-Hashes/solutions.rb) | 21 | | week01 | [3-I-Know-Everything](week01/3-I-Know-Everything) | None | 22 | | week02 | [1-Points](week02/1-Points) | None | 23 | | week02 | [2-Vector](week02/2-Vector) | None | 24 | | week02 | [3-Resolve-with-each](week02/3-Resolve-with-each) | None | 25 | | week03 | [1-fmi-tribute](week03/1-fmi-tribute) | None | 26 | | week03 | [2-Patch-Hash](week03/2-Patch-Hash) | [solution.rb](week03/2-Patch-Hash/solution.rb) | 27 | | week03 | [3-My-Enumerable](week03/3-My-Enumerable) | [solution.rb](week03/3-My-Enumerable/solution.rb), [solution_test.rb](week03/3-My-Enumerable/solution_test.rb) | 28 | | week03 | [4-FMI-Tribute-Number-Set](week03/4-FMI-Tribute-Number-Set) | None | 29 | | week04 | [1-Game-Of-Life](week04/1-Game-Of-Life) | None | 30 | | week04 | [2-Polynoms-And-Derivates](week04/2-Polynoms-And-Derivates) | [solution.rb](week04/2-Polynoms-And-Derivates/solution.rb), [solution_test.rb](week04/2-Polynoms-And-Derivates/solution_test.rb) | 31 | 32 | ## Course Program 33 | 34 | This is a detailed program about the course, separated in weeks 35 | 36 | ### Week 1 37 | 38 | * Ruby introduction. 39 | * Rbenv/Ruby Installer and Git setup. 40 | * Introduction to the basic Ruby types, syntax & language structures. 41 | * Solving problems with Ruby. 42 | 43 | ### Week 2 44 | 45 | * In depth look of Array, Hash, String, Symbol, Numeric. 46 | * Introducing Ruby blocks. 47 | * Working with Enumerable and all it's magic. 48 | 49 | ### Week 3 50 | 51 | * OOP Introduction. Classes, Modules and Constants. 52 | * Freedom patching. Opening classes & adding functionality. 53 | * Method resolution lookup and ancestor chain. 54 | * Implementing our own Enumerable objects. 55 | 56 | ### Week 4 57 | 58 | * Keyword arguments & parallel assingments. 59 | * Second take on Ruby OOP model 60 | * Sending messages arround and responding to them. Working with method aliases. 61 | * Understanding Duck Typing & SOLID principles. 62 | * Solving OOP problems 63 | 64 | ### Week 5 65 | 66 | * Enumerators in Ruby. 67 | * Lambda vs Proc semantics. 68 | * * require vs load. 69 | * Working IO in Ruby. Dealing with Files. 70 | * Dealing with exceptions. 71 | 72 | ### Week 6 73 | 74 | * Kernel & Module methods. 75 | * Constants & Scope 76 | * Making namespaces in Ruby 77 | 78 | ### Week 7 79 | 80 | * Singleton classes. 81 | * Dynamic method definition. 82 | * Don't be evil: class_eval and instance_eval. 83 | * Constants, instance variables API. 84 | 85 | ### Week 8 86 | 87 | * Threading or: How I Learned to Stop Worrying and Learned to Love the GIL. 88 | * JRuby and Rubinius. 89 | * Threading locals. Fibers. 90 | * Basic networking. 91 | 92 | ### Week 9 93 | 94 | * RubyGems: The structure of a gem. Gem specifications. Rake. Bundler. 95 | * Introspection & Meta programming in Ruby. 96 | 97 | ### Week 10 98 | 99 | * Looking at the bigger picture & solving problems using everything we know up to know. 100 | * Working in teams 101 | 102 | ### Week 11 103 | 104 | * Intro to HTTP. The Tricky Parts: Cookies, Sessions. 105 | * Intro to Sinatra. 106 | 107 | ### Week 12 108 | 109 | * Intro to SQL. Transactions, Primary Keys, Foreign Keys, Indexes. 110 | * The Architecture of a Web app. 111 | 112 | ### Week 13, 14, 15, 16 - The Rails part 113 | 114 | * Intro to Rails: Convention over Configuration. 115 | * Overview of the Builtin Frameworks, Scaffolding. 116 | * Basic Routing and Controller Rendering. 117 | * Views, Layouts and Partials Structure. 118 | * Controller Rendering and Redirection. Basic Authentication. Rendering forms. Mailers, Intro to ActiveJob. 119 | * Intro to ActiveRecord. Intro to schema.rb and Migrations. Validations and Callbacks. How not to abuse the callbacks. 120 | * Basic Query Interface. Intro to associations. Associations in depth. Many-to-many, .through, Automatic Reverses. 121 | * Transactions, Single table inheritence. Has Secure Password. Musings on Small Models. Sandi Metz 5 Rules. Thoughts on OOP. 122 | * Routes inside out. Introduction to engines. Middleware, Rack, Railties, Initializers and Configuration. 123 | * The Rails Eco System. Third party gems. Responders, Timecop, RSpec, Capybara. Active Model, Internationalization. 124 | * Constant Autoloading explained. The Asset Pipeline. Rake Tasks, Command Line Tools and Debugging Rails applications. 125 | -------------------------------------------------------------------------------- /list_problems_and_solutions.rb: -------------------------------------------------------------------------------- 1 | class Weeks 2 | def self.list_weeks 3 | Dir.glob('week*/').sort.map do |week_name| 4 | WeekFolder.new week_name.chop # Trailing / 5 | end 6 | end 7 | end 8 | 9 | class WeekFolder 10 | attr_reader :name, :problems 11 | 12 | def initialize(name) 13 | @name = name 14 | @problems = [] 15 | 16 | gather_problems 17 | end 18 | 19 | def to_s 20 | @name 21 | end 22 | 23 | def inspect 24 | to_s 25 | end 26 | 27 | private 28 | 29 | def gather_problems 30 | @problems = Dir.glob("#{@name}/[0-9]*").sort.map do |problem| 31 | Problem.new problem 32 | end 33 | end 34 | end 35 | 36 | class Problem 37 | attr_reader :name, :path 38 | 39 | def initialize(path) 40 | @name = File.basename(path) 41 | @path = path 42 | @solutions = [] 43 | 44 | solutions = Dir.glob("#{@path}/*solution*") 45 | @solutions = solutions unless solutions == [] 46 | end 47 | 48 | def solutions 49 | return "None" unless solution? 50 | @solutions 51 | end 52 | 53 | def solution? 54 | @solutions.length > 0 55 | end 56 | 57 | def to_s 58 | @name 59 | end 60 | end 61 | 62 | TABLE_HEADER = %( 63 | | Week | Problem | Solution | 64 | | ---- |:-------:| --------:| 65 | ) 66 | 67 | table = TABLE_HEADER 68 | 69 | Weeks.list_weeks.each do |week| 70 | week.problems.each do |problem| 71 | solutions = problem.solutions 72 | 73 | if solutions.respond_to?(:each) 74 | solutions = solutions.map do |solution| 75 | "[#{File.basename(solution)}](#{solution})" 76 | end.join(', ') 77 | end 78 | 79 | row = "| #{week} | [#{problem}](#{problem.path}) | #{solutions} |\n" 80 | table += row 81 | end 82 | end 83 | 84 | puts table 85 | -------------------------------------------------------------------------------- /partners/SiteGround.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming101-Ruby/7bd525eca6c89f3fd4aad1948dbdb20ede0490cf/partners/SiteGround.png -------------------------------------------------------------------------------- /partners/coderunners.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming101-Ruby/7bd525eca6c89f3fd4aad1948dbdb20ede0490cf/partners/coderunners.png -------------------------------------------------------------------------------- /week01/1-Warmup-Problems/README.md: -------------------------------------------------------------------------------- 1 | # Warmup Ruby Problems 2 | 3 | You can find solutions here: 4 | 5 | * In [procedural_solutions.rb](procedural_solutions.rb) are our first attempts at writing Ruby. Almost everything is written with `while` and `if` 6 | * In [good_solutions.rb](good_solutions.rb) we do a rekate on everything with our knowledge of `Enumerable` 7 | 8 | In a file called `warmup.rb`, solve the following problems: 9 | 10 | ## Factorial 11 | 12 | Implement the factorial function `fact(n)`. 13 | 14 | * Implement it using a simple loop 15 | * Implement it using recursion 16 | 17 | ## Lucas series 18 | 19 | Because Fibonacci is way too trivial, implement the following functions that work with [Lucas series](https://en.wikipedia.org/wiki/Lucas_number): 20 | 21 | * `nth_lucas(n)` -> returns the nth Lucas number 22 | * `first_lucas(n)` -> returns a list of the first `n` Lucas numbers 23 | 24 | Quick hint: 25 | 26 | ``` 27 | L(0) = 2 28 | L(1) = 1 29 | L(n) = L(n-1) + L(n-2) 30 | ``` 31 | 32 | ## Working with digits 33 | 34 | Those are classic problems for using module division: 35 | 36 | * Given an integer `n`, return the number of digits in `n` -> `count_digits(n)` 37 | * Given an integer `n`, return the sum of all digits in `n` -> `sum_digits(n)` 38 | * `factorial_digits(n)` -> for example, if we have `145`, we need to calculate `1! + 4! + 5!` 39 | 40 | ## Fibonacci number 41 | 42 | Implement a function, called `fib_number(n)`, which takes an integer n and returns a number, which is formed by concatenating the first n Fibonacci numbers. 43 | 44 | Examples: 45 | 46 | ```ruby 47 | fib_number 3 == 112 48 | fib_number 10 == 11235813213455 49 | ``` 50 | 51 | ## Hack Numbers 52 | 53 | A hack number is an integer, that matches the following criteria: 54 | 55 | * The number, represented in binary, is a palindrome 56 | * The number, represented in binary, has an odd number of 1's in it 57 | 58 | Example of hack numbers: 59 | 60 | * 1 is `1` in binary 61 | * 7 is `111` in binary 62 | * 7919 is `1111011101111` in binary 63 | 64 | Implement the following functions: 65 | 66 | * `hack?(n)` -> checks if `n` is a hack number 67 | * `next_hack(n)` -> returns the next hack number, that is bigger than `n` 68 | 69 | Few examples: 70 | 71 | ```ruby 72 | hack? 1 == true 73 | next_hack 0 == 1 74 | 75 | hack? 21 == true 76 | next_hack 10 == 21 77 | 78 | hack? 8191 == true 79 | next_hack 8031 == 8191 80 | ``` 81 | 82 | ## Vowels in a string 83 | 84 | Implement a function, called `count_vowels(str)`, which returns the count of all vowels in the string `str`. 85 | 86 | __Count uppercase vowels aswell!__ 87 | 88 | The English vowels are `aeiouy`. 89 | 90 | **Examples:** 91 | 92 | ```ruby 93 | count_vowels "Python" == 2 94 | count_vowels "Theistareykjarbunga" == 8 95 | count_vowels "grrrrgh!" == 0 96 | count_vowels "Github is the second best thing that happend to programmers, after the keyboard!" == 22 97 | count_vowels "A nice day to code!" == 8 98 | ``` 99 | 100 | ## Consonants in a string 101 | 102 | Implement a function, called `count_consonants str)`, which returns the count of all consonants in the string `str`. 103 | 104 | __Count uppercase consonants as well!__ 105 | 106 | The English consonants are `bcdfghjklmnpqrstvwxz`. 107 | 108 | **Examples:** 109 | 110 | ```ruby 111 | count_consonants "Python" == 4 112 | count_consonants "Theistareykjarbunga" == 11 113 | count_consonants "grrrrgh!" == 7 114 | count_consonants "Github is the second best thing that happend to programmers, after the keyboard!" == 44 115 | count_consonants "A nice day to code!" == 6 116 | ``` 117 | 118 | ## Palindrome Score 119 | 120 | We denote the "Palindrome score" of an integer by the following function: 121 | 122 | * `P(n) = 1`, if `n` is palindrome 123 | * `P(n) = 1 + P(s)` where `s` is the sum of `n` and the `reverse of n` 124 | 125 | Implement a function, called `p_score(n)`, which finds the palindrome score for n. 126 | 127 | Lets see two examples: 128 | 129 | * `p_score(121) = 1`, because `121` is a palindrome. 130 | * `p_score(48) = 3`, because: 131 | 132 | 1. `P(48) = 1 + P(48 + 84) = 132` 133 | 2. `P(132) = 1 + 1 + P(132 + 321 = 363)` 134 | 3. `P(363) = 1`. 135 | 4. When we return from the recursion, we get 3. 136 | 137 | **Examples:** 138 | 139 | ```ruby 140 | p_score 121 == 1 141 | p_score 48 == 3 142 | p_score 198 == 6 143 | ``` 144 | 145 | ## Object palindomes 146 | 147 | A palindrome is a word, phrase, number, or other sequence of characters which reads the same backward or forward. 148 | 149 | Implement the following function: `palindrome?(obj)` that checks if the given obj is palindrome. 150 | 151 | **Examples:** 152 | 153 | ```ruby 154 | palindrome? 121 == true 155 | palindrome? 123 == false 156 | palindrome? 'kapak' == true 157 | palindrome? 'baba' == false 158 | palindrome? 'azobi4amma4iboza' == true 159 | ``` 160 | 161 | 162 | ## Prime numbers 163 | 164 | The usual deal. Implement the following functions: 165 | 166 | * Check if number is prime -> `prime?(n)` 167 | * List the first `n` prime numbers -> `first_primes(n)` 168 | * **OPTIONAL**: List the first `n` prime numbers, using the [Sieve of Eratosthenes](https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes) 169 | 170 | ## Sum all numbers in a given string 171 | 172 | You are given a string, where there can be numbers. Return the sum of all numbers in that string (not digits, numbers) 173 | 174 | Examples: 175 | 176 | ```ruby 177 | sum_of_numbers_in_string "1111" == 1111 178 | sum_of_numbers_in_string "1abc33xyz22" == 1 + 33 + 22 = 56 179 | sum_of_numbers_in_string "abcd" == 0 180 | ``` 181 | 182 | ## Anagrams 183 | 184 | Here is the explanation of what an Anagram is - 185 | 186 | Implemenent the following functions: 187 | 188 | * `anagrams?(a, b)` - returns true, if the string `a` is an anagram of `b` 189 | 190 | ## Is number balanced? 191 | 192 | A number is called balanced, if we take the middle of it and the sums of the left and right parts are equal. 193 | 194 | For example, the number `1238033` is balanced, because it's left part is `123` and right part is `033`. 195 | 196 | We have : `1 + 2 + 3 = 0 + 3 + 3 = 6`. 197 | 198 | A number with only one digit is always balanced! 199 | 200 | Implement the function `balanced?(n)` that checks if `n` is balanced. 201 | 202 | **Few examples:** 203 | 204 | ```ruby 205 | balanced?(9) == True 206 | balanced?(11) == True 207 | balanced?(13) == False 208 | balanced?(121) == True 209 | balanced?(4518) == True 210 | balanced?(28471) == False 211 | balanced?(1238033) == True 212 | ``` 213 | 214 | ## Zero Insertion 215 | 216 | Given an integer, implement the function `zero_insert(n)`, which returns a new integer, constructed by the following algorithm: 217 | 218 | * If two neighboring digits are the same (like `55`), insert a 0 between them (`505`) 219 | * Also, if we add two neighboring digits and take their module by 10 (`% 10`) and the result is 0 - add 0 between them. 220 | 221 | For example, if we have the number `116457`, result will be: `10160457`: 222 | 223 | * 1 and 1 are the same, so we insert 0 between them 224 | * `6 + 4 % 10 = 0`, so we insert 0 between them. 225 | 226 | **Few examples:** 227 | 228 | ```ruby 229 | zero_insert(116457) == 10160457 230 | zero_insert(55555555) == 505050505050505 231 | zero_insert(1) == 1 232 | zero_insert(6446) == 6040406 233 | ``` 234 | -------------------------------------------------------------------------------- /week01/1-Warmup-Problems/good_solutions.rb: -------------------------------------------------------------------------------- 1 | def fact(n) 2 | # We are using upto method of Integer - http://ruby-doc.org/core-2.2.0/Integer.html#method-i-upto 3 | # After this, we are reducing with * 4 | # This will find the products of all digits from 1 to n 5 | # a stands for accumulated value so far 6 | # e stands for the next value of the upto 7 | 1.upto(n).reduce { |a, e| a * e } 8 | end 9 | 10 | def nth_lucas(n) 11 | a, b = 2, 1 12 | 13 | # Again, we use the upto method to simulate a for cycle 14 | # going from 2 to n 15 | # Since we do not need the block variable, we do not write |i| 16 | 2.upto(n) do 17 | a, b = b, a + b 18 | end 19 | 20 | a 21 | end 22 | 23 | def first_lucas(n) 24 | # Not the best solution, 25 | # Because we are going to recalculate every nth lucas 26 | 1.upto(n).map { |index| nth_lucas index } 27 | end 28 | 29 | def to_digits(n) 30 | n.to_s.chars.map { |d| d.to_i } 31 | end 32 | 33 | def count_digits(n) 34 | to_digits(n).length 35 | end 36 | 37 | def sum_digits(n) 38 | to_digits(n).reduce { |a, e| a + e } 39 | end 40 | 41 | def factorial_digits(n) 42 | to_digits(n) 43 | .map { |d| factorial d } 44 | .reduce { |a, e| a + e } 45 | end 46 | 47 | def first_fibs(n) 48 | a, b = 1, 1 49 | result = [a] 50 | 51 | 2.upto(n) do 52 | a, b = b, a + b 53 | result << a 54 | end 55 | 56 | result 57 | end 58 | 59 | def fib_number(n) 60 | # We do the following steps: 61 | # 1. We create a list of the first n numbers -> [1, 1, 2] 62 | # 2. After this, we turn it to a string by joining it with '' -> '112' 63 | # 3. Finally, we turn the string to an integer -> 112 64 | first_fibs(n).join('').to_i 65 | end 66 | 67 | def hack?(n) 68 | # We turn n into a binary string by giving the second argumen to to_s 69 | bn = n.to_s 2 70 | bn.count('1').odd? && bn.reverse == bn 71 | end 72 | 73 | def next_hack(n) 74 | n = n.next 75 | n = n.next until hack? n 76 | n 77 | end 78 | 79 | def count_vowels(str) 80 | vowels = 'aeiouy'.chars 81 | 82 | str.downcase 83 | .chars 84 | .select { |ch| vowels.include? ch } 85 | .length 86 | end 87 | 88 | def count_consonants(str) 89 | consonants = 'bcdfghjklmnpqrstvwxz'.chars 90 | 91 | str.downcase 92 | .chars 93 | .select { |ch| consonants.include? ch } 94 | .length 95 | end 96 | 97 | def palindrome?(object) 98 | object.to_s.reverse == object.to_s 99 | end 100 | 101 | # This is a great recursion problem 102 | # We just make a Ruby code around the definition for p_score 103 | def p_score(n) 104 | return 1 if palindrome? n 105 | 106 | 1 + p_score(n + n.to_s.reverse.to_i) 107 | end 108 | 109 | def largest_palindrome(n) 110 | n -= 1 111 | n -= 1 until palindrome? n 112 | n 113 | end 114 | 115 | def prime?(n) 116 | # Waiting for PR 117 | end 118 | 119 | def list_first_primes(n) 120 | # Waiting for PR 121 | end 122 | 123 | def sieve(n) 124 | # Waiting for PR 125 | end 126 | 127 | def char_is_positive_digit?(n) 128 | # http://batsov.com/articles/2013/07/02/the-elements-of-style-in-ruby-number-5-readability-of-long-numeric-literals/ 129 | 123_456_789_0.to_s.chars.include? n 130 | end 131 | 132 | def sum_of_numbers_in_string(str) 133 | result = 0 134 | chars = str.chars 135 | 136 | while chars.length > 0 137 | ns = chars.take_while { |ch| char_is_positive_digit? ch } 138 | result += ns.join('').to_i 139 | 140 | if ns.length == 0 141 | chars = chars.drop_while { |ch| !char_is_positive_digit? ch } 142 | else 143 | chars = chars.drop(ns.length) 144 | end 145 | end 146 | 147 | result 148 | end 149 | 150 | def anagrams?(a, b) 151 | a.chars.sort == b.chars.sort 152 | end 153 | 154 | def balanced?(n) 155 | n = n.to_s 156 | mid = n.length / 2 157 | 158 | left_part = n.slice(0, mid) 159 | right_part = n.slice(mid + n.length % 2, n.length) 160 | 161 | sum_digits(left_part.to_i) == sum_digits(right_part.to_i) 162 | end 163 | 164 | def zero_insert(n) 165 | result = '' 166 | index, n = 0, n.to_s 167 | 168 | while index < n.length - 1 169 | a, b = n[index].to_i, n[index + 1].to_i 170 | 171 | result += a.to_s 172 | result += '0' if a == b || (a + b) % 10 == 0 173 | 174 | index += 1 175 | end 176 | 177 | result += n[index] 178 | result 179 | end 180 | -------------------------------------------------------------------------------- /week01/1-Warmup-Problems/procedural_solutions.rb: -------------------------------------------------------------------------------- 1 | def fact(n) 2 | if n == 0 3 | 1 4 | else 5 | n * fact(n - 1) 6 | end 7 | end 8 | 9 | def chars(str) 10 | result = [] 11 | 12 | i = 0, n = str.length 13 | 14 | while i < n do 15 | result.push(str[i]) 16 | i = i.next 17 | end 18 | 19 | result 20 | end 21 | 22 | def anagrams?(a, b) 23 | a.chars.sort == b.chars.sort 24 | end 25 | 26 | def hack?(n) 27 | n = n.to_s 2 28 | n.reverse == n and n.count('1').odd? 29 | end 30 | 31 | def next_hack(n) 32 | n = n.next 33 | 34 | until hack? n do 35 | n = n.next 36 | end 37 | 38 | n 39 | end 40 | 41 | def sum_digits(n) 42 | digits = n.to_s.chars 43 | result, index = 0, 0 44 | 45 | while index < digits.length do 46 | result += digits[index].to_i 47 | index += 1 48 | end 49 | 50 | result 51 | end 52 | 53 | def balanced?(n) 54 | n = n.to_s 55 | mid = n.length / 2 56 | 57 | left_part = n.slice(0, mid) 58 | right_part = n.slice(mid + n.length % 2, n.length) 59 | 60 | sum_digits(left_part.to_i) == 61 | sum_digits(right_part.to_i) 62 | end 63 | 64 | def zero_insert(n) 65 | result = "" 66 | index, n = 0, n.to_s 67 | 68 | while index < n.length - 1 69 | a, b = n[index].to_i, n[index + 1].to_i 70 | 71 | result += a.to_s 72 | result += '0' if a == b or (a + b) % 10 == 0 73 | 74 | index += 1 75 | end 76 | 77 | result += n[index] 78 | result 79 | end 80 | 81 | def number_to_digits(n) 82 | result = [] 83 | 84 | while n != 0 85 | result.push(n % 10) 86 | n = n / 10 87 | end 88 | 89 | result.reverse 90 | end 91 | 92 | def number_to_digits_r(n) 93 | return [n] if n < 10 94 | number_to_digits_r(n / 10).push(n % 10) 95 | end 96 | 97 | def digits_to_number(digits) 98 | index, result = 0, 0 99 | 100 | while index < digits.length 101 | result = result * 10 + digits[index] 102 | index = index.next 103 | end 104 | 105 | result 106 | end 107 | 108 | def greyscale_histogram(image) 109 | histogram = Array.new 256, 0 110 | 111 | row_index, col_index = 0, 0 112 | 113 | while row_index < image.length 114 | col_index = 0 115 | 116 | while col_index < image[row_index].length 117 | pixel_value = image[row_index][col_index] 118 | histogram[pixel_value] += 1 119 | col_index += 1 120 | end 121 | 122 | row_index += 1 123 | end 124 | 125 | histogram 126 | end 127 | 128 | image = [[0, 0, 0, 0, 0], 129 | [111, 255, 0, 0, 111], 130 | [100, 100, 100, 100, 100], 131 | [1, 1, 1, 1, 1], 132 | [3, 3, 5, 6, 9]] 133 | 134 | result = greyscale_histogram image 135 | 136 | p result[0] 137 | p result[1] 138 | p result[3] 139 | p result[5] 140 | p result[6] 141 | p result[9] 142 | p result[100] 143 | p result[111] 144 | p result[255] 145 | -------------------------------------------------------------------------------- /week01/2-List-and-Hashes/README.md: -------------------------------------------------------------------------------- 1 | # Warmup problems with List and Hashes 2 | 3 | ## Transforming integer into a list of digits and vice versa 4 | 5 | Implement the Ruby function that takes an integer and returns a list of digits: 6 | 7 | ```ruby 8 | number_to_digits 123 == [1, 2, 3] 9 | number_to_digits 8087 = [8, 0, 8, 7] 10 | ``` 11 | 12 | After you are ready, implement the reverse of that function: 13 | 14 | ```ruby 15 | digits_to_number [1, 2, 3] == 123 16 | digits_to_number [8, 0, 8, 7] == 8087 17 | ``` 18 | 19 | ## Grayscale Image Histogram 20 | 21 | Implement a function called `grayslace_histogram(image)` that takes a matrix (list of lists) of an image and returns the histogram distribution of each pixel. 22 | 23 | Each of the matrix's values will be between 0 and 255. 24 | 25 | Return a list result, which is a histogram of image => the value of `result[i]` should be the ammount of times `i` is in the matrix image. 26 | 27 | 28 | For example, lets have the following 5x5 image 29 | 30 | ``` 31 | 0 0 0 0 0 32 | 111 255 0 0 111 33 | 100 100 100 100 100 34 | 1 1 1 1 1 35 | 3 3 5 6 9 36 | ``` 37 | 38 | In the result, we should have the following results: 39 | 40 | ```ruby 41 | result[0] = 7 42 | result[1] = 5 43 | result[3] = 2 44 | result[5] = 1 45 | result[6] = 1 46 | result[9] = 1 47 | result[100] = 5 48 | result[111] = 2 49 | result[255] = 1 50 | ``` 51 | 52 | ## Maximal Scalar Product 53 | 54 | Implement the following function: `max_scalar_product(v1, v2)`. 55 | 56 | You are given two vectors - `v1` and `v2` 57 | 58 | A scalar product of two vectors is the following: 59 | 60 | ``` 61 | v1 = {a1, a2, ..., an} 62 | v2 = {b1, b2, ..., bn} 63 | 64 | The scalar product is written as: 65 | v1 . v2 = a1 * b1 + a2 * b2 + ... + an * bn 66 | ``` 67 | 68 | Find a permutation of `v1` and a permutation of `v2` for which their scalar product **is the largest possible** and **return that scalar product** 69 | 70 | ## Max Span 71 | 72 | Implement the following function: `max_span(numbers)` where `numbers` is a list of numbers. 73 | 74 | Consider the leftmost and righmost appearances of some value in the list. 75 | 76 | We'll say that the "span" is the number of elements between the two inclusive. A single value has a span of 1. 77 | 78 | **Returns the largest span found in the given array.** 79 | 80 | Examples: 81 | 82 | ```ruby 83 | max_span([1, 2, 1, 1, 3]) == 4 84 | max_span([1, 4, 2, 1, 4, 1, 4]) == 6 85 | max_span([1, 4, 2, 1, 4, 4, 4]) == 6 86 | ``` 87 | 88 | ## Sum Numbers in Matrix 89 | 90 | You are given a `NxM` matrix of integer numbers. 91 | 92 | Implement a function, called `sum_matrix(m)` that returns the sum of all numbers in the matrix. 93 | 94 | The matrix will be represented as nested lists in Python. 95 | 96 | ```ruby 97 | m = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] 98 | sum_matrix(m) == 45 99 | m = [[0, 0, 0], [0, 0, 0], [0, 0, 0]] 100 | sum_matrix(m) == 0 101 | m = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]] 102 | sum_matrix(m) == 55 103 | ``` 104 | 105 | ## Matrix Bombing 106 | 107 | You are givn a `NxM` matrix of integer numbers. 108 | 109 | We can drop a bomb at any place in the matrix, which has the following effect: 110 | 111 | * All of the **3 to 8 neighbours** (depending on where you hit!) of the target are reduced by the value of the target. 112 | * Numbers can be reduced only to 0 - they cannot go to negative. 113 | 114 | For example, if we have the following matrix: 115 | 116 | ``` 117 | 10 10 10 118 | 10 9 10 119 | 10 10 10 120 | ``` 121 | 122 | and we drop bomb at `9`, this will result in the following matrix: 123 | 124 | ``` 125 | 1 1 1 126 | 1 9 1 127 | 1 1 1 128 | ``` 129 | 130 | Implement a function called `matrix_bombing_plan(m)`. 131 | 132 | The function should return a dictionary where keys are positions in the matrix, represented as tuples, and values are the total sum of the elements of the matrix, after the bombing at that position. 133 | 134 | The positions are the standard indexes, starting from `(0, 0)` 135 | 136 | For example if we have the following matrix: 137 | 138 | ``` 139 | 1 2 3 140 | 4 5 6 141 | 7 8 9 142 | ``` 143 | 144 | and run the function, we will have: 145 | 146 | ```ruby 147 | {[0, 0]=> 42, 148 | [0, 1]=> 36, 149 | [0, 2]=> 37, 150 | [1, 0]=> 30, 151 | [1, 1]=> 15, 152 | [1, 2]=> 23, 153 | [2, 0]=> 29, 154 | [2, 1]=> 15, 155 | [2, 2]=> 26} 156 | ``` 157 | 158 | We can see that if we drop the bomb at `(1, 1)` or `(2, 1)`, we will do the most damage! 159 | 160 | We are going to represent one point as a list of two elements. 161 | 162 | ## Group Function 163 | 164 | We are going to implement a very helpful function, called `group`. 165 | 166 | `group` takes a list of things and returns a list of group, where each group is formed by all equal consecutive elements in the list. 167 | 168 | **For example:** 169 | 170 | ```ruby 171 | group([1, 1, 1, 2, 3, 1, 1]) == [[1, 1, 1], [2], [3], [1, 1]] 172 | group([1, 2, 1, 2, 3, 3]) == [[1], [2], [1], [2], [3, 3]] 173 | ``` 174 | 175 | ## Longest subsequence of equal consecutive elements 176 | 177 | Implement the function `max_consecutive(items)`, which takes a list of things and returns an integer - the count of elements in the longest subsequence of equal consecutive elements. 178 | 179 | For example, in the list `[1, 2, 3, 3, 3, 3, 4, 3, 3]`, the result is 4, where the longest subsequence is formed by `3, 3, 3, 3` 180 | 181 | **Test examples::** 182 | 183 | ```ruby 184 | max_consecutive([1, 2, 3, 3, 3, 3, 4, 3, 3]) == 4 185 | max_consecutive([1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 5]) == 3 186 | ``` 187 | 188 | -------------------------------------------------------------------------------- /week01/2-List-and-Hashes/solution.rb: -------------------------------------------------------------------------------- 1 | def number_to_digits(n) 2 | n.to_s.chars.map(&:to_i) 3 | end 4 | 5 | def digits_to_number(digits) 6 | digits.reduce(0) { |a, e| a * 10 + e } 7 | end 8 | 9 | def max_scalar_product(v1, v2) 10 | v1 = v1.sort 11 | v2 = v2.sort 12 | 13 | v1.each_with_index 14 | .map { |x, i| x * v2[i] } 15 | .reduce(&:+) 16 | end 17 | 18 | def sum_matrix(m) 19 | m.map { |row| row.reduce(&:+) }.reduce(&:+) 20 | end 21 | 22 | def grayscale_histogram(image) 23 | histogram = Array.new 256, 0 24 | 25 | row_index, col_index = 0, 0 26 | 27 | while row_index < image.length 28 | col_index = 0 29 | 30 | while col_index < image[row_index].length 31 | pixel_value = image[row_index][col_index] 32 | histogram[pixel_value] += 1 33 | col_index += 1 34 | end 35 | 36 | row_index += 1 37 | end 38 | 39 | histogram 40 | end 41 | 42 | def max_span(items) 43 | 44 | end 45 | 46 | def matrix_bombing_plan(m) 47 | 48 | end 49 | -------------------------------------------------------------------------------- /week01/3-I-Know-Everything/README.md: -------------------------------------------------------------------------------- 1 | # The last set of problems for those who know everything 2 | 3 | ## 100 SMS 4 | 5 | Before the smartphones, when you had to write some message, the keypads looked like that: 6 | 7 | ![Nokia 3310 Keypad](nokia.jpg) 8 | 9 | For example, on such keypad, if you want to write **Ruby**, you had to press the following sequence of numbers: 10 | 11 | ``` 12 | 7778822999 13 | ``` 14 | 15 | Each key contains some letters from the alphabet. And by pressing that key, you rotate the letters until you get to your desired one. 16 | 17 | It's time to implement some encode / decode functions for the old keypads! 18 | 19 | ### `numbers_to_message(pressedSequence)` 20 | 21 | First, implement the function that takes a list of integers - the sequence of numbers that have been pressed. The function should return the corresponding string of the message. 22 | 23 | There are a few special rules: 24 | 25 | * If you press `1`, the next letter is going to be capitalized 26 | * If you press `0`, this will insert a single white-space 27 | * If you press a number and wait for a few seconds, the special breaking number `-1` enters the sequence. This is the way to write different letters from only one keypad! 28 | 29 | Few examples: 30 | 31 | ``` 32 | numbers_to_message([2, -1, 2, 2, -1, 2, 2, 2]) = "abc" 33 | numbers_to_message([2, 2, 2, 2]) = "a" 34 | numbers_to_message([1, 4, 4, 4, 8, 8, 8, 6, 6, 6, 0, 3, 3, 0, 1, 7, 7, 7, 7, 7, 2, 6, 6, 3, 2]) 35 | = 36 | "Ivo e Panda" 37 | ``` 38 | 39 | ### `message_to_numbers(messsage)` 40 | 41 | This function takes a string - the `message` and returns the **minimal** keystrokes that you ned to write that `message` 42 | 43 | Few examples: 44 | 45 | ``` 46 | message_to_numbers("abc") = [2, -1, 2, 2, -1, 2, 2, 2] 47 | message_to_numbers("a") = [2] 48 | message_to_numbers("Ivo e panda") 49 | = 50 | [1, 4, 4, 4, 8, 8, 8, 6, 6, 6, 0, 3, 3, 0, 1, 7, 2, 6, 6, 3, 2] 51 | message_to_numbers("aabbcc") = [2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, 2, -1, 2, 2, 2] 52 | ``` 53 | 54 | ## Spam and Eggs 55 | 56 | This is a problem from the [Python 2012 course in FMI](http://2012.fmi.py-bg.net/). 57 | 58 | You can see the original problem statement here - http://2012.fmi.py-bg.net/tasks/1 59 | 60 | Implement a function, called `prepare_meal(number)` that takes an integer and returns a string, generated by the following algorithm: 61 | 62 | __Еggs:__ 63 | 64 | If there is an integer `n`, such that `3^n` divides `number` and `n` is the largest possible, 65 | the result should be a string, containing `n` times `spam`. 66 | 67 | For example: 68 | 69 | ```ruby 70 | prepare_meal(3) == 'spam' 71 | prepare_meal(27) == 'spam spam spam' 72 | prepare_meal(7) == '' 73 | ``` 74 | 75 | __Spam:__ 76 | 77 | If number is divisible by 5, add `and eggs` to the result. 78 | 79 | For example: 80 | 81 | ```ruby 82 | prepare_meal(5) == 'eggs' 83 | prepare_meal(15) == 'spam and eggs' 84 | prepare_meal(45) == 'spam spam and eggs' 85 | ``` 86 | 87 | __Notice that in the first case, there is no "and". In the rest - there is.__ 88 | 89 | 90 | ```ruby 91 | prepare_meal(5) == "eggs" 92 | prepare_meal(3) == "spam" 93 | prepare_meal(27) == "spam spam spam" 94 | prepare_meal(15) == "spam and eggs" 95 | prepare_meal(45) == "spam spam and eggs" 96 | prepare_meal(7) == "" 97 | ``` 98 | 99 | ## Reduce file path 100 | 101 | A file path in a Unix OS looks like this - `/home/radorado/code/hackbulgaria/week0` 102 | 103 | We start from the root - `/` and we navigate to the destination fodler. 104 | 105 | But there is a problem - if we have `..` and `.` in our file path, it's not clear where we are going to end up. 106 | 107 | * `..` means to go back one directory 108 | * `.` means to stay in the same directory 109 | * we can have more then one `/` between the directories - `/home//code` 110 | 111 | So for example : `/home//radorado/code/./hackbulgaria/week0/../` reduces to `/home/radorado/code/hackbulgaria`. 112 | 113 | 114 | Implement a function, called `reduce_file_path(path)` which takes a string and returns the reduced version of the path. 115 | 116 | * Every `..` means that we have to go one directory back 117 | * Every `.` means that we are staying in the same directory 118 | * Every extra `/` is unnecessary 119 | * Always remove the last `/` 120 | 121 | **Few examples:** 122 | 123 | ```ruby 124 | reduce_file_path("/") == "/" 125 | reduce_file_path("/srv/../") == "/" 126 | reduce_file_path("/srv/www/htdocs/wtf/") == "/srv/www/htdocs/wtf" 127 | reduce_file_path("/srv/www/htdocs/wtf") == "/srv/www/htdocs/wtf" 128 | reduce_file_path("/srv/./././././") == "/srv" 129 | reduce_file_path("/etc//wtf/") == "/etc/wtf" 130 | reduce_file_path("/etc/../etc/../etc/../") == "/" 131 | reduce_file_path("//////////////") == "/" 132 | reduce_file_path("/../") == "/" 133 | ``` 134 | 135 | ## Word from a^nb^n 136 | 137 | Implement a function, called `an_bn?(word)` that checks if the given `word` is from the `a^nb^n for n>=0` language. 138 | Here, `a^n` means a to the power of n - __repeat the character "a" n times.__ 139 | 140 | Lets see few words from this language: 141 | 142 | * for `n = 0`, this is the empty word `""` 143 | * for `n = 1`, this is the word `"ab"` 144 | * for `n = 2`, this is the word `"aabb"` 145 | * for `n = 3`, this is the word `"aaabbb"` 146 | * and so on - first, you repeat the character "a" n times, and after this - repeat "b" n times 147 | 148 | The function should return true if the given `word` is from `a^nb^n for n>=0"` for some n. 149 | 150 | **Test examples:** 151 | 152 | ```ruby 153 | an_bn?("") == true 154 | an_bn?("rado") == false 155 | an_bn?("aaabb") == false 156 | an_bn?("aaabbb") == true 157 | an_bn?("aabbaabb") == false 158 | an_bn?("bbbaaa") == false 159 | an_bn?("aaaaabbbbb") == true 160 | ``` 161 | 162 | ## Credit card validation 163 | 164 | Implement a function, called `valid_credit_card?(number)`, which returns true/false based on the following algorithm: 165 | 166 | * Each credit card number must contain odd count of digits. 167 | * We transform the number with the following steps (based on the fact that we start from index 0) 168 | - All digits, read from right to left, at even positions (index), **remain the same.** 169 | - Every digit, read from right to left, at odd position is replaced by the result, that is obtained from doubling the given digit. 170 | * After the transformation, we find the sum of all digits in the transformed number. 171 | * The number is valid, if the sum is divisible by 10. 172 | 173 | For example: `79927398713` is valid, bacause: 174 | 175 | * When we double and replace all digits at odd position we get: `7 (18 = 2 * 9) 9 (4 = 2 * 2) 7 (6 = 2 * 3) 9 (16 = 2 * 8) 7 (2 = 2 * 1) 3` 176 | * The sum of all digits of the new number is 70, which is divisible by 10 => the card number is valid. 177 | 178 | More examples: 179 | 180 | * `79927398713` is a valid number 181 | * `79927398715` is invalid number 182 | 183 | ## Goldbach Conjecture 184 | 185 | Implement a function, called `goldbach(n)` which returns a list of lists, that is the goldbach conjecture for the given number `n`. 186 | 187 | The Goldbach Conjecture states: 188 | 189 | > Every even integer greater than 2 can be expressed as the sum of two primes. 190 | 191 | Keep in mind that there can be more than one combination of two primes, that sums up to the given number. 192 | 193 | __The result should be sorted by the first item in the tuple.__ 194 | 195 | For example: 196 | 197 | * `4 = 2 + 2` 198 | * `6 = 3 + 3` 199 | * `8 = 3 + 5` 200 | * `10 = 3 + 7 = 5 + 5` 201 | * `100 = 3 + 97 = 11 + 89 = 17 + 83 = 29 + 71 = 41 + 59 = 47 + 53` 202 | 203 | **Few examples:** 204 | 205 | ```ruby 206 | goldbach(4) == [[2,2]] 207 | goldbach(6) == [[3,3]] 208 | goldbach(8) == [[3,5]] 209 | goldbach(10) == [[3,7], [5,5]] 210 | goldbach(100) == [[3, 97], [11, 89], [17, 83], [29, 71], [41, 59], [47, 53]] 211 | ``` 212 | 213 | ## Magic Square 214 | 215 | Implement a function, called `magic_square?(matrix)` that checks if the given array of arrays `matrix` is a magic square. 216 | 217 | A magic square is a square matrix where the numbers in each row, and in each column, and the numbers in the forward and backward main diagonals, all add up to the same number. 218 | 219 | 220 | ```ruby 221 | magic_square?([[1,2,3], [4,5,6], [7,8,9]]) == false 222 | 223 | magic_square?([[4,9,2], [3,5,7], [8,1,6]]) == true 224 | magic_square?([[7,12,1,14], [2,13,8,11], [16,3,10,5], [9,6,15,4]]) == true 225 | magic_square?([[23, 28, 21], [22, 24, 26], [27, 20, 25]]) == true 226 | magic_square?([[16, 23, 17], [78, 32, 21], [17, 16, 15]]) == false 227 | ``` 228 | -------------------------------------------------------------------------------- /week01/3-I-Know-Everything/nokia.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming101-Ruby/7bd525eca6c89f3fd4aad1948dbdb20ede0490cf/week01/3-I-Know-Everything/nokia.jpg -------------------------------------------------------------------------------- /week01/README.md: -------------------------------------------------------------------------------- 1 | # Week 01 - Staring out with Ruby 2 | 3 | Ruby is a very beautiful & strange at first language. 4 | 5 | We are going to solve a lot of problems using it & making progress one step at a time. Don't expect the entire syntax from the first week :) 6 | 7 | ## Getting to know Ruby 8 | 9 | Here are materials, **related to motivation & philoshopy of Ruby:** 10 | 11 | * [Interview with Yukihiro Matsumoto called The Philosophy of Ruby](http://www.artima.com/intv/ruby.html) 12 | * [Keynote: Jim Weirich - Why aren't you using Ruby? (RubyConf Uruguay 2013)](https://www.youtube.com/watch?v=0D3KfnbTdWw) 13 | 14 | Here are materials, **related to the syntax & language constructs:** 15 | 16 | * [The first lecture is located here](https://ruby.hackbulgaria.com/lectures/01#/) 17 | * The first problem that you are going to face is to run the [hello.rb](hello.rb) file and examine the structures in it. This will give you clues for how to solve your problems. 18 | * [To get a good idea of **if, elsif, else and unless structures**, read here](http://www.tutorialspoint.com/ruby/ruby_if_else.htm) 19 | * [If you wonder what kind of loop you can use, take a loot at that reference](http://www.tutorialspoint.com/ruby/ruby_loops.htm) 20 | * [As you will see both double and single quotes, check this for the difference](http://stackoverflow.com/questions/6395288/double-vs-single-quotes) 21 | 22 | ## Basic Language Constructs 23 | 24 | The if statement comes in many forms: 25 | 26 | You can do it as a one-liner: 27 | 28 | ```ruby 29 | if 1 == 1 then puts "1 == 1" end 30 | ``` 31 | 32 | This will output `1 == 1`. 33 | 34 | You can use if as a modifier: 35 | 36 | ```ruby 37 | puts "1 == 1" if 1 == 1 38 | ``` 39 | 40 | This will output, again, `1 == 1` 41 | 42 | If you want to use multiline ifs, you can do like so: 43 | 44 | ```ruby 45 | if 2.even? 46 | puts "Did you know?" 47 | puts "2 is even!" 48 | end 49 | ``` 50 | 51 | There is no need for you to write `then`, when you have multiline ifs. 52 | 53 | Every `if` can have multiple `elsif` and one `else` statements: 54 | 55 | ```ruby 56 | color = ARGV[0] 57 | 58 | if color == "red" 59 | puts "Hold on!" 60 | elsif color == "yellow" 61 | puts "Prepare yourself" 62 | elsif color == "green" 63 | puts "Go Go Go!" 64 | else 65 | puts "I don't know this color" 66 | end 67 | ``` 68 | 69 | There is an opposite of the `if` construction, called `unless`. 70 | 71 | Unless means "if not" and can be used in such scenarios. 72 | 73 | Now go ahead and test some Ruby code! 74 | -------------------------------------------------------------------------------- /week01/hello.rb: -------------------------------------------------------------------------------- 1 | def hello_ruby 2 | puts 'Hello Ruby' 3 | end 4 | 5 | def example_with_numbers 6 | a = 5 7 | b = 60 8 | 9 | puts a + b 10 | 11 | # This can be written also as: 12 | # a +1 while a < b 13 | while a < b do 14 | a += 1 15 | end 16 | 17 | puts "a is #{a}, b is #{b}" 18 | end 19 | 20 | def example_reading_arguments 21 | if ARGV.empty? 22 | puts 'No arguments given' 23 | return 24 | end 25 | 26 | arg = ARGV.first.to_i 27 | 28 | if arg.even? 29 | puts "Argument #{arg} is even" 30 | else 31 | puts "Argument #{arg} is odd" 32 | end 33 | end 34 | 35 | # Here we have arrays / lists 36 | # The documentation about Arrays is here - http://ruby-doc.org/core-2.2.0/Array.html 37 | def sum(numbers) 38 | result = 0 39 | index = 0 40 | 41 | while index < numbers.length 42 | result += numbers[index] 43 | index += 1 44 | end 45 | 46 | result 47 | end 48 | 49 | # Here we have hashes 50 | # The documentation about Hashes is here - http://ruby-doc.org/core-2.2.0/Hash.html 51 | def ruby_books 52 | { 53 | 'Matz' => 'The Ruby Language', 54 | 'Black' => 'The Well-Grounded Rubyist' 55 | } 56 | end 57 | 58 | # There is no need for () when calling functions / methods 59 | hello_ruby 60 | example_with_numbers 61 | example_reading_arguments 62 | puts sum [1, 2, 3, 4] 63 | 64 | books = ruby_books 65 | 66 | puts "Matz book is: #{books['Matz']}" if books.key? 'Matz' 67 | -------------------------------------------------------------------------------- /week02/1-Points/README.md: -------------------------------------------------------------------------------- 1 | # Points 2 | 3 | Implement a class in Ruby, called `InfinitePlane` which is going to simulate a 2D coordinate system. 4 | 5 | ![](http://centurion2.com/XNA/GameProgrammingBasics/GPB100/GPB110/Game2DCoordinateSystem.PNG) 6 | 7 | The class should take x and y position of a point in that plane: 8 | 9 | ```ruby 10 | plane = InfinitePlane.new 0, 0 11 | ``` 12 | 13 | Using this class, make a method called `move_to_directions(directions)`, which solves the 1st [problem from here](https://github.com/HackBulgaria/ApplicationFall2015/tree/master/1-Points) 14 | 15 | Here is an example: 16 | 17 | ```ruby 18 | >> plane = InfinitePlane.new 0, 0 19 | >> plane.move_to_directions '>>><<<~>>>~^^^' 20 | >> puts plane.to_array 21 | [-3, -3] 22 | ``` 23 | 24 | Use `each` whenever necessary! 25 | -------------------------------------------------------------------------------- /week02/1-Points/plane.rb: -------------------------------------------------------------------------------- 1 | class InfinitePlane 2 | def initialize(x, y) 3 | @x, @y = x, y 4 | end 5 | 6 | def move_to_directions(directions) 7 | coeff = 1 8 | 9 | dx_moves = ['>', '<'] 10 | dy_moves = ['^', 'v'] 11 | 12 | moves = { 13 | '>' => 1, 14 | '<' => -1, 15 | '^' => -1, 16 | 'v' => 1, 17 | '~' => 0 18 | } 19 | 20 | directions.chars.each do |direction| 21 | coeff *= -1 if direction == '~' 22 | step = moves[direction] * coeff 23 | 24 | if dx_moves.include? direction 25 | @x += step 26 | else 27 | @y += step 28 | end 29 | end 30 | end 31 | 32 | def to_s 33 | "(#{@x}, #{@y})" 34 | end 35 | end 36 | 37 | plane = InfinitePlane.new 0, 0 38 | plane.move_to_directions '>>><<<~>>>~^^^' 39 | 40 | puts plane 41 | -------------------------------------------------------------------------------- /week02/2-Vector/README.md: -------------------------------------------------------------------------------- 1 | # Vectors 2 | 3 | We are going to implement vectors in 2D and n-dimensional space. 4 | 5 | Check the example for [classes.rb](../classes.rb) in order to get some insight on how to set and get values of instance variables. 6 | 7 | ## Vector2D 8 | 9 | Implement class named Vector2D with the following interface: 10 | 11 | ```ruby 12 | class Vector2D 13 | def initialize(x, y) 14 | @x, @y = x, y 15 | end 16 | 17 | def x 18 | end 19 | 20 | def x=(value) 21 | end 22 | 23 | def y 24 | end 25 | 26 | def y=(value) 27 | end 28 | 29 | def length 30 | # Your code goes here. 31 | end 32 | 33 | def normalize 34 | # Your code goes here. 35 | end 36 | 37 | def ==(other) 38 | # Your code goes here. 39 | end 40 | 41 | def +(other) 42 | # Return a new Vector2D that represents the result 43 | # Your code goes here. 44 | end 45 | 46 | def -(other) 47 | # Return a new Vector2D that represents the result 48 | # Your code goes here. 49 | end 50 | 51 | def *(scalar) 52 | # Return a new Vector2D that represents the result 53 | # Your code goes here. 54 | end 55 | 56 | def /(scalar) 57 | # Return a new Vector2D that represents the result 58 | # Your code goes here. 59 | end 60 | 61 | def dot(other) 62 | # Return the dot product of the two vectors 63 | # https://en.wikipedia.org/wiki/Dot_product#Algebraic_definition 64 | end 65 | 66 | def to_s 67 | # Your code goes here. 68 | end 69 | end 70 | ``` 71 | 72 | ## Vector 73 | 74 | Now that we got our hands dirty with classes and vectors, let's implement a 75 | more generic n-dimensional vector. Let's use this vector as the basis for the 76 | implementation of two other vector types. 77 | 78 | * `Vector2D` the same vector as above, implemented in terms of `Vector`. 79 | * `Vector3D` similar to `Vector2D`, but has a `z` attribute as well. 80 | 81 | The `Vector` interface should be: 82 | 83 | ```ruby 84 | class Vector 85 | def initialize(*components) 86 | # Let's make it more interesting here. I wanna initialize the vector with 87 | # `Vector.new(1, 2, 3, 4)` and `Vector.new([1, 2, 3, 4])` and expect the 88 | # same vector. 89 | end 90 | 91 | def dimension 92 | # Your code goes here 93 | end 94 | 95 | def length 96 | # Your code goes here. 97 | end 98 | 99 | def normalize 100 | # Your code goes here. 101 | end 102 | 103 | def [](index) 104 | # Your code goes here. 105 | end 106 | 107 | def []=(index, value) 108 | # Your code goes here. 109 | end 110 | 111 | def ==(other) 112 | # Your code goes here. 113 | end 114 | 115 | def +(vector_of_same_dimension_or_scalar) 116 | # Return a new Vector that represents the result 117 | # Your code goes here. 118 | end 119 | 120 | def -(vector_of_same_dimension_or_scalar) 121 | # Return a new Vector that represents the result 122 | # Your code goes here. 123 | end 124 | 125 | def *(scalar) 126 | # Return a new Vector that represents the result 127 | # Your code goes here. 128 | end 129 | 130 | def /(scalar) 131 | # Return a new Vector that represents the result 132 | # Your code goes here. 133 | end 134 | 135 | def dot(vector_of_same_dimension_or_scalar) 136 | # Return the dot product of the two vectors 137 | # https://en.wikipedia.org/wiki/Dot_product#Algebraic_definition 138 | end 139 | 140 | def to_s 141 | # Your code goes here. 142 | end 143 | end 144 | ``` 145 | -------------------------------------------------------------------------------- /week02/3-Resolve-with-each/README.md: -------------------------------------------------------------------------------- 1 | # Resolve Problems from week 1 with each 2 | 3 | Take a look at your solutions to the problems from [week01](../../week01) 4 | 5 | Replace every `while` loop, **wherever applicable**, with `each` and a block. 6 | 7 | Rember, blocks with `{ |arg| ...}` are for single lines. 8 | 9 | Blocks with `do |arg| ... end` are for multiple lines. 10 | -------------------------------------------------------------------------------- /week02/README.md: -------------------------------------------------------------------------------- 1 | # Week 2 - Class, Objects, Modules, Blocks 2 | 3 | We are going to enter the magical world of OOP in Ruby! 4 | 5 | In order to understand what is happening, we recommend the following prereading materials: 6 | 7 | * From the [**Programming Ruby**](http://ruby-doc.com/docs/ProgrammingRuby/) book: 8 | * The `Ruby.new` chapter 9 | * The `Classes, Objects and Variables` chapter 10 | * [What happens when you call `SomeClass.new`?](http://stackoverflow.com/questions/10383535/in-ruby-whats-the-relationship-between-new-and-initialize-how-to-return-n) 11 | * [Here is the first part of the lecture for Enumerable](https://ruby.hackbulgaria.com/lectures/02) 12 | -------------------------------------------------------------------------------- /week02/classes.rb: -------------------------------------------------------------------------------- 1 | class Person 2 | def initialize(name, age) 3 | @name = name 4 | @age = age 5 | end 6 | 7 | def name 8 | @name 9 | end 10 | 11 | def age 12 | @age 13 | end 14 | 15 | def name=(name) 16 | @name = name 17 | end 18 | 19 | def age=(age) 20 | @age = age 21 | end 22 | end 23 | 24 | def print_information(person) 25 | puts "#{person.name} is #{person.age} years old" 26 | end 27 | 28 | ivan = Person.new('Ivan', 21) 29 | maria = Person.new('Maria', 21) 30 | 31 | print_information ivan 32 | print_information maria 33 | 34 | ivan.name = 'Ivaylo' 35 | ivan.age = 22 36 | 37 | print_information ivan 38 | -------------------------------------------------------------------------------- /week02/position.rb: -------------------------------------------------------------------------------- 1 | class Position 2 | def initialize(x, y) 3 | @x, @y = x, y 4 | end 5 | 6 | def +(other) 7 | Position.new @x + other.x, @y + other.y 8 | # @x += other.x 9 | # @y += other.y 10 | end 11 | 12 | def to_s 13 | "(#{@x}, #{@y})" 14 | end 15 | 16 | def x 17 | @x 18 | end 19 | 20 | def x=(value) 21 | @x = value 22 | end 23 | 24 | def y 25 | @y 26 | end 27 | 28 | def y=(value) 29 | @y = value 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /week03/1-fmi-tribute/.rubocop.yml: -------------------------------------------------------------------------------- 1 | Style/Documentation: 2 | Enabled: false 3 | -------------------------------------------------------------------------------- /week03/1-fmi-tribute/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rake' 4 | gem 'minitest' 5 | gem 'rubocop' 6 | -------------------------------------------------------------------------------- /week03/1-fmi-tribute/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | ast (2.0.0) 5 | astrolabe (1.3.0) 6 | parser (>= 2.2.0.pre.3, < 3.0) 7 | minitest (5.4.2) 8 | parser (2.2.0.pre.5) 9 | ast (>= 1.1, < 3.0) 10 | slop (~> 3.4, >= 3.4.5) 11 | powerpack (0.0.9) 12 | rainbow (2.0.0) 13 | rake (10.3.2) 14 | rubocop (0.26.1) 15 | astrolabe (~> 1.3) 16 | parser (>= 2.2.0.pre.4, < 3.0) 17 | powerpack (~> 0.0.6) 18 | rainbow (>= 1.99.1, < 3.0) 19 | ruby-progressbar (~> 1.4) 20 | ruby-progressbar (1.6.0) 21 | slop (3.6.0) 22 | 23 | PLATFORMS 24 | ruby 25 | 26 | DEPENDENCIES 27 | minitest 28 | rake 29 | rubocop 30 | -------------------------------------------------------------------------------- /week03/1-fmi-tribute/README.md: -------------------------------------------------------------------------------- 1 | # FMI 2 | 3 | 2011 was a great year for Ruby. Colomb have just discovered the new earth, the 4 | first space shuffle has landed on Mars and the dinosaurs have just went MIA. In 5 | this great age of time, FMI had a great Ruby student, named Genadi. 6 | 7 | Let me share my experience with you and present you with my [first] ever Ruby 8 | homework. 9 | 10 | :kiss: to [@skanev] and [@mitio] for this one. 11 | 12 | ## Resources 13 | 14 | Enumerable is a gem. Go through its documentation. Array builds on top of it. 15 | 16 | * http://www.ruby-doc.org/core-2.1.3/Enumerable.html 17 | * http://www.ruby-doc.org/core-2.1.3/Array.html 18 | 19 | ## Handycap 20 | 21 | Avoid using `Enumerable#each` and you get a sticker :) 22 | 23 | [@skanev]: http://github.com/skanev 24 | [@mitio]: http://github.com/mitio 25 | [first]: http://2011.fmi.ruby.bg/tasks/1 26 | -------------------------------------------------------------------------------- /week03/1-fmi-tribute/Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake/testtask' 2 | require 'rubocop/rake_task' 3 | 4 | RuboCop::RakeTask.new 5 | 6 | Rake::TestTask.new do |t| 7 | t.test_files = FileList['*_test.rb'] 8 | t.verbose = true 9 | end 10 | 11 | task default: %i(test rubocop) 12 | -------------------------------------------------------------------------------- /week03/1-fmi-tribute/patch_array.rb: -------------------------------------------------------------------------------- 1 | class Array 2 | # Your code goes here. 3 | end 4 | -------------------------------------------------------------------------------- /week03/1-fmi-tribute/patch_array_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | 3 | require_relative 'solution' 4 | 5 | class ArrayTest < Minitest::Test 6 | def test_to_hash 7 | assert_equal({ one: 1, two: 2 }, [[:one, 1], [:two, 2]].to_hash) 8 | end 9 | 10 | def test_index_by 11 | assert_equal( 12 | { 'Coltrane' => 'John Coltrane', 'Davis' => 'Miles Davis' }, 13 | ['John Coltrane', 'Miles Davis'].index_by { |name| name.split(' ').last } 14 | ) 15 | end 16 | 17 | def test_occurences_count 18 | assert_equal({ foo: 2, bar: 1 }, [:foo, :bar, :foo].occurences_count) 19 | end 20 | 21 | def test_subarray_count 22 | assert_equal 2, [1, 2, 3, 2, 3, 1].subarray_count([2, 3]) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /week03/2-Patch-Hash/README.md: -------------------------------------------------------------------------------- 1 | # FMI 2 | 3 | How can I follow those FMI problems, heh? Watashi wa besuto o tsukushimasu! 4 | 5 | ## Resources 6 | 7 | Enumerable is a gem. Go through its documentation. Hash builds on top of it. 8 | 9 | * 10 | * 11 | 12 | Use whatever you need in order to solve the following problems: 13 | 14 | ## Hash#pick 15 | 16 | `Hash#pick` returns a new hash, with only the specified keys in it. 17 | 18 | ```ruby 19 | class Hash 20 | def pick(*keys) 21 | # Your code goes here. 22 | end 23 | end 24 | 25 | >> {a: 1, b: 2, c: 3}.pick(:a, :b) 26 | => {:a=>1, :b=>2} 27 | ``` 28 | 29 | ## Hash#except 30 | 31 | `Hash#except` returns a new hash, without the specified keys in it. Kind of like 32 | a reversed `Hash#pluck`. 33 | 34 | ```ruby 35 | class Hash 36 | def except(*keys) 37 | # Your code goes here. 38 | end 39 | end 40 | 41 | >> {a: 1, b: 2, d: nil}.except(:d) 42 | => {:a=>1, :b=>2} 43 | ``` 44 | 45 | ## Hash#compact_values 46 | 47 | `Hash#compact_values` returns a new hash, with only the truthy keys in it. 48 | 49 | ```ruby 50 | class Hash 51 | def compact_values 52 | # Your code goes here. 53 | end 54 | end 55 | 56 | >> {a: 1, b: 2, c: false, d: nil}.compact_values 57 | => {:a=>1, :b=>2} 58 | ``` 59 | 60 | ## Hash#defaults 61 | 62 | `Hash#defaults` returns a new hash, setting values only if they were not already 63 | present in the hash. 64 | 65 | ```ruby 66 | class Hash 67 | def defaults(hash) 68 | # Your code goes here. 69 | end 70 | end 71 | 72 | >> {a: 1, b: 2}.defaults(a: 4, c: 3) 73 | => {:a=>1, :b=>2, :c=>3} 74 | ``` 75 | 76 | ## Bang Bang 77 | 78 | Add bang version methods (e.g. Hash#pick!) that change the hash inplace. Think 79 | whether you can reuse the implementations with the non-bang version methods. 80 | 81 | [@skanev]: http://github.com/skanev 82 | [@mitio]: http://github.com/mitio 83 | 84 | -------------------------------------------------------------------------------- /week03/2-Patch-Hash/solution.rb: -------------------------------------------------------------------------------- 1 | class Hash 2 | def pick3(*keys) 3 | Hash.new.tap do |h| 4 | each do |k, v| 5 | h[k] = v if keys.include? k 6 | end 7 | end 8 | end 9 | 10 | def pick2(*keys) 11 | select do |k, _| 12 | keys.include? k 13 | end 14 | end 15 | 16 | def pick(*keys) 17 | result = {} 18 | 19 | each do |k, v| 20 | result[k] = v if keys.include? k 21 | end 22 | 23 | result 24 | end 25 | 26 | def except2(*keys) 27 | reject { |k, _| keys.include? k } 28 | end 29 | 30 | def except(*keys) 31 | result = {} 32 | 33 | each do |k, v| 34 | result[k] = v unless keys.include? k 35 | end 36 | 37 | result 38 | end 39 | 40 | def compact_values 41 | select { |_, value| value } 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /week03/3-My-Enumerable/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rake' 4 | gem 'minitest' 5 | gem 'rubocop' 6 | -------------------------------------------------------------------------------- /week03/3-My-Enumerable/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | ast (2.0.0) 5 | astrolabe (1.3.0) 6 | parser (>= 2.2.0.pre.3, < 3.0) 7 | minitest (5.4.3) 8 | parser (2.2.0.pre.5) 9 | ast (>= 1.1, < 3.0) 10 | slop (~> 3.4, >= 3.4.5) 11 | powerpack (0.0.9) 12 | rainbow (2.0.0) 13 | rake (10.4.2) 14 | rubocop (0.26.1) 15 | astrolabe (~> 1.3) 16 | parser (>= 2.2.0.pre.4, < 3.0) 17 | powerpack (~> 0.0.6) 18 | rainbow (>= 1.99.1, < 3.0) 19 | ruby-progressbar (~> 1.4) 20 | ruby-progressbar (1.6.0) 21 | slop (3.6.0) 22 | 23 | PLATFORMS 24 | ruby 25 | 26 | DEPENDENCIES 27 | minitest 28 | rake 29 | rubocop 30 | -------------------------------------------------------------------------------- /week03/3-My-Enumerable/README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | The great power of Ruby iteration comes out of a simple module. I bet, you can 4 | implement part of it as well. 5 | 6 | ## Resources 7 | 8 | Reading the Enumerable documentation one more time won't hurt. Read it again, 9 | and again, and again... You get the idea :) 10 | 11 | * 12 | 13 | ## Testing 14 | 15 | There are tests which you can use in order to check if your code is OK. 16 | 17 | In order to run the tests, you will have to take all files from this directory (without `README.md`). 18 | 19 | When you have all files, frist, you need to install the desired gems: 20 | 21 | ``` 22 | $ bundle install 23 | ``` 24 | 25 | If you do not have bundler, install it: 26 | 27 | ``` 28 | $ gem install bundler 29 | ``` 30 | 31 | And when you have everything ready, in order to run the tests, type the following command: 32 | 33 | ``` 34 | $ bundle exec rake 35 | ``` 36 | 37 | You will see which tests pass and which don't 38 | 39 | ## My Enumerable 40 | 41 | Implement a module called `MyEnumerable` which implements a similar module to 42 | Enumerable, but with own little twist. _Meaning, we have methods Enumerable 43 | doesn't, we named filter how we liked it, etc._ 44 | 45 | ```ruby 46 | module MyEnumerable 47 | def map 48 | # Your code goes here. 49 | end 50 | 51 | def filter 52 | # Your code goes here. 53 | end 54 | 55 | def reject 56 | # Your code goes here. 57 | end 58 | 59 | def reduce(initial = nil) 60 | # Your code goes here. 61 | end 62 | 63 | def any? 64 | # Your code goes here. 65 | end 66 | 67 | def one? 68 | # Your code goes here. 69 | end 70 | 71 | def all? 72 | # Your code goes here. 73 | end 74 | 75 | # Yield each consequative n elements. 76 | def each_cons(n) 77 | # Your code goes here. 78 | end 79 | 80 | def include?(element) 81 | # Your code goes here. 82 | end 83 | 84 | # Count the occurences of an element in the collection. If no element is 85 | # given, count the size of the collection. 86 | def count(element = nil) 87 | # Your code goes here. 88 | end 89 | 90 | # Count the size of the collection. 91 | def size 92 | # Your code goes here. 93 | end 94 | 95 | # Groups the collection by result of the block. 96 | # Returns a hash where the keys are the evaluated 97 | # result from the block and the values are arrays 98 | # of elements in the collection that correspond to 99 | # the key. 100 | def group_by 101 | end 102 | 103 | def min 104 | # Your code goes here. 105 | end 106 | 107 | def min_by 108 | # Your code goes here. 109 | end 110 | 111 | def max 112 | # Your code goes here. 113 | end 114 | 115 | def max_by 116 | # Your code goes here. 117 | end 118 | 119 | def take(n) 120 | # Your code goes here. 121 | end 122 | 123 | def take_while 124 | # Your code goes here. 125 | end 126 | 127 | def drop(n) 128 | # Your code goes here. 129 | end 130 | 131 | def drop_while 132 | # Your code goes here. 133 | end 134 | end 135 | ``` 136 | 137 | ## Enumerator 138 | 139 | Return an iterator for the method, if no block is given. 140 | 141 | ## Aliases 142 | 143 | As a bonus, create aliases for the following methods, so they can be invoked 144 | with different names. 145 | 146 | ``` 147 | #map -> #collect 148 | #filter -> #select 149 | #reduce -> #foldl 150 | ``` 151 | 152 | ## Strict 153 | 154 | As a bigger challenge, try to strictly follow Enumerable. For example if no 155 | initial element is given to `#reduce` Ruby's Enumerable will take the first one 156 | of the collection as the initial value. 157 | 158 | This is what makes `(1..10).reduce { |sum, n| sum + n }` work. Consult with 159 | [rubyspec][] to better mimic Ruby's Enumerable behaviour. 160 | 161 | [rubyspec]: https://github.com/rubyspec/rubyspec/tree/7fb7465aac1ec8e2beffdfa9053758fa39b443a5/core/enumerable 162 | -------------------------------------------------------------------------------- /week03/3-My-Enumerable/Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake/testtask' 2 | require 'rubocop/rake_task' 3 | 4 | RuboCop::RakeTask.new 5 | 6 | Rake::TestTask.new do |t| 7 | t.test_files = FileList['*_test.rb'] 8 | t.verbose = true 9 | end 10 | 11 | task default: %i(test rubocop) 12 | -------------------------------------------------------------------------------- /week03/3-My-Enumerable/solution.rb: -------------------------------------------------------------------------------- 1 | # Implementation of our own Enumerable class 2 | module MyEnumerable 3 | def map 4 | Array.new.tap do |arr| 5 | each do |element| 6 | value = yield element 7 | arr << value 8 | end 9 | end 10 | end 11 | 12 | def filter 13 | Array.new.tap do |arr| 14 | each do |element| 15 | arr << element if (yield element) 16 | end 17 | end 18 | end 19 | 20 | def first 21 | element = nil 22 | 23 | each do |x| 24 | element = x 25 | break 26 | end 27 | 28 | element 29 | end 30 | 31 | def reduce(initial = nil) 32 | skip_first = false 33 | 34 | if initial.nil? 35 | initial = first 36 | skip_first = true 37 | end 38 | 39 | each do |x| 40 | if skip_first 41 | skip_first = false 42 | next 43 | end 44 | initial = yield initial, x 45 | end 46 | 47 | initial 48 | end 49 | 50 | def negate_block(&block) 51 | proc { |x| !block.call(x) } 52 | end 53 | 54 | def reject(&block) 55 | filter(negate_block(&block)) 56 | end 57 | 58 | def size 59 | map { |_| 1 }.reduce(0, &:+) 60 | end 61 | 62 | def any?(&block) 63 | filter(&block).size > 0 64 | end 65 | 66 | def all?(&block) 67 | filter(&block).size == size 68 | end 69 | 70 | def include?(element) 71 | # Your code goes here 72 | end 73 | 74 | def count(element = nil) 75 | return size if element.nil? 76 | 77 | filter { |x| x == element }.size 78 | end 79 | 80 | def min 81 | # Your code goes here. 82 | end 83 | 84 | def min_by 85 | # Your code goes here. 86 | end 87 | 88 | def max 89 | # Your code goes here. 90 | end 91 | 92 | def max_by 93 | # Your code goes here. 94 | end 95 | 96 | def take(n) 97 | # Your code goes here. 98 | end 99 | 100 | def take_while 101 | # Your code goes here. 102 | end 103 | 104 | def drop(n) 105 | # Your code goes here. 106 | end 107 | 108 | def drop_while 109 | # Your code goes here. 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /week03/3-My-Enumerable/solution_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | 3 | require_relative 'solution' 4 | 5 | class SolutionTest < Minitest::Test 6 | class Collection 7 | include MyEnumerable 8 | 9 | def initialize(*data) 10 | @data = data 11 | end 12 | 13 | def each(&block) 14 | @data.each(&block) 15 | end 16 | 17 | def ==(otherCollection) 18 | @data == otherCollection.data 19 | end 20 | 21 | def get(index) 22 | return @data[index] 23 | end 24 | end 25 | 26 | def test_map 27 | collection = Collection.new(*1..5) 28 | 29 | assert_equal [2, 3, 4, 5, 6], collection.map(&:succ) 30 | end 31 | 32 | def test_filter 33 | collection = Collection.new(*1..10) 34 | 35 | assert_equal [1, 3, 5, 7, 9], collection.filter(&:odd?) 36 | end 37 | 38 | def test_reject 39 | collection = Collection.new(*1..10) 40 | 41 | assert_equal [1, 3, 5, 7, 9], collection.reject(&:even?) 42 | end 43 | 44 | def test_reduce 45 | collection = Collection.new(*1..10) 46 | 47 | assert_equal 55, collection.reduce(0) { |sum, n| sum + n } 48 | end 49 | 50 | def test_include? 51 | collection = Collection.new(*1..10) 52 | 53 | assert_equal true, collection.include?(5) 54 | end 55 | 56 | 57 | def test_any? 58 | collection = Collection.new(*1..10) 59 | 60 | assert collection.any?(&:even?) 61 | end 62 | 63 | def test_all? 64 | collection = Collection.new(*1..10) 65 | 66 | assert collection.all? { |x| x > 0 } 67 | end 68 | 69 | def test_size 70 | collection = Collection.new(*1..10) 71 | 72 | assert_equal 10, collection.size 73 | end 74 | 75 | def test_count_with_element_nil 76 | collection = Collection.new(*1..10) 77 | 78 | assert_equal 10, collection.count 79 | end 80 | 81 | def test_count_with_non_nil_element 82 | collection = Collection.new(*1..10) 83 | 84 | assert_equal 1, collection.count(10) 85 | end 86 | 87 | def test_min_with_numbers 88 | collection = Collection.new(*1..10) 89 | assert_equal 1, collection.min 90 | end 91 | 92 | def test_min_by 93 | collection = Collection.new(*['apple', 'orange', 'horse', 'ruby']) 94 | assert_equal 'ruby', collection.min_by { |x| x.length } 95 | end 96 | 97 | def test_max 98 | collection = Collection.new(*1..10) 99 | assert_equal 10, collection.max 100 | end 101 | 102 | def test_max_by 103 | collection = Collection.new(*['apples', 'oranges', 'horse', 'ruby']) 104 | assert_equal 'oranges', collection.max_by { |x| x.length } 105 | end 106 | 107 | def test_take_in_standard_case 108 | collection = Collection.new(*1..10) 109 | assert_equal [1, 2, 3, 4, 5], collection.take(5) 110 | end 111 | 112 | def test_take_with_0 113 | collection = Collection.new(*1..10) 114 | assert_equal [], collection.take(0) 115 | end 116 | 117 | def test_take_with_larger_than_size_number 118 | collection = Collection.new(*1..2) 119 | assert_equal [1, 2], collection.take(5) 120 | end 121 | 122 | def test_take_while_with_some_elements_matching 123 | collection = Collection.new(*[0, 0, 0, 1, 2, 3]) 124 | assert_equal [0, 0, 0], collection.take_while { |x| x.zero? } 125 | end 126 | 127 | def test_take_with_no_elements_matching 128 | collection = Collection.new(*[2, 4, 6, 3]) 129 | assert_equal [], collection.take_while {|x| x.odd? } 130 | end 131 | 132 | def test_drop_in_standard_case 133 | collection = Collection.new(*1..10) 134 | assert_equal [6, 7, 8, 9, 10], collection.drop(5) 135 | end 136 | 137 | def test_drop_with_0 138 | collection = Collection.new(*1..10) 139 | assert_equal [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], collection.drop(0) 140 | end 141 | 142 | def test_take_with_larger_than_size_number 143 | collection = Collection.new(*1..2) 144 | assert_equal [1, 2], collection.take(5) 145 | end 146 | 147 | def test_drop_while_with_some_elements_matching 148 | collection = Collection.new(*[0, 0, 0, 1, 2, 3]) 149 | assert_equal [1, 2, 3], collection.drop_while { |x| x.zero? } 150 | end 151 | 152 | def test_drop_with_no_elements_matching 153 | collection = Collection.new(*[2, 4, 6, 3]) 154 | assert_equal [2, 4, 6, 3], collection.drop_while {|x| x.odd? } 155 | end 156 | end 157 | -------------------------------------------------------------------------------- /week03/4-FMI-Tribute-Number-Set/README.md: -------------------------------------------------------------------------------- 1 | # The second FMI Tribute 2 | 3 | The guys over FMI are doing great job with their Ruby courses. 4 | 5 | Solve this great [`NumberSet` problem](http://2014.fmi.ruby.bg/tasks/2) from FMI's 2014 Ruby course. 6 | -------------------------------------------------------------------------------- /week03/README.md: -------------------------------------------------------------------------------- 1 | # Week03 - Class, Modules, Blocks & Enumerable 2 | 3 | Here are the needed materials: 4 | 5 | * [What is `&block` and `yield` in Ruby?](http://stackoverflow.com/questions/814739/whats-this-block-in-ruby-and-how-does-it-get-passed-in-a-method-here) 6 | * [Understanding Ruby blocks, procs & lambdas](http://www.reactive.io/tips/2008/12/21/understanding-ruby-blocks-procs-and-lambdas/) 7 | * [Guide to Ruby collections - Enumerable & Enumerator](http://www.sitepoint.com/guide-ruby-collections-iii-enumerable-enumerator/) 8 | * [How does `Symbol#to_proc` work?](http://benjamintan.io/blog/2015/03/16/how-does-symbol-to_proc-work/) 9 | * [How does defining [square bracket] method in Ruby work?](http://stackoverflow.com/questions/10018900/how-does-defining-square-bracket-method-in-ruby-work) 10 | * [Ruby: kind_of? vs. instance_of? vs. is_a?](http://stackoverflow.com/questions/3893278/ruby-kind-of-vs-instance-of-vs-is-a) 11 | -------------------------------------------------------------------------------- /week03/block_magic.rb: -------------------------------------------------------------------------------- 1 | def each(data) 2 | index = 0 3 | while index < data.length 4 | yield data[index] 5 | index += 1 6 | end 7 | end 8 | 9 | def each2(data, &block) 10 | index = 0 11 | while index < data.length 12 | block.call(data[index]) 13 | index += 1 14 | end 15 | end 16 | 17 | def transform_value(x) 18 | yield x 19 | end 20 | 21 | def map(items) 22 | index = 0 23 | result = [] 24 | while index < items.length 25 | value = yield items[index] 26 | result << value 27 | index += 1 28 | end 29 | 30 | result 31 | end 32 | -------------------------------------------------------------------------------- /week03/collection.rb: -------------------------------------------------------------------------------- 1 | class Collection 2 | def initialize(*data) 3 | @data = data 4 | end 5 | 6 | def each 7 | index = 0 8 | while index < @data.length 9 | yield @data[index] 10 | index += 1 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /week04/1-Game-Of-Life/.rubocop.yml: -------------------------------------------------------------------------------- 1 | Style/Documentation: 2 | Enabled: false 3 | -------------------------------------------------------------------------------- /week04/1-Game-Of-Life/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rake' 4 | gem 'minitest' 5 | gem 'rubocop' 6 | -------------------------------------------------------------------------------- /week04/1-Game-Of-Life/README.md: -------------------------------------------------------------------------------- 1 | # Game of Life 2 | 3 | A classic problem in Ruby land. The nice folks over FMI have it well described [here][]. 4 | 5 | [here]: http://2011.fmi.ruby.bg/tasks/6 6 | -------------------------------------------------------------------------------- /week04/1-Game-Of-Life/Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake/testtask' 2 | require 'rubocop/rake_task' 3 | 4 | RuboCop::RakeTask.new 5 | 6 | Rake::TestTask.new do |t| 7 | t.test_files = FileList['*_test.rb'] 8 | t.verbose = true 9 | end 10 | 11 | task default: %i(test rubocop) 12 | -------------------------------------------------------------------------------- /week04/1-Game-Of-Life/game_of_life.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming101-Ruby/7bd525eca6c89f3fd4aad1948dbdb20ede0490cf/week04/1-Game-Of-Life/game_of_life.rb -------------------------------------------------------------------------------- /week04/1-Game-Of-Life/game_of_life_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | 3 | require_relative 'solution' 4 | 5 | class SolutionTest < Minitest::Test 6 | def test_the_truth 7 | assert true 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /week04/2-Polynoms-And-Derivates/README.md: -------------------------------------------------------------------------------- 1 | # Polynomials and Derivates 2 | 3 | ## Polynomials 4 | 5 | In math, [a polynomial](https://en.wikipedia.org/wiki/Polynomial) is a function, defined like that: 6 | 7 | ``` 8 | f(x) = Cn*x^n + Cn-1*x^n-1 + ... + c1*x^1 + c0 9 | ``` 10 | 11 | where: 12 | 13 | * `Cn` to `C0` are coefficients. **We are going to work with positive integers for coefficients.** 14 | * `x` is the variable and `x^n` means x to the power of `n` 15 | * If the given coeff is equal to `1`, it can be omitted. 16 | 17 | Here are few examples: 18 | 19 | ``` 20 | f(x) = 2x^3 + 3x + 1 21 | g(x) = x^3 + x^2 + x 22 | ``` 23 | 24 | ## Derivatives 25 | 26 | A derivative of a polynomial function is easily calculated. The only thing that we need to know is how to take derivative from each member of the polynomial function. 27 | 28 | Here is the general rule for taking derivative of a function in the following form: 29 | 30 | ``` 31 | f(x) = c * x^n 32 | f'(x) = n * c * x^(n - 1) 33 | ``` 34 | 35 | Where `c` and `n` are positive integers and `f'(x)` denotes the derivative of `f(x)` 36 | 37 | There are two corner cases: 38 | 39 | Taking derivative of `x` to the power of 1. 40 | 41 | ``` 42 | f(x) = c * x 43 | f'(x) = c 44 | ``` 45 | 46 | and taking derivatives of constants: 47 | 48 | ``` 49 | f(x) = c 50 | f'(x) = 0 51 | ``` 52 | 53 | 54 | So if we want to take the derivative of a polynomial function, we just apply that rule to every member of the polynom: 55 | 56 | ``` 57 | f(x) = 2x^3 + 3x + 1 58 | f'(x) = 6x^2 + 3 59 | ``` 60 | 61 | ## Your task 62 | 63 | Using your OO knowledge, implement a program that takes a string, representing a polynomial function and returns / prints the derivative of that polynomial function. 64 | 65 | Few examples: 66 | 67 | ``` 68 | $ ruby solution.rb '2*x^3+x' 69 | Derivative of f(x) = 2*x^3 + x is: 70 | f'(x) = 6*x^2 + 1 71 | ``` 72 | 73 | ``` 74 | $ ruby solution.rb '1' 75 | The derivative of f(x) = 1 is: 76 | f'(x) = 0 77 | ``` 78 | 79 | ``` 80 | $ ruby solution.rb 'x^4+10*x^3' 81 | The derivative of f(x) = x^4 + 10*x^3 is: 82 | f'(x) = 4*x^3 + 30*x^2 83 | ``` 84 | 85 | Few things to keep in mind: 86 | 87 | ``` 88 | $ ruby solution.rb '1+x^2' 89 | The derivative of f(x) = x^2 + 1 is: 90 | f'(x) = 2*x 91 | ``` 92 | 93 | And 94 | 95 | ``` 96 | $ ruby solution.rb '2*x^2 + x^2' 97 | The derivative of f(x) = 3*x^2 is: 98 | f'(x) = 6*x 99 | ``` 100 | 101 | Don't bother checking if the polynomial is correct for it's variable. It's always going to be the same ( for example `x`) 102 | 103 | ## Hints 104 | 105 | Take your input via `ARGV[0]`. Thing about the different part of your program. 106 | 107 | Implemented it in such a way that you are not dependant only on console input / output. 108 | 109 | **Write tests in order to validate your code.** 110 | -------------------------------------------------------------------------------- /week04/2-Polynoms-And-Derivates/solution.rb: -------------------------------------------------------------------------------- 1 | class Monom 2 | attr_accessor :coeff, :var, :power 3 | 4 | def initialize(coeff, var, power) 5 | @coeff = coeff 6 | @var = var 7 | @power = power 8 | end 9 | 10 | def self.parse(input) 11 | parsed = MonomParser.new(input) 12 | 13 | coeff = parsed.coeff 14 | coeff = 0 unless coeff 15 | 16 | var = parsed.var 17 | var = 'x' unless var 18 | 19 | power = parsed.power 20 | power = 0 unless power 21 | 22 | Monom.new coeff, var, power 23 | end 24 | 25 | def self.constant(x) 26 | Monom.new(x, 'x', 0) 27 | end 28 | 29 | def ==(other) 30 | return false unless other 31 | 32 | coeff == other.coeff && var == other.var && power == other.power 33 | end 34 | 35 | def to_s 36 | return coeff.to_s if constant? 37 | 38 | power_1 = "#{@coeff}*#{@var}" 39 | return power_1 if power == 1 40 | 41 | power_1 + "^#{@power}" 42 | end 43 | 44 | def +(other) 45 | if can_add?(other) 46 | Monom.new(coeff + other.coeff, var, power) 47 | end 48 | end 49 | 50 | def equal_power?(other) 51 | power == other.power 52 | end 53 | 54 | def equal_var?(other) 55 | var == other.var 56 | end 57 | 58 | def can_add?(other) 59 | equal_var?(other) && equal_power?(other) 60 | end 61 | 62 | def constant? 63 | power == 0 64 | end 65 | 66 | def derivate 67 | return Monom.constant(0) if constant? 68 | return Monom.constant(coeff) if power == 1 69 | 70 | Monom.new(coeff * power, var, power - 1) 71 | end 72 | end 73 | 74 | class Monom 75 | class MonomParser 76 | NUMERIC = /^[0-9]+$/ 77 | MULT = '*' 78 | 79 | attr_reader :coeff, :var, :power 80 | 81 | def initialize(input) 82 | @input = input 83 | try_take_coeff 84 | try_take_var 85 | try_take_power 86 | end 87 | 88 | private 89 | 90 | def try_take_coeff 91 | c = @input.chars.take_while { |ch| ch =~ NUMERIC } 92 | 93 | if c == [] 94 | @coeff = 1 95 | return 96 | end 97 | 98 | @coeff = c.join('').to_i 99 | end 100 | 101 | def try_take_var 102 | input = @input.chars.drop_while { |ch| ch =~ NUMERIC } 103 | input.shift if input.length > 0 && input[0] == MULT 104 | 105 | if input.length == 0 106 | @var = 'x' 107 | @power = 0 108 | return 109 | end 110 | 111 | @var = input[0] 112 | end 113 | 114 | def try_take_power 115 | return if @power 116 | 117 | input = @input.chars.reverse 118 | power = input.take_while { |ch| ch =~ NUMERIC } 119 | 120 | @power = 1 if power == [] 121 | @power = power.reverse.join('').to_i unless power == [] 122 | end 123 | end 124 | end 125 | 126 | class Polynomial 127 | def initialize(*monoms) 128 | # power => monom 129 | @monoms = {} 130 | 131 | monoms = monoms.flatten 132 | monoms.each { |monom| self << monom } 133 | end 134 | 135 | def self.parse(input) 136 | input = input.delete(' ') 137 | parts = input.split('+') 138 | p = Polynomial.new 139 | 140 | parts.each do |part| 141 | p << Monom.parse(part) 142 | end 143 | 144 | p 145 | end 146 | 147 | def ==(other) 148 | monoms == other.monoms 149 | end 150 | 151 | def <<(monom) 152 | if @monoms[monom.power] 153 | @monoms[monom.power] = @monoms[monom.power] + monom 154 | else 155 | @monoms[monom.power] = monom 156 | end 157 | end 158 | 159 | def to_a 160 | @monoms.map { |power, _| power } 161 | .sort 162 | .reverse 163 | .map { |power| @monoms[power] } 164 | end 165 | 166 | def to_s 167 | monoms = to_a 168 | # We take care if we have only 1 Monom (usually, zero) 169 | return monoms[0].to_s if monoms.length == 1 170 | 171 | monoms.select { |monom| monom.coeff != 0 }.map(&:to_s).join(' + ') 172 | end 173 | 174 | def derivate 175 | Polynomial.new to_a.map(&:derivate) 176 | end 177 | 178 | protected 179 | 180 | attr_reader :monoms 181 | end 182 | -------------------------------------------------------------------------------- /week04/2-Polynoms-And-Derivates/solution_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require_relative 'solution' 3 | 4 | class MonomTest < Minitest::Test 5 | def test_monom_to_s 6 | m = Monom.new(2, 'x', 5) 7 | assert_equal '2*x^5', m.to_s 8 | end 9 | 10 | def test_monom_to_s_when_power_is_1 11 | m = Monom.new(2, 'x', 1) 12 | assert_equal '2*x', m.to_s 13 | end 14 | 15 | def test_monom_to_s_when_constant 16 | m = Monom.constant(2) 17 | assert_equal '2', m.to_s 18 | end 19 | 20 | def test_monom_to_s_when_constant_and_0 21 | m = Monom.constant(0) 22 | assert_equal '0', m.to_s 23 | end 24 | 25 | def test_if_monom_is_constant_when_it_is_not 26 | m = Monom.new(2, 'x', 5) 27 | assert_equal false, m.constant? 28 | end 29 | 30 | def test_if_monom_is_constant_when_created_with_new 31 | m = Monom.new(2, 'x', 0) 32 | assert m.constant? 33 | end 34 | 35 | def test_if_monom_is_constant_when_created_with_constant 36 | m = Monom.constant(2) 37 | assert m.constant? 38 | end 39 | 40 | def test_monom_equal_power_when_powers_are_equal 41 | m1 = Monom.new(2, 'x', 5) 42 | m2 = Monom.new(3, 'x', 5) 43 | 44 | assert m1.equal_power? m2 45 | end 46 | 47 | def test_monom_equal_power_when_powers_are_not_equal 48 | m1 = Monom.new(2, 'x', 5) 49 | m2 = Monom.new(3, 'x', 6) 50 | 51 | assert_equal false, m1.equal_power?(m2) 52 | end 53 | 54 | def test_adding_monoms_with_equal_var_and_power 55 | m1 = Monom.new(1, 'x', 2) 56 | m2 = Monom.new(2, 'x', 2) 57 | expected = Monom.new(3, 'x', 2) 58 | 59 | assert_equal expected, m1 + m2 60 | end 61 | 62 | def test_adding_constants 63 | m1 = Monom.constant(2) 64 | m2 = Monom.constant(3) 65 | expected = Monom.constant(5) 66 | 67 | assert_equal expected, m1 + m2 68 | end 69 | 70 | def test_derivate_constant 71 | m1 = Monom.constant(1) 72 | expected = Monom.constant(0) 73 | 74 | assert_equal expected, m1.derivate 75 | end 76 | 77 | def test_derivate_power_equal_to_1 78 | m1 = Monom.new(5, 'x', 1) 79 | expected = Monom.constant(5) 80 | 81 | assert_equal expected, m1.derivate 82 | end 83 | 84 | def test_standard_derivate 85 | m1 = Monom.new(2, 'x', 5) 86 | expected = Monom.new(10, 'x', 4) 87 | 88 | assert_equal expected, m1.derivate 89 | end 90 | 91 | def test_monom_parse_with_all_parts 92 | input = '2*x^5' 93 | expected = Monom.new(2, 'x', 5) 94 | 95 | assert_equal(expected, Monom.parse(input)) 96 | end 97 | 98 | def test_monom_parse_with_more_than_1_digit_coeff_and_power 99 | input = '22*x^55' 100 | expected = Monom.new(22, 'x', 55) 101 | 102 | assert_equal(expected, Monom.parse(input)) 103 | end 104 | 105 | def test_monom_parse_with_power_1 106 | input = '2*x' 107 | expected = Monom.new(2, 'x', 1) 108 | 109 | assert_equal expected, Monom.parse(input) 110 | end 111 | 112 | def test_monom_parse_when_is_only_var 113 | input = 'x' 114 | expected = Monom.new(1, 'x', 1) 115 | 116 | assert_equal expected, Monom.parse(input) 117 | end 118 | 119 | def test_monom_parse_constant 120 | input = '2' 121 | expected = Monom.constant(2) 122 | 123 | assert_equal expected, Monom.parse(input) 124 | end 125 | 126 | def test_monom_parse_constant_with_more_than_1_digit 127 | input = '12345' 128 | expected = Monom.constant(123_45) 129 | 130 | assert_equal expected, Monom.parse(input) 131 | end 132 | end 133 | 134 | class TestPolynomial < Minitest::Test 135 | def test_empty_polynomial_to_s 136 | p = Polynomial.new 137 | assert_equal '', p.to_s 138 | end 139 | 140 | def test_empty_polynomial_to_a 141 | p = Polynomial.new 142 | assert_equal [], p.to_a 143 | end 144 | 145 | def test_polynomial_to_a_with_one_monom 146 | p = Polynomial.new 147 | m = Monom.new(2, 'x', 5) 148 | 149 | p << m 150 | 151 | assert_equal [m], p.to_a 152 | end 153 | 154 | def test_polynomial_to_s_with_monoms 155 | p = Polynomial.new 156 | p << Monom.new(2, 'x', 5) 157 | p << Monom.new(3, 'x', 2) 158 | p << Monom.new(5, 'x', 1) 159 | 160 | expected = '2*x^5 + 3*x^2 + 5*x' 161 | 162 | assert_equal expected, p.to_s 163 | end 164 | 165 | def test_polynomial_derivative 166 | p = Polynomial.new 167 | p << Monom.new(2, 'x', 5) 168 | p << Monom.new(3, 'x', 2) 169 | p << Monom.new(5, 'x', 1) 170 | 171 | expected = '10*x^4 + 6*x + 5' 172 | assert_equal expected, p.derivate.to_s 173 | end 174 | 175 | def test_polynomial_derivative_when_we_have_0_in_the_derivative 176 | p = Polynomial.new 177 | p << Monom.new(2, 'x', 5) 178 | p << Monom.new(3, 'x', 2) 179 | p << Monom.constant(5) 180 | 181 | expected = '10*x^4 + 6*x' 182 | assert_equal expected, p.derivate.to_s 183 | end 184 | 185 | def test_polynomial_derivative_of_constants 186 | p = Polynomial.new 187 | p << Monom.constant(5) 188 | 189 | expected = '0' 190 | assert_equal expected, p.derivate.to_s 191 | end 192 | 193 | def test_polynomial_parse_to_equal_a_polynomial 194 | input = '2*x^3+x' 195 | expected = Polynomial.new Monom.new(2, 'x', 3), Monom.new(1, 'x', 1) 196 | 197 | assert_equal expected, Polynomial.parse(input) 198 | end 199 | 200 | def test_polynomial_parse_to_get_the_right_string 201 | input = '2*x^2 + x^2' 202 | expected = '3*x^2' 203 | 204 | assert_equal expected, Polynomial.parse(input).to_s 205 | end 206 | end 207 | -------------------------------------------------------------------------------- /week04/2-Polynoms-And-Derivates/start.rb: -------------------------------------------------------------------------------- 1 | require_relative 'solution' 2 | 3 | def main 4 | p = Polynomial.parse(ARGV[0]) 5 | puts p.derivate.to_s 6 | end 7 | 8 | main 9 | -------------------------------------------------------------------------------- /week04/README.md: -------------------------------------------------------------------------------- 1 | # Basic Object Model 2 | 3 | ## Resources: 4 | 5 | * [Basic Object Model slides](https://ruby.hackbulgaria.com/lectures/07#/) 6 | * [How Ruby's case statement works and `===` operator](http://www.skorks.com/2009/08/how-a-ruby-case-statement-works-and-what-you-can-do-with-it/) 7 | * [Why Ruby's String no longer mixes Enumerable?](http://stackoverflow.com/questions/2266534/ruby-string-no-longer-mixes-in-enumerable-in-1-9) 8 | * [What is `attr_accessor` in Ruby and how can we define it?](http://stackoverflow.com/questions/4370960/what-is-attr-accessor-in-ruby) 9 | * [What is `to_proc` and how does it work?](http://stackoverflow.com/questions/14881125/what-does-to-proc-method-mean) 10 | * [Ruby's class heirarchy](http://rubylearning.com/images/class.png) 11 | * [Why does Ruby have both private and protected methods?](http://stackoverflow.com/questions/3534449/why-does-ruby-have-both-private-and-protected-methods) 12 | * [What Is the Difference Between a Block, a Proc, and a Lambda in Ruby?](http://awaxman11.github.io/blog/2013/08/05/what-is-the-difference-between-a-block/) 13 | 14 | ## Code Examples 15 | 16 | * [private.rb](private.rb) 17 | * [protected.rb](protected.rb) 18 | * [to_proc.rb](to_proc.rb) 19 | -------------------------------------------------------------------------------- /week04/private.rb: -------------------------------------------------------------------------------- 1 | class Panda 2 | private 3 | def who_can_call_me 4 | puts "Called a private method" 5 | end 6 | 7 | public 8 | def call1 9 | who_can_call_me 10 | end 11 | 12 | def call2 13 | self.who_can_call_me 14 | end 15 | end 16 | 17 | # The following calls: 18 | # Panda.new.who_can_call_me -> ERROR, NOT WORKING 19 | # Panda.new.call1 -> WORKS, ITS OK 20 | # Panda.new.call2 -> ERROR, NOT WORKING - explicit receiver 21 | # Panda.new.send(:who_can_call_me) -> WORKS, ITS OK 22 | -------------------------------------------------------------------------------- /week04/protected.rb: -------------------------------------------------------------------------------- 1 | class Panda 2 | protected 3 | def who_can_call_me 4 | puts "Called a private method" 5 | end 6 | 7 | public 8 | def call1 9 | who_can_call_me 10 | end 11 | 12 | def call2 13 | self.who_can_call_me 14 | end 15 | end 16 | 17 | # The following calls: 18 | # Panda.new.who_can_call_me -> ERROR, NOT WORKING 19 | # Panda.new.call1 -> WORKS, ITS OK 20 | # Panda.new.call2 -> WORKS, ITS OK 21 | # Panda.new.send(:who_can_call_me) -> WORKS, ITS OK 22 | -------------------------------------------------------------------------------- /week04/symbol.rb: -------------------------------------------------------------------------------- 1 | class Hash 2 | def to_proc 3 | lambda do |key| 4 | fetch key, nil 5 | end 6 | end 7 | end 8 | 9 | class Symbol 10 | def to_proc 11 | lambda do |x, *args| 12 | x.public_send self, *args 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /week04/to_proc.rb: -------------------------------------------------------------------------------- 1 | class Symbol 2 | def to_proc 3 | lambda do |object, *args| 4 | p self 5 | p object 6 | p args 7 | object.public_send(self, *args) 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /week05/1-Shopping-Cart/README.md: -------------------------------------------------------------------------------- 1 | # Shopping Card Problem - from 2011 FMI's Ruby course 2 | 3 | The guys over the FMI Ruby course are amazing. They have great problems. 4 | 5 | In order to test our skills with the object model & exceptions so far, we are going to implement a shopping card with various ways to give discounts. 6 | 7 | Holidays are coming, so we should be ready for this :) 8 | 9 | ## The Problem 10 | 11 | [You can find the problem description here](http://2011.fmi.ruby.bg/tasks/3) 12 | 13 | ## Tests 14 | 15 | Try to write tests for this problem. [You will find sample tests in the problem's folder here.](shopping_cart_test.rb) 16 | 17 | In order to run tests, you need to have `shopping_cart.rb` file in the same directory as the tests. Then run: 18 | 19 | ``` 20 | $ ruby shopping_cart_test.rb 21 | ``` 22 | -------------------------------------------------------------------------------- /week05/1-Shopping-Cart/shopping_cart_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | 3 | require_relative 'shopping_cart.rb' 4 | 5 | class ShoppingCardTest < Minitest::Test 6 | def test_register_item_in_inventory 7 | inventory = Inventory.new 8 | 9 | inventory.register 'Green Tea', '1.99' 10 | inventory.register 'Red Tea', '2.49' 11 | inventory.register 'Earl Grey', '1.49' 12 | 13 | assert_equal 3, inventory.length 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /week05/README.md: -------------------------------------------------------------------------------- 1 | # Week 5 - Before XMas 2 | 3 | Lectures and materials for this week: 4 | 5 | * [Basic Objectg Model pt. 2](https://ruby.hackbulgaria.com/lectures/08#/) 6 | * [Exceptions](https://ruby.hackbulgaria.com/lectures/09#/) 7 | -------------------------------------------------------------------------------- /week06/1-Panda-Social-Network/README.md: -------------------------------------------------------------------------------- 1 | # We are going to make a social networks for Pandas 2 | 3 | This is the next big thing. We promise! 4 | 5 | ## Panda 6 | 7 | For our social network, we are going to need a `Panda` class which behaves like that: 8 | 9 | ```ruby 10 | ivo = Panda.new("Ivo", "ivo@pandamail.com", "male") 11 | 12 | ivo.name == "Ivo" # true 13 | ivo.email == "ivo@pandamail.com" # true 14 | ivo.gender == "male" # true 15 | ivo.male? == true # true 16 | ivo.female? == false # true 17 | ``` 18 | 19 | The `Panda` class also should be possible to: 20 | 21 | * Be turned into a string 22 | * Be hashed and used as a key in a dictionary 23 | 24 | Two `Panda` instances are equal if they have matching `name`, `email` and `gender` attributes. 25 | 26 | ## SocialNetwork 27 | 28 | Now it is time for our social network! 29 | 30 | Implement a class, called `PandaSocialNetwork`, which has the following public methods: 31 | 32 | * `add_panda(panda)` - this method adds a `panda` to the social network. The panda has 0 friends for now. If the panda is already in the network, raise an `PandaAlreadyThere` error. 33 | * `has_panda(panda)` - returns `true` or `false` if the `panda` is in the network or not. 34 | * `make_friends(panda1, panda2)` - makes the two pandas friends. Raise `PandasAlreadyFriends` if they are already friends. **The friendship is two-ways** - `panda1` is a friend with `panda2` and `panda2` is a friend with `panda1`. **If `panda1` or `panda2` are not members of the network, add them!** 35 | * `are_friends(panda1, panda2)` - returns `true` if the pandas are friends. Otherwise, `false` 36 | * `friends_of(panda)` - returns a list of `Panda` with the friends of the given `panda`. Returns `false` if the `panda` is not a member of the network. 37 | * `connection_level(panda1, panda2)` - returns the connection level between `panda1` and `panda2`. If they are friends, the level is 1. Otherwise, count the number of friends you need to go through from `panda` in order to get to `panda2`. If they are not connected at all, return `-1`! Return `false` if one of the pandas are not member of the network. 38 | * `are_connected(panda1, panda2)` - return `true` if the pandas are connected somehow, between friends, or `false` otherwise. 39 | * `how_many_gender_in_network(level, panda, gender)` - returns the number of `gender` pandas (male of female) that in the `panda` network in that many `level`s deep. If `level == 2`, we will have to look in all friends of `panda` and all of their friends too. And count 40 | 41 | ## An example 42 | 43 | ```ruby 44 | network = PandaSocialNetwork.new 45 | ivo = Panda.new("Ivo", "ivo@pandamail.com", "male") 46 | rado = Panda.new("Rado", "rado@pandamail.com", "male") 47 | tony = Panda.new("Tony", "tony@pandamail.com", "female") 48 | 49 | network.add_panda(ivo) 50 | network.add_panda(rado) 51 | network.add_panda(tony) 52 | 53 | network.make_friends(ivo, rado) 54 | network.make_friends(rado, tony) 55 | 56 | network.connection_level(ivo, rado) == 1 # true 57 | network.connection_level(ivo, tony) == 2 # true 58 | 59 | network.how_many_gender_in_network(1, rado, "female") == 1 # true 60 | ``` 61 | 62 | ## Save and load from file 63 | 64 | The next thing our social network is going to do is `saving to your hard drive` 65 | 66 | ### social_network.save(file_name) 67 | 68 | Implement a method, that saves a social network instance to a file. 69 | 70 | Think about what format you should use! 71 | 72 | ### social_network.load(file_name) 73 | 74 | Implement a static method, that loads a social network from a saved file and returns a new social network instance. 75 | 76 | ## Bonus: Save and load from multiple formats 77 | 78 | Saved and loaded the file in your custom format? Great! Now let's take it up a level and make it so we can save and load social network instances in `json`, `yaml` and `xml` on top of the format we already have. 79 | 80 | Think about the interface of the `#save` and `#load` methods. Will it change? How can we keep the current interface? Should we `if/else` in `#save` or `#load` for the different formats? Is there a better way??? 81 | 82 | ## Bonus: Put it all in one namespace 83 | 84 | Let's follow all the best practices and leak only one constant to `Object`. 85 | -------------------------------------------------------------------------------- /week06/1-Panda-Social-Network/group.rb: -------------------------------------------------------------------------------- 1 | def group_consecutive(items, n) 2 | arr = [] 3 | while items.length > 0 4 | arr += [items.take(n)] 5 | items = items.drop(n) 6 | end 7 | 8 | arr 9 | end 10 | 11 | students = File.read('students').split("\n").shuffle 12 | 13 | groups = group_consecutive(students, 2) 14 | 15 | puts groups.map { |group| "#{group[0]} и #{group[1]}" } 16 | -------------------------------------------------------------------------------- /week06/1-Panda-Social-Network/groups: -------------------------------------------------------------------------------- 1 | Виктор Радивчев и Кристиян Тодоров 2 | Звезделина Димитрова и Радослав Ангелов 3 | Цветина Георгиева и Александър Ковачев 4 | Стефан Петров и Алейдин Караимин 5 | Емил Кирилов и Симона Стоянова 6 | Павлина Вацева и Георги Марков 7 | Антон Великов и Михаил Георгиев 8 | Александрина Каракехайова и Мария Шахпазова 9 | Петко Петков и Юлиан Дудин 10 | Александър Пирнарев и Мила Русева 11 | Александър Ненов и Никола Жишев 12 | Таня Пенкова и Георги Линдарев 13 | Веселин Стоянов и Николай Вълчанов 14 | Петя Панайотова и 15 | -------------------------------------------------------------------------------- /week06/1-Panda-Social-Network/students: -------------------------------------------------------------------------------- 1 | Никола Жишев 2 | Николай Вълчанов 3 | Мила Русева 4 | Цветина Георгиева 5 | Петко Петков 6 | Павлина Вацева 7 | Таня Пенкова 8 | Александрина Каракехайова 9 | Виктор Радивчев 10 | Александър Ковачев 11 | Александър Ненов 12 | Мария Шахпазова 13 | Радослав Ангелов 14 | Емил Кирилов 15 | Александър Пирнарев 16 | Михаил Георгиев 17 | Георги Марков 18 | Георги Линдарев 19 | Стефан Петров 20 | Антон Великов 21 | Звезделина Димитрова 22 | Кристиян Тодоров 23 | Алейдин Караимин 24 | Юлиан Дудин 25 | Петя Панайотова 26 | Симона Стоянова 27 | Веселин Стоянов 28 | -------------------------------------------------------------------------------- /week06/README.md: -------------------------------------------------------------------------------- 1 | # Week 6 - Git, GitHub, Teamwork & Graphs 2 | 3 | We are going to do a bunch of problems, while working in teams of 2. 4 | 5 | In order to have a nice flow, check the following materials: 6 | 7 | * [Getting started with Git](https://git-scm.com/book/en/v2/Getting-Started-Git-Basics) 8 | * [Getting your project on GitHub](https://guides.github.com/introduction/getting-your-project-on-github/) 9 | * [Git - the simple guide without deep shit!](http://rogerdudler.github.io/git-guide/) 10 | * [Non-programmer git intro](http://blog.scottlowe.org/2015/01/14/non-programmer-git-intro/) 11 | * [GitHub Flow & How to work in teams](https://guides.github.com/introduction/flow/) 12 | * [What is a Git branch](http://git-scm.com/book/en/v1/Git-Branching-What-a-Branch-Is) 13 | * [What is a Pull Request](https://help.github.com/articles/using-pull-requests/) 14 | * [How to make Sublime default commit message editor for git](https://help.github.com/articles/associating-text-editors-with-git/#using-sublime-text-as-your-editor) 15 | * [How to make vim your default commit message editor for git](http://stackoverflow.com/questions/2596805/how-do-i-make-git-use-the-editor-of-my-choice-for-commits) 16 | * [GitHub training videos](https://www.youtube.com/watch?v=8oRjP8yj2Wo&index=1&list=PLg7s6cbtAD165JTRsXh8ofwRw0PqUnkVH) 17 | * [A series of Atlasian Git tutorials](https://www.atlassian.com/git/) 18 | * [Git cheat sheet with the most popular commands](https://training.github.com/kit/downloads/github-git-cheat-sheet.pdf) 19 | 20 | ## Object equality 21 | 22 | We recommend the following materials on object equality in Ruby: 23 | 24 | * [What's the difference between equal?, eql?, ===, and ==](http://stackoverflow.com/questions/7156955/whats-the-difference-between-equal-eql-and) 25 | * [`alias` vs. `method_alias`](http://blog.bigbinary.com/2012/01/08/alias-vs-alias-method.html) 26 | -------------------------------------------------------------------------------- /week06/bfs.rb: -------------------------------------------------------------------------------- 1 | require 'set' 2 | 3 | class Graph 4 | def initialize 5 | @graph = {} 6 | end 7 | 8 | def add_vertex(vertex_name) 9 | return if @graph.key? vertex_name 10 | 11 | @graph[vertex_name] = [] 12 | end 13 | 14 | def connect(v1, v2) 15 | add_vertex(v1) unless @graph.key? v1 16 | add_vertex(v2) unless @graph.key? v2 17 | 18 | @graph[v1] << v2 19 | @graph[v2] << v1 20 | end 21 | 22 | # This BFS yields each vertex and its level that can be reached from start_vertex 23 | def bfs(start_vertex) 24 | q = Queue.new 25 | visited = Set.new 26 | path = { start_vertex => nil } 27 | 28 | q << [0, start_vertex] 29 | visited << start_vertex 30 | 31 | until q.empty? 32 | level, current = q.pop 33 | yield level, current if block_given? 34 | 35 | unvisited = @graph[current].select { |v| !visited.include? v } 36 | 37 | unvisited.each do |v| 38 | path[v] = current 39 | q << [level + 1, v] 40 | visited << v 41 | end 42 | end 43 | 44 | path 45 | end 46 | 47 | def connected?(v1, v2) 48 | bfs(v1) do |_level, node| 49 | return true if node == v2 50 | end 51 | 52 | false 53 | end 54 | 55 | def path_between(v1, v2) 56 | path_table = bfs(v1) 57 | path = [] 58 | 59 | return false unless path_table.key? v2 60 | current = v2 61 | 62 | until current.nil? 63 | path << current 64 | current = path_table[current] 65 | end 66 | 67 | path.reverse 68 | end 69 | end 70 | 71 | g = Graph.new 72 | 73 | g.connect('1', '2') 74 | g.connect('2', '3') 75 | g.connect('1', '3') 76 | g.connect('3', '4') 77 | g.connect('3', '5') 78 | g.connect('1', '5') 79 | 80 | g.add_vertex('6') 81 | 82 | g.bfs '1' do |level, node| 83 | puts "Level: #{level} for node: #{node}" 84 | end 85 | 86 | puts g.connected? '1', '4' 87 | puts g.connected? '1', '6' 88 | 89 | puts g.path_between('1', '4').join ' -> ' 90 | -------------------------------------------------------------------------------- /week06/git.md: -------------------------------------------------------------------------------- 1 | # Неща за Git и GitHub 2 | 3 | Важни неща, които да гледаме в началото. 4 | 5 | ## Какво да направим първо? 6 | 7 | **ПЪРВО** 8 | 9 | Хубаво е да се направи repository в GitHub и да се добавят нужните collaborators. 10 | 11 | Добавеният човек трябва да потвърди по email. 12 | 13 | **ВТОРО** 14 | 15 | Клонирайте repository-то локално при вас. 16 | 17 | Това става, като вземете url-а от GitHub, който е или **SSH** или **HTTPS**. 18 | 19 | След това клонирането става със следната команда (на мястото на URL-а трябва да стои коректния URL) 20 | 21 | ``` 22 | $ cd /home/name/some/path/to/projects 23 | $ git clone git@github.com:HackBulgaria/Programming101-Ruby.git 24 | ``` 25 | 26 | **ТРЕТО** 27 | 28 | Разделете си работата, правете бранчове, отваряйте pull requests. 29 | 30 | ## Workflow: 31 | 32 | 0. master-а винаги (ако може) е stable! 33 | 1. Всеки ще работи в собствен branch. 34 | 2. Често push-вай за добро! 35 | 3. При нужда от merge се отваря Pull Request от бранча, в който се работи (compare) към бранча, в който искате да merge-вате (base) 36 | 37 | ## Команди 38 | 39 | Основните команди за добавяне са следните: 40 | 41 | ``` 42 | $ git add some_file.rb 43 | $ git commit -m 'Commit message here' 44 | $ git push origin master 45 | ``` 46 | 47 | Ако искате да си направите собствен branch: 48 | 49 | ``` 50 | $ git branch feature_branch_name 51 | $ git checkout feature_branch_name 52 | ``` 53 | 54 | След това работите нормално. 55 | 56 | Ако искате да качите нещата от бранча в GitHub: 57 | 58 | ``` 59 | $ git push origin feature_branch_name 60 | ``` 61 | 62 | След това може да отворите Pull Request от GitHub 63 | 64 | ## Събиране на промените в master 65 | 66 | Имаме следната ситуация. 67 | 68 | Има два бранча - `A` и `B` 69 | 70 | И двата бранча са готови да отидат в `master`. 71 | 72 | Тогава, за да се обединят промените от двата бранча, се правят следните неща: 73 | 74 | 1. Спирате да работите и да commit-вате и push-вате нови неща. 75 | 2. Отварят се (ако вече не са) нови Pull Requests от `A` към `master` и `B` към master 76 | 3. Един от двамата merge-ва през GitHub двата бранча в `master` 77 | 4. И двамата, локално изпълняват следните команди: 78 | 79 | ``` 80 | $ git checkout master 81 | $ git pull origin master 82 | ``` 83 | 84 | Това ще свали промените от GitHubл локално в repo-то. 85 | 86 | След това, ако искате да вземете промените в съответния branch, в който се работи локално: 87 | 88 | ``` 89 | $ git checkout A 90 | $ git merge master 91 | $ git push origin A 92 | ``` 93 | -------------------------------------------------------------------------------- /week07/1-Roman-Numerals/README.md: -------------------------------------------------------------------------------- 1 | # Roman Numerals 2 | 3 | Roman literals are pain to process. Mentally, physically, sonically?1? There 4 | is a reason for us to write the Arabic numbers we do today. 5 | 6 | However, we can use them to impress our friends! 7 | 8 | Let's work on an API that does this: 9 | 10 | ```ruby 11 | >> Roman::LXXIII 12 | => 73 13 | >> Roman::LXXIV 14 | => 74 15 | >> Roman::LXXV 16 | => 75 17 | ``` 18 | -------------------------------------------------------------------------------- /week08/2-meta/.rubocop.yml: -------------------------------------------------------------------------------- 1 | Style/Documentation: 2 | Enabled: false 3 | -------------------------------------------------------------------------------- /week08/2-meta/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rake' 4 | gem 'minitest' 5 | gem 'rubocop' 6 | -------------------------------------------------------------------------------- /week08/2-meta/README.md: -------------------------------------------------------------------------------- 1 | # Meta 2 | 3 | To exercise our meta programming let's do some problems! 4 | 5 | ## Object#singleton_class 6 | 7 | In older versions of Ruby, there wasn't `Object#singleton_class`. Let's 8 | implement our own! 9 | 10 | ## Object#define_singleton_method 11 | 12 | While at it, define `Object#define_singleton_method` as well. 13 | 14 | ## String#to_proc 15 | 16 | Symbol#to_proc wasn't around in older Rubies. It came out of Active Support, 17 | the community driven monkey patches used by Ruby on Rails. Let's try to start 18 | another trend by implementing String#to_proc. 19 | 20 | Bonus: 21 | 22 | Let's make this working: 23 | 24 | ```ruby 25 | [2, 3, 4, 5].map(&'succ.succ') #=> [4, 5, 6, 7] 26 | ``` 27 | 28 | ## Module#private_attr_accessor 29 | 30 | Pretty simple, `Module#private_attr_accesssor` should act like 31 | `attr_accessor`, but always create private accessors. Some kids like to access 32 | their data through accessors only. Let them do that. 33 | 34 | Of course, you should implement `private_attr_reader` and 35 | `private_attr_writter`. The `protected_*` family as well. 36 | 37 | ## Module#cattr_accessor 38 | 39 | Define an accessor exposing API for class variables. This includes 40 | `Module#cattr_reader` and `Module#cattr_writter`. 41 | 42 | As a bonus, let us specify default values, like this: 43 | 44 | ```ruby 45 | class TestCase 46 | cattr_accessor(:tests) { [] } 47 | end 48 | 49 | TestCase.tests #=> [] 50 | ``` 51 | 52 | ## Blackhole Object 53 | 54 | Nils are annoying, right? They get into your way, they crash the program. Such 55 | a bummer! 56 | 57 | Instead of fixing our program, let's hide our nils by monkey patching `nil` in 58 | such a way, that we no longer get a `NoMethodError` when we access a method 59 | that doesn't exist. 60 | 61 | Hint: You have to patch `NilClass`. 62 | 63 | ## Proxy 64 | 65 | Create a Proxy class, that delegates every method call to its target object. 66 | 67 | ```ruby 68 | proxy = Proxy.new [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 69 | 70 | proxy.size #=> 10 71 | proxy.sizes #=> NoMethodError 72 | proxy[0] #=> 1 73 | proxy & [2] #=> [2] 74 | 75 | proxy.respond_to? :size #=> true 76 | proxy.respond_to? :zzzz #=> false 77 | ``` 78 | 79 | Bonus: 80 | 81 | Make sure it respects `#method_missing` and `#respond_to_missing?` to the 82 | target class. 83 | 84 | ## Object#delegate 85 | 86 | Delegating a method to existing object is a commonly used task in Rubyland. 87 | Let's make it easier by defining a helper. 88 | 89 | ```ruby 90 | User = Struct.new(:first_name, :last_name) 91 | 92 | class Invoce 93 | delegate :fist_name, to: '@user' 94 | delegate :last_name, to: '@user' 95 | 96 | def initialize(user) 97 | @user = user 98 | end 99 | end 100 | 101 | user = User.new 'Genadi', 'Samokovarov' 102 | invoice = Invoce.new(user) 103 | 104 | invoice.fist_name #=> "Genadi" 105 | invoice.last_name #=> "Samokovarov" 106 | ``` 107 | 108 | ## FMI 109 | 110 | 2008 was a great year for Ruby! Check out the [metaprogramming problems], our 111 | friends over FMI gave for that year. 112 | 113 | [metaprogramming problems]: http://2008.fmi.ruby.bg/tasks/2.html 114 | -------------------------------------------------------------------------------- /week08/2-meta/Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake/testtask' 2 | require 'rubocop/rake_task' 3 | 4 | RuboCop::RakeTask.new 5 | 6 | Rake::TestTask.new do |t| 7 | t.test_files = FileList['*_test.rb'] 8 | t.verbose = true 9 | end 10 | 11 | task default: %i(test rubocop) 12 | -------------------------------------------------------------------------------- /week08/2-meta/solution.rb: -------------------------------------------------------------------------------- 1 | class Object 2 | def define_singleton_method(name, method = nil, &block) 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /week08/2-meta/solution_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | 3 | require_relative 'solution' 4 | 5 | class SolutionTest < Minitest::Test 6 | def test_the_truth 7 | obj = Object.new 8 | obj.define_singleton_method(:foo) { 42 } 9 | 10 | assert_equal 42, obj.foo 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /week08/3-active-support/.rubocop.yml: -------------------------------------------------------------------------------- 1 | Style/Documentation: 2 | Enabled: false 3 | -------------------------------------------------------------------------------- /week08/3-active-support/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rake' 4 | gem 'minitest' 5 | gem 'rubocop' 6 | -------------------------------------------------------------------------------- /week08/3-active-support/README.md: -------------------------------------------------------------------------------- 1 | # Active Support 2 | 3 | [Active Support] is the Ruby on Rails component responsible for providing Ruby 4 | language extensions, utilities, and other transversal stuff. 5 | 6 | It offers a richer bottom-line at the language level, targeted both at the 7 | development of Rails applications, and at the development of Ruby on Rails 8 | itself. 9 | 10 | We'll be using it throughout all of our Ruby on Rails applications, so why not 11 | reimplement some of it functionalities for fun and profit? 12 | 13 | ## Object#blank? 14 | 15 | In Ruby, everything, except `false` and `nil`, is truthy. However, in a lot of 16 | cases we care whether or not we have useful data, and not whether or not we 17 | have a truthy, or falsy, Ruby object. This is where `Object#blank?` comes 18 | into play. 19 | 20 | The following values are considered to be blank in a Rails application: 21 | 22 | - nil and false, 23 | - strings composed only of whitespace (see note below), 24 | - empty arrays and hashes, and 25 | - any other object that responds to empty? and is empty. 26 | 27 | Note that numbers are not mentioned. In particular, 0 and 0.0 are not blank. 28 | 29 | ## Object#present? 30 | 31 | `Object#present?` is the opposite of `Object#blank?`. 32 | 33 | ## Object#presence 34 | 35 | The presence method returns its receiver if `present?`, and `nil` otherwise. It is 36 | useful for idioms like this: 37 | 38 | ```ruby 39 | host = config[:host].presence || 'localhost' 40 | ``` 41 | 42 | ## Object#try 43 | 44 | When you want to call a method on an object only if it is not `nil`, the simplest 45 | way to achieve it is with conditional statements, adding unnecessary clutter. 46 | The alternative is to use try. `try` is like `Object#send` except that it returns 47 | `nil` if sent to `nil.` 48 | 49 | Here is an example: 50 | 51 | ```ruby 52 | # Without try: 53 | unless @number.nil? 54 | @number.next 55 | end 56 | ``` 57 | 58 | ```ruby 59 | # With try: 60 | @number.try(:next) 61 | ``` 62 | 63 | Another example is this code from `ActiveRecord::ConnectionAdapters::AbstractAdapter` 64 | where @logger could be `nil.` You can see that the code uses try and avoids an 65 | unnecessary check. 66 | 67 | ```ruby 68 | def log_info(sql, name, ms) 69 | if @logger.try(:debug?) 70 | name = '%s (%.1fms)' % [name || 'SQL', ms] 71 | @logger.debug(format_log_entry(name, sql.squeeze(' '))) 72 | end 73 | end 74 | ``` 75 | 76 | `try` can also be called without arguments but a block, which will only be 77 | executed if the object is not nil: 78 | 79 | ```ruby 80 | @person.try { |p| "#{p.first_name} #{p.last_name}" } 81 | ``` 82 | 83 | Even more, if you skip the parameter, method calls go to the target object. 84 | 85 | ```ruby 86 | @person.try { "#{first_name} #{last_name}" } 87 | ``` 88 | 89 | ### Do or do not, there is no try 90 | 91 | A lot of people consider `Object#try` an anti-pattern. Why do you think that 92 | is? 93 | 94 | ## StringInquirer 95 | 96 | `StringInquiry` is a special kind of `String`. One that let's you ask the 97 | question like: 98 | 99 | > Are you the string production? 100 | 101 | It may sound silly, but its quite handy when it comes to code readability. Here 102 | is an example: 103 | 104 | ```ruby 105 | # Without StringInqury 106 | 107 | environment = 'production' 108 | 109 | if environment == 'production' 110 | # Please don't write code like that in a production application. 111 | end 112 | 113 | # With StringInquiry: 114 | 115 | environment = StringInquirer.new('production') 116 | if environment.production? 117 | # Seriously, don't even try it. 118 | end 119 | 120 | StringInquirer.new('active').inactive? # => false 121 | ``` 122 | 123 | While at it, add a `String#inquiry` method that converts a string into a 124 | `StringInquirer` object making equality checks even prettier. 125 | 126 | ```ruby 127 | "production".inquiry.production? # => true 128 | "active".inquiry.inactive? # => false 129 | ``` 130 | 131 | [Active Support]: http://guides.rubyonrails.org/active_support_core_extensions.html 132 | -------------------------------------------------------------------------------- /week08/3-active-support/Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake/testtask' 2 | require 'rubocop/rake_task' 3 | 4 | RuboCop::RakeTask.new 5 | 6 | Rake::TestTask.new do |t| 7 | t.test_files = FileList['*_test.rb'] 8 | t.verbose = true 9 | end 10 | 11 | task default: %i(test rubocop) 12 | -------------------------------------------------------------------------------- /week08/3-active-support/solution.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackBulgaria/Programming101-Ruby/7bd525eca6c89f3fd4aad1948dbdb20ede0490cf/week08/3-active-support/solution.rb -------------------------------------------------------------------------------- /week08/3-active-support/solution_test.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | 3 | require_relative 'solution' 4 | 5 | class SolutionTest < Minitest::Test 6 | def test_the_truth 7 | assert true 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /week09/1-Micro-Blog/.rubocop.yml: -------------------------------------------------------------------------------- 1 | Style/Documentation: 2 | Enabled: false 3 | -------------------------------------------------------------------------------- /week09/1-Micro-Blog/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rake' 4 | gem 'minitest' 5 | gem 'rubocop' 6 | 7 | gem 'rack' 8 | gem 'sinatra' 9 | gem 'sqlite3' 10 | -------------------------------------------------------------------------------- /week09/1-Micro-Blog/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | ast (2.2.0) 5 | minitest (5.8.4) 6 | parser (2.3.0.2) 7 | ast (~> 2.2) 8 | powerpack (0.1.1) 9 | rack (1.6.4) 10 | rack-protection (1.5.3) 11 | rack 12 | rainbow (2.1.0) 13 | rake (10.5.0) 14 | rubocop (0.36.0) 15 | parser (>= 2.3.0.0, < 3.0) 16 | powerpack (~> 0.1) 17 | rainbow (>= 1.99.1, < 3.0) 18 | ruby-progressbar (~> 1.7) 19 | ruby-progressbar (1.7.5) 20 | sinatra (1.4.7) 21 | rack (~> 1.5) 22 | rack-protection (~> 1.4) 23 | tilt (>= 1.3, < 3) 24 | sqlite3 (1.3.11) 25 | tilt (2.0.2) 26 | 27 | PLATFORMS 28 | ruby 29 | 30 | DEPENDENCIES 31 | minitest 32 | rack 33 | rake 34 | rubocop 35 | sinatra 36 | sqlite3 37 | 38 | BUNDLED WITH 39 | 1.10.6 40 | -------------------------------------------------------------------------------- /week09/1-Micro-Blog/README.md: -------------------------------------------------------------------------------- 1 | # Micro Blog 2 | 3 | Hi guys & girls, 4 | 5 | The time has come for you to make your first web application for the course. 6 | 7 | For this task you will be using [Sinatra](http://www.sinatrarb.com/) to build a **microblogging application** 8 | 9 | **Note:** We do not want you to use any database! We want a pure Ruby solution that stores data in memory relying on arrays, hashes, sets and other structures. 10 | 11 | ## Routes 12 | 13 | Your application will have to provide the following interface: 14 | 15 | * `get '/'` - Display a view of all posts. 16 | * `get '/new'` - Displays a view for creating a new post. 17 | * `post '/new'` - Creates a new post. 18 | * `get '/42'` - Displays a view of the post with id equal to 42. 19 | * `delete '/42'` - Deletes the post with id equal to 42. 20 | 21 | ## Visuals & HTML 22 | 23 | We don't have any visual requirements so we'll leave the styling to you. 24 | 25 | To help yourself with the HTML, you can use the following cheat sheets (or anything else on the internet): 26 | 27 | * 28 | * 29 | 30 | ## Validations 31 | 32 | However, we require you to perform some validation: 33 | 34 | * We would like to see a suitable page if the requested blog post is not present. 35 | * It would be nice to display something on the index page if there are no blog posts yet. 36 | * We would like our blog posts to not exceed the limit of 256 characters. Make sure you check them before saving! 37 | 38 | ## Tags 39 | 40 | Tags. We want them too. 41 | 42 | Every sequence in a post that begins with `#` and continuous with a valid Ruby identifier is considered a tag. 43 | 44 | **A post could have many tags.** We want to be able to search for every post that is tagged in a particular way. 45 | 46 | `get '/search/ruby'` is the interface that we will use for searching for posts tagged with `#ruby`. Present them in the same fashion you present the posts on the index page. 47 | 48 | GL & HF! 49 | 50 | ## HTTP Client for communication 51 | 52 | Our microblog runs on a HTTP server. In order to create a new post, we need a `form` with `POST` method and action pointing to `/new` 53 | 54 | For diversity (and learning) reasons, we are going to create a simple console-based Ruby application, that will serve as a client to our microblog. 55 | 56 | Our client should read from some config / settings file where our blog is located as an URL. 57 | 58 | After this, we are looking for a similar interface: 59 | 60 | ``` 61 | $ ruby client.rb get all 62 | Displaying all blog posts: 63 | ... 64 | ``` 65 | 66 | ``` 67 | $ ruby client.rb get 42 68 | Displaying blog post with id 42: 69 | ... 70 | ``` 71 | 72 | ``` 73 | $ ruby client.rb get 43 74 | Blog post with id 43 not found. 75 | ``` 76 | 77 | ``` 78 | $ ruby client.rb delete 42 79 | Blog post with id 42 deleted. 80 | ``` 81 | 82 | ``` 83 | $ ruby client.rb create blog_post.txt 84 | Creating a new blog post with contents from blog_post.txt 85 | ... 86 | Created with id 1337! 87 | ``` 88 | 89 | ``` 90 | $ ruby client.rb search ruby 91 | Searching for blog posts with #ruby: 92 | ... 93 | ``` 94 | 95 | **Try to solve that problem using the plain `net/http` library** - 96 | -------------------------------------------------------------------------------- /week09/1-micro-blog/README.md: -------------------------------------------------------------------------------- 1 | # Microblog 2 | 3 | Recreate your Sintara, Rack microblogs in Ruby on Rails. 4 | 5 | ## Spoilers: 6 | 7 | Read about Rails generators. 8 | 9 | # Extension 10 | 11 | Our blog turn out very successful. Now is the time to extend its functionality. 12 | 13 | We would like you to add functionality for posting photos too. The way Tumblr does. 14 | 15 | We would like to be able to post a photo by going to '/photos/new' where we would enter the URL of the photo in a text box. 16 | 17 | Both posts and photos should have titles. 18 | 19 | We would like to be able to: 20 | * list all photos by going to '/photos' 21 | * view a specific photo by visiting '/photos/:id' 22 | 23 | Layouts for viewing all posts and all photos should be different. Be creative! 24 | 25 | We would also like to be able to view both photos and posts when we go to our index page('/'). 26 | 27 | It would be nice if you extract the similarities between posts and photos. For example, both of them should display a specific title and date of submission. 28 | 29 | :) -------------------------------------------------------------------------------- /week09/README.md: -------------------------------------------------------------------------------- 1 | # Web, HTTP, Sinatra, SQL 2 | 3 | It's all about that Web, bout that Web! 4 | 5 | Slides: 6 | 7 | * [HTTP, Rack & Sinatra](https://ruby.hackbulgaria.com/lectures/17#/) 8 | -------------------------------------------------------------------------------- /week10/1-online-store/README.md: -------------------------------------------------------------------------------- 1 | It's time again to create another web application. 2 | 3 | This time we'll be working on an online store. The application should provide only REST services in the form of JSON-formatted responses. No pretty HTML this time. 4 | 5 | We have a couple of concepts in our store that you should be aware of. First we have brands. Every brand goes with its name and a short description. It represents the producer of a product. We have categories. They have names only. Then we have products where each product has a name, a brand, a category, a price and a quantity of stock. 6 | 7 | What you have to do is define the following endpoints: 8 | * `GET /brands/count` - Returns count of the brands in the store. 9 | * `GET /brands/{index}` - Returns the brand with {index}. 10 | * `GET /brands/range/{index}/{count}` - Returns {count} brands starting from {index}. 11 | * `GET /brands/range/{index}` - Returns all brands starting from {index}. 12 | * `GET /brands` - Returns all brands registered in the store. 13 | * `POST /brands/new` - Creates a new brand. 14 | * `PUT /brands/{index}` - Updates brand with {index}. 15 | * `DELETE /brands/{index}` - Deletes brand with {index}. 16 | * `GET /categories/count` - Returns count of the categories in the store. 17 | * `GET /categories/{index}` - Returns the category with {index}. 18 | * `GET /categories/range/{index}/{count}` - Returns {count} categories starting from {index}. 19 | * `GET /categories/range/{index}` - Returns all categories starting from {index}. 20 | * `GET /categories` - Returns all categories registered in the store. 21 | * `POST /categories/new` - Creates a new category. 22 | * `PUT /categories/{index}` - Updates category with {index}. 23 | * `DELETE /categories/{index}` - Deletes category with {index}. 24 | * `GET /products/count` - Returns count of the products in the store. 25 | * `GET /products/{index}` - Returns the product with {index}. 26 | * `GET /products/range/{index}/{count}` - Returns {count} products starting from {index}. 27 | * `GET /products/range/{index}` - Returns all products starting from {index}. 28 | * `GET /products` - Returns all products registered in the store. 29 | * `POST /products/new` - Creates a new product. 30 | * `PUT /products/{index}` - Updates product with {index}. 31 | * `DELETE /products/{index}` - Deletes product with {index}. 32 | * `GET /search/{type}/{slug}` - Returns entities of specified {type} with name that contains {slug}. E.g: GET /search/product/something 33 | * `GET /search/{type}/{property}/{slug}` - Returns entities of specified {type} with {property} that contains {slug}. E.g: GET /search/category/description/something 34 | 35 | To make things more interesting we'll require you to **NOT** use generators at all! 36 | 37 | Start by using mock data for these entities and only do the "piping" by defining the required routes and actions. Next time we'll cover persistence and you will be able to actually store this data on disk. 38 | -------------------------------------------------------------------------------- /week11/1-course-management/README.md: -------------------------------------------------------------------------------- 1 | # Course management 2 | 3 | Howdy! 4 | 5 | For our next adventure in the world of World-Wide Web we would like to develop an application for managing our Rails course. The next lines describe the key components of this system. 6 | 7 | ## Lectures 8 | We have many lectures in our system. Every lecture is composed of a name, a text body and many tasks. These tasks are for the students to solve and could be as many as we want. 9 | 10 | ### Endpoints 11 | * GET /lectures - Lists all lectures in the system. 12 | * GET /lectures/new - Displays form for creating new lecture. 13 | * GET /lectures/42 - Displays lecture 42. 14 | * GET /lectures/42/edit - Edits lecture 42. 15 | * POST /lectures - Creates a new lecture 16 | * DELETE /lectures/42 - Deletes lecture 42. 17 | * PUT/PATCH /lectures/42 - Updates lecture 42. 18 | 19 | ## Tasks 20 | Every task has a name, description and many solutions. 21 | 22 | ### Endpoints 23 | * GET /lectures/42/tasks - Lists all tasks for lecture 42. 24 | * GET /lectures/42/tasks/new - Displays form for creating new task for lecture 42. 25 | * GET /lectures/42/tasks/3 - Displays task 3 for lecture 42. 26 | * GET /lectures/42/tasks/3/edit - Edits task 3 for lecture 42. 27 | * POST /lectures/42/tasks - Creates task for lecture 42. 28 | * DELETE /lectures/42/tasks/3 - Deletes task 3 for lecture 42. 29 | * PUT/PATCH /lectures/42/tasks/3 - Updates task 3 for lecture 42. 30 | 31 | ## Solutions 32 | Every task has many potential solutions sent by our students. We need a way to manage them too. The solution has a text block with the solution in it. 33 | 34 | ### Endpoints 35 | * GET /lectures/42/tasks/3/solutions - Lists all solutions for tasks 3 in lecture 42. 36 | * GET /lectures/42/tasks/3/solutions/new - Displays form for creating new solution for task 3 in lecture 42. 37 | * GET /lectures/42/tasks/3/solutions/13 - Displays solution 13 for task 3 in lecture 42. 38 | * GET /lectures/42/tasks/3/solutions/13/edit - Edits solution 13 for task 3 in lecture 42. 39 | * POST /lectures/42/tasks/3/solutions - Creates solution for task 3 in lecture 42. 40 | * DELETE /lectures/42/tasks/3/solutions/13 - Deletes solution 13 for task 3 in lecture 42. 41 | * PUT/PATCH /lectures/42/tasks/3/solutions/13 - Updates solution 13 for task 3 in lecture 42. 42 | 43 | This time we're not building a REST service so don't forget to add some pretty web views for managing all these endpoints. 44 | 45 | Go on and build that thing! (: 46 | -------------------------------------------------------------------------------- /week12/1-authentication/README.md: -------------------------------------------------------------------------------- 1 | # Authentication 2 | 3 | Authentication is fundamental for a web application. Now is the time to implement one. 4 | 5 | ## Sign up 6 | 7 | We need a page with a sign up form so the users of our application could provide authentication credentials. This page displays input fields for first name, last name, email, passsword and password confirmation. 8 | 9 | Submitting the sign up form creates a new user with the specified credentials in the application. 10 | 11 | ## Sign in 12 | 13 | The sign in form displays input fields for email and password. It also includes a "Remember me" checkbox. 14 | 15 | Submitting the sign in form authenticates the user and takes him to the welcome page (if the credentials are valid). 16 | 17 | ## Welcome 18 | 19 | The welcome page displays some information about the authenticated user along with a link for signing out. 20 | 21 | ## Validation 22 | 23 | Make sure to validate your forms and display proper error messages. 24 | 25 | # Remember me 26 | 27 | Enabling the remember me option during authentication the user session should be persisted across browser restarts, etc.. 28 | 29 | ## NO SCAFFOLDING! 30 | 31 | Please (: 32 | 33 | ## Hints 34 | 35 | Somebody shushed me about `ActiveRecord::Base#has_secure_password` and 36 | `ActionController::Base#cookies`! 37 | -------------------------------------------------------------------------------- /week13/1-semantic-tweets/README.md: -------------------------------------------------------------------------------- 1 | # Semantic tweets 2 | 3 | The task for this week will involve lots of tweets, API calls, and positive 4 | vibes! 5 | 6 | ## TL;DR 7 | Create an application that fetches tweets from user's Twitter feed (using 8 | [Twitter's REST API](https://dev.twitter.com/rest/public)) then tries to find 9 | out which tweets are positive, neutral or negative. 10 | 11 | Do this processing by a plain Ruby class called `TweetStatus`. How would you 12 | find out whether a tweet is positive from it's content? I dunno, figure it out, 13 | lol :trollface:. 14 | 15 | ## Requirements 16 | 17 | You obviously need a Twitter account with lots of data in the feed. 18 | 19 | ## Restrictions 20 | 21 | If you feel adventurous, make sure you don't use some fancy library wrapping 22 | the APIs. We'd like you to work directly with the API provided by the service. 23 | 24 | ## Extended 25 | 26 | In addition to the functionality above, you should add a form for posting new 27 | tweets. The content of the tweet should first be passed through Semantria and 28 | only if the sentiment of the tweet is positive it should be posted to Twitter. 29 | -------------------------------------------------------------------------------- /week14/1-testrospective/README.md: -------------------------------------------------------------------------------- 1 | # Testrospective 2 | 3 | Now that we know about testing, let's go 4 | back and write automated tests for every 5 | task after the microblog. 6 | 7 | _(You can find the test-discovery framework 8 | we did in this folder.)_ 9 | -------------------------------------------------------------------------------- /week14/1-testrospective/meta.rb: -------------------------------------------------------------------------------- 1 | class Ampersand < BasicObject 2 | module PreserveBuiltinBehaviour 3 | Undefined = Object.new 4 | 5 | def &(other = Undefined) 6 | if other == Undefined 7 | Ampersand.new(self) 8 | else 9 | super 10 | end 11 | end 12 | end 13 | 14 | def initialize(obj) 15 | @obj = obj 16 | end 17 | 18 | def method_missing(name, *args, &block) 19 | if @obj.nil? 20 | nil 21 | else 22 | @obj.send(name, *args, &block) 23 | end 24 | end 25 | end 26 | 27 | class Object 28 | def & 29 | Ampersand.new(self) 30 | end 31 | end 32 | 33 | [Fixnum, Bignum, NilClass, TrueClass, FalseClass].each do |klass| 34 | klass.prepend(Ampersand::PreserveBuiltinBehaviour) 35 | end 36 | -------------------------------------------------------------------------------- /week14/1-testrospective/meta_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'test' 2 | require_relative 'meta' 3 | 4 | class MetaTest < Test 5 | setup do 6 | @user = Object.new 7 | end 8 | 9 | teardown do 10 | Clean.current_table 11 | end 12 | 13 | test "expect random objects to be callable and return nil" do 14 | assert_raises NoMethodError do 15 | user.account 16 | end 17 | 18 | assert_raises NoMethodError do 19 | user.account 20 | end 21 | end 22 | 23 | test "expect Rado to nyama preateli" do 24 | rado = Object.new.tap do |obj| 25 | obj.define_singleton_method(:friends) { nil } 26 | end 27 | 28 | assert nil, rado.&.friends.&.count 29 | end 30 | 31 | test "expect nil to be callable and return nil" do 32 | assert nil, nil.&.asdasd 33 | end 34 | 35 | test "expect builtin behaviour to still work" do 36 | assert false, true & false 37 | assert true, true & true 38 | assert false, nil & true 39 | assert false, false.&(nil) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /week14/1-testrospective/test.rb: -------------------------------------------------------------------------------- 1 | class Test 2 | def self.test(name, &block) 3 | puts "Testing #{name}..." 4 | new.instance_exec(&block) 5 | end 6 | 7 | module Assertions 8 | def assert(expected, actual) 9 | unless expected == actual 10 | abort "[#{caller.first}] Expected #{expected.inspect}, got: #{actual.inspect}" 11 | end 12 | end 13 | 14 | def assert_raises(exception_class, &block) 15 | block.call 16 | rescue exception_class 17 | return 18 | rescue Exception => e 19 | abort "Expected to raise #{exception_class}, got #{e.class}" 20 | end 21 | end 22 | 23 | include Assertions 24 | end 25 | --------------------------------------------------------------------------------