Slide 1

Slide 1 text

Thai Pangsakulyanont 1 Solving algorithmic problems in Ruby Ruby Wednesday #4

Slide 2

Slide 2 text

About Me Slide № 2 @dtinth Thai Pangsakulyanont

Slide 3

Slide 3 text

About Me Slide № 3 JavaScript developer Thai Pangsakulyanont I am a… (speaking in a Ruby meetup)

Slide 4

Slide 4 text

About Me Slide № 4 Front-end architect @ Taskworld Thai Pangsakulyanont

Slide 5

Slide 5 text

About Me Slide № 5 Use Ruby a lot Thai Pangsakulyanont But in my day-to-day work and in daily life, I find myself using Ruby a lot!

Slide 6

Slide 6 text

Slide № 6 Today: Using Ruby to solve problems

Slide 7

Slide 7 text

Slide № 7 Today: Using Ruby to solve problems - Competitive programming (A competition where you go and solve algorithm problems and win some internet points, and sometimes, some cash)

Slide 8

Slide 8 text

Slide № 8 Today: Using Ruby to solve problems - Competitive programming - Practical, daily life stuff

Slide 9

Slide 9 text

Slide № 9 Advent of Code Last year, I’ve been playing with this thing called…

Slide 10

Slide 10 text

Slide № 10 Advent of Code Yearly programming competition

Slide 11

Slide 11 text

Slide № 11 Advent of Code Yearly programming competition December 1~25

Slide 12

Slide 12 text

Slide № 12 Advent of Code Yearly programming competition December 1~25 Each day @ 12:00, a new problem 12:00pm Thailand Time

Slide 13

Slide 13 text

Slide № 13 Advent of Code Each problem, two puzzles When you solve the first puzzle the second puzzle will be unlocked.

Slide 14

Slide 14 text

Slide № 14 Advent of Code Each problem, two puzzles Each puzzle solved gives you one star

Slide 15

Slide 15 text

Slide № 15 Goal Collect 50 stars If you want to flex your problem-solving muscle, I really recommend trying it. Most problems are not too hard. Although the event is already over, you can still collect stars.

Slide 16

Slide 16 text

Slide № 16 Goal Collect 50 stars Competition But there’s also this competition part, and it’s a really fierce competition.

Slide 17

Slide 17 text

Slide № 17 Goal Collect 50 stars Competition Let’s look at the leaderboard.

Slide 18

Slide 18 text

Slide № 18 Goal Collect 50 stars Competition Here’s how it works…

Slide 19

Slide 19 text

Slide № 19 Goal Collect 50 stars Competition • The first person to collect
 each star gets 100 pts.

Slide 20

Slide 20 text

Slide № 20 Goal Collect 50 stars Competition • The first person to collect
 each star gets 100 pts. • The second person, 99 pts • (…)

Slide 21

Slide 21 text

Slide № 21 Goal Collect 50 stars Competition • The first person to collect
 each star gets 100 pts. • The second person, 99 pts • (…) • The 100th person, 1 pt. After this, you can collect stars but you won’t get any point.

Slide 22

Slide 22 text

Slide № 22 Now, let’s go ahead and look at the first day’s problem.

Slide 23

Slide 23 text

Slide № 23

Slide 24

Slide 24 text

Slide № 24 When you enter, you will see a wall of text that you have to quickly read through.

Slide 25

Slide 25 text

Slide № 25 Since it’s the first day, here it explains the rules of this game.

Slide 26

Slide 26 text

Slide № 26 Scrolling down, you’ll encounter…

Slide 27

Slide 27 text

Slide № 27 The problem statement…

Slide 28

Slide 28 text

Slide № 28 …and some example cases. Before we dive in, let’s look at the leaderboard for this problem.

Slide 29

Slide 29 text

Slide № 29 As you can see, if you don’t solve this problem in 3 minutes 47 seconds, you’re not going to win any point.

Slide 30

Slide 30 text

Slide № 30 Now let’s look at each example.

Slide 31

Slide 31 text

Slide № 31

Slide 32

Slide 32 text

Slide № 32 1122 I’ll explain how to solve this puzzle quickly. Basically, you have to look for…

Slide 33

Slide 33 text

Slide № 33 1122 +1 +2 …the digits that match the next digit. Sum them all to obtain the puzzle’s answer.

Slide 34

Slide 34 text

Slide № 34 1122 +1 +2 Solution: 3

Slide 35

Slide 35 text

Slide № 35 1111

Slide 36

Slide 36 text

Slide № 36 1111 +1 +1 +1 +1 As you can see, it also wraps around.

Slide 37

Slide 37 text

Slide № 37 1111 +1 Solution: 4 +1 +1 +1

Slide 38

Slide 38 text

Slide № 38 1234 How about this one?

Slide 39

Slide 39 text

Slide № 39 1234 Solution: 0

Slide 40

Slide 40 text

Slide № 40 91212129 And this one?

Slide 41

Slide 41 text

Slide № 41 91212129 +9

Slide 42

Slide 42 text

Slide № 42 91212129 Solution: 9 +9

Slide 43

Slide 43 text

Slide № 43 Devising a solution After understanding the problem, now it’s time to devise a solution.

Slide 44

Slide 44 text

Slide № 44 1223334551 Solution: 0 The most obvious way to solve this is to go through each digit, …

Slide 45

Slide 45 text

Slide № 45 1223334551 Solution: 0 …comparing it to the next…

Slide 46

Slide 46 text

Slide № 46 1223334551 Solution: 0

Slide 47

Slide 47 text

Slide № 47 1223334551 Solution: 0

Slide 48

Slide 48 text

Slide № 48 1223334551 +2 Solution: 2 …and if they match, add it to the accumulator…

Slide 49

Slide 49 text

Slide № 49 1223334551 Solution: 2

Slide 50

Slide 50 text

Slide № 50 1223334551 Solution: 2 …and keep repeating…

Slide 51

Slide 51 text

Slide № 51 1223334551 +3 Solution: 5

Slide 52

Slide 52 text

Slide № 52 1223334551 +3 Solution: 8

Slide 53

Slide 53 text

Slide № 53 1223334551 +5 Solution: 13

Slide 54

Slide 54 text

Slide № 54 1223334551 +1 Solution: 14 …until you reached the end.

Slide 55

Slide 55 text

Slide № 55 1223334551 Solution: 14 The answer will be in the accumulator.

Slide 56

Slide 56 text

Slide № 56 Let’s quickly write some code for this!

Slide 57

Slide 57 text

Slide № 57 input = "1223334551"

Slide 58

Slide 58 text

Slide № 58 input = "1223334551" solution = 0

Slide 59

Slide 59 text

Slide № 59 input = "1223334551" solution = 0 (0...input.length)

Slide 60

Slide 60 text

Slide № 60 input = "1223334551" solution = 0 (0...input.length).each do |i| end

Slide 61

Slide 61 text

Slide № 61 input = "1223334551" solution = 0 (0...input.length).each do |i| if input[i] == input[(i + 1)] end end

Slide 62

Slide 62 text

Slide № 62 input = "1223334551" solution = 0 (0...input.length).each do |i| if input[i] == input[(i + 1) % input.length] end end wrapping around

Slide 63

Slide 63 text

Slide № 63 input = "1223334551" solution = 0 (0...input.length).each do |i| if input[i] == input[(i + 1) % input.length] solution += input[i] end end

Slide 64

Slide 64 text

Slide № 64 input = "1223334551" solution = 0 (0...input.length).each do |i| if input[i] == input[(i + 1) % input.length] solution += input[i].to_i end end don’t forget to convert to int

Slide 65

Slide 65 text

Slide № 65 input = "1223334551" solution = 0 (0...input.length).each do |i| if input[i] == input[(i + 1) % input.length] solution += input[i].to_i end end puts solution At the end of the loop, the solution variable contains the answer. Let’s print it.

Slide 66

Slide 66 text

Slide № 66 input = "1223334551" solution = 0 (0...input.length).each do |i| if input[i] == input[(i + 1) % input.length] solution += input[i].to_i end end puts solution (Many people would write code like this, but in their favorite language)

Slide 67

Slide 67 text

Slide № 67 input = "1223334551" solution = 0 (0...input.length).each do |i| if input[i] == input[(i + 1) % input.length] solution += input[i].to_i end end puts solution Slow feedback loop. This works correctly, but the more I learn Ruby, I realize this is an inefficient way of solving the problem. The feedback loop is too slow. You have to write the whole program until you can check if it’s correct or not.

Slide 68

Slide 68 text

Slide № 68 input = "1223334551" solution = 0 (0...input.length).each do |i| if input[i] == input[(i + 1) % input.length] solution += input[i].to_i end end puts solution Slow feedback loop. Incorrect result Error If you made a mistake, you’ll only know it when you run the program. Then you have to figure out what’s wrong. Then fix it. Then run it again.

Slide 69

Slide 69 text

Slide № 69 Solving the problem again, but with faster feedback loop

Slide 70

Slide 70 text

Slide № 70 irb Interactive Ruby Interactive Ruby runs your code line-by-line, giving you immediate feedback after each statement.

Slide 71

Slide 71 text

Slide № 71 $ irb

Slide 72

Slide 72 text

Slide № 72 $ irb 2.4.1 :001 > Running `irb` puts me into the Ruby shell.

Slide 73

Slide 73 text

Slide № 73 $ irb 2.4.1 :001 > input = "1223334551" First, I store the input in Ruby’s memory.

Slide 74

Slide 74 text

Slide № 74 $ irb 2.4.1 :001 > input = "1223334551" => "1223334551" 2.4.1 :002 >

Slide 75

Slide 75 text

Slide № 75 $ irb 2.4.1 :001 > input = "1223334551" => "1223334551" 2.4.1 :002 > input.chars Let’s try breaking it down by character.

Slide 76

Slide 76 text

Slide № 76 $ irb 2.4.1 :001 > input = "1223334551" => "1223334551" 2.4.1 :002 > input.chars => ["1", "2", "2", "3", "3", "3", "4", "5", "5", "1"] 2.4.1 :003 >

Slide 77

Slide 77 text

Slide № 77 $ irb 2.4.1 :001 > input = "1223334551" => "1223334551" 2.4.1 :002 > input.chars => ["1", "2", "2", "3", "3", "3", "4", "5", "5", "1"] 2.4.1 :003 > input.chars Pressing [Up] recalls the last line.

Slide 78

Slide 78 text

Slide № 78 $ irb 2.4.1 :001 > input = "1223334551" => "1223334551" 2.4.1 :002 > input.chars => ["1", "2", "2", "3", "3", "3", "4", "5", "5", "1"] 2.4.1 :003 > input I don’t want to deal with wrapping around, so…

Slide 79

Slide 79 text

Slide № 79 $ irb 2.4.1 :001 > input = "1223334551" => "1223334551" 2.4.1 :002 > input.chars => ["1", "2", "2", "3", "3", "3", "4", "5", "5", "1"] 2.4.1 :003 > (input I’m just gonna take the first digit…

Slide 80

Slide 80 text

Slide № 80 $ irb 2.4.1 :001 > input = "1223334551" => "1223334551" 2.4.1 :002 > input.chars => ["1", "2", "2", "3", "3", "3", "4", "5", "5", "1"] 2.4.1 :003 > (input + input[0]) …and append it to the end of the string.

Slide 81

Slide 81 text

Slide № 81 $ irb 2.4.1 :001 > input = "1223334551" => "1223334551" 2.4.1 :002 > input.chars => ["1", "2", "2", "3", "3", "3", "4", "5", "5", "1"] 2.4.1 :003 > (input + input[0]) => "12233345511" 2.4.1 :004 >

Slide 82

Slide 82 text

Slide № 82 $ irb 2.4.1 :001 > input = "1223334551" => "1223334551" 2.4.1 :002 > input.chars => ["1", "2", "2", "3", "3", "3", "4", "5", "5", "1"] 2.4.1 :003 > (input + input[0]) => "12233345511" 2.4.1 :004 > (input + input[0])

Slide 83

Slide 83 text

Slide № 83 $ irb 2.4.1 :001 > input = "1223334551" => "1223334551" 2.4.1 :002 > input.chars => ["1", "2", "2", "3", "3", "3", "4", "5", "5", "1"] 2.4.1 :003 > (input + input[0]) => "12233345511" 2.4.1 :004 > (input + input[0]).chars Let’s break it down into characters again.

Slide 84

Slide 84 text

Slide № 84 $ irb 2.4.1 :001 > input = "1223334551" => "1223334551" 2.4.1 :002 > input.chars => ["1", "2", "2", "3", "3", "3", "4", "5", "5", "1"] 2.4.1 :003 > (input + input[0]) => "12233345511" 2.4.1 :004 > (input + input[0]).chars => ["1", "2", "2", "3", "3", "3", "4", "5", "5", "1", "1"] 2.4.1 :005 >

Slide 85

Slide 85 text

Slide № 85 $ irb 2.4.1 :001 > input = "1223334551" => "1223334551" 2.4.1 :002 > input.chars => ["1", "2", "2", "3", "3", "3", "4", "5", "5", "1"] 2.4.1 :003 > (input + input[0]) => "12233345511" 2.4.1 :004 > (input + input[0]).chars => ["1", "2", "2", "3", "3", "3", "4", "5", "5", "1", "1"] 2.4.1 :005 > Now, I have an array of string. I think it’s easier for me to work with array of numbers instead.

Slide 86

Slide 86 text

Slide № 86 $ irb 2.4.1 :001 > input = "1223334551" => "1223334551" 2.4.1 :002 > input.chars => ["1", "2", "2", "3", "3", "3", "4", "5", "5", "1"] 2.4.1 :003 > (input + input[0]) => "12233345511" 2.4.1 :004 > (input + input[0]).chars => ["1", "2", "2", "3", "3", "3", "4", "5", "5", "1", "1"] 2.4.1 :005 > (input + input[0]).chars

Slide 87

Slide 87 text

Slide № 87 $ irb 2.4.1 :001 > input = "1223334551" => "1223334551" 2.4.1 :002 > input.chars => ["1", "2", "2", "3", "3", "3", "4", "5", "5", "1"] 2.4.1 :003 > (input + input[0]) => "12233345511" 2.4.1 :004 > (input + input[0]).chars => ["1", "2", "2", "3", "3", "3", "4", "5", "5", "1", "1"] 2.4.1 :005 > (input + input[0]).chars.map(&:to_i) So I’m gonna map over this array, converting each element into int.

Slide 88

Slide 88 text

Slide № 88 $ irb 2.4.1 :001 > input = "1223334551" => "1223334551" 2.4.1 :002 > input.chars => ["1", "2", "2", "3", "3", "3", "4", "5", "5", "1"] 2.4.1 :003 > (input + input[0]) => "12233345511" 2.4.1 :004 > (input + input[0]).chars => ["1", "2", "2", "3", "3", "3", "4", "5", "5", "1", "1"] 2.4.1 :005 > (input + input[0]).chars.map(&:to_i) => [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1]

Slide 89

Slide 89 text

Slide № 89 2.4.1 :005 > (input + input[0]).chars.map(&:to_i) => [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1] 2.4.1 :006 >

Slide 90

Slide 90 text

Slide № 90 2.4.1 :005 > (input + input[0]).chars.map(&:to_i) => [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1] 2.4.1 :006 > (input + input[0]).chars.map(&:to_i) Now, I’m going to use…

Slide 91

Slide 91 text

Slide № 91 2.4.1 :005 > (input + input[0]).chars.map(&:to_i) => [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1] 2.4.1 :006 > (input + input[0]).chars.map(&:to_i).each_cons(2) …the `each_cons` method.

Slide 92

Slide 92 text

Slide № 92 2.4.1 :005 > (input + input[0]).chars.map(&:to_i) => [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1] 2.4.1 :006 > (input + input[0]).chars.map(&:to_i).each_cons(2) => # 2.4.1 :007 > It returns an Enumerator that lets me iterate through each 2 consecutive digits.

Slide 93

Slide 93 text

Slide № 93 2.4.1 :005 > (input + input[0]).chars.map(&:to_i) => [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1] 2.4.1 :006 > (input + input[0]).chars.map(&:to_i).each_cons(2) => # 2.4.1 :007 > (input + input[0]).chars.map(&:to_i).each_cons(2) To see how the iteration looks like…

Slide 94

Slide 94 text

Slide № 94 2.4.1 :005 > (input + input[0]).chars.map(&:to_i) => [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1] 2.4.1 :006 > (input + input[0]).chars.map(&:to_i).each_cons(2) => # 2.4.1 :007 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .to_a you can convert it into an Array.

Slide 95

Slide 95 text

Slide № 95 2.4.1 :005 > (input + input[0]).chars.map(&:to_i) => [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1] 2.4.1 :006 > (input + input[0]).chars.map(&:to_i).each_cons(2) => # 2.4.1 :007 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .to_a => [[1, 2], [2, 2], [2, 3], [3, 3], [3, 3], [3, 4], [4, 5], [5, 5], [5, 1], [1, 1]] 2.4.1 :008 >

Slide 96

Slide 96 text

Slide № 96 2.4.1 :005 > (input + input[0]).chars.map(&:to_i) => [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1] 2.4.1 :006 > (input + input[0]).chars.map(&:to_i).each_cons(2) => # 2.4.1 :007 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .to_a => [[1, 2], [2, 2], [2, 3], [3, 3], [3, 3], [3, 4], [4, 5], [5, 5], [5, 1], [1, 1]] 2.4.1 :008 > Now, I’m only interested in pairs of consecutive digits where the left matches the right.

Slide 97

Slide 97 text

Slide № 97 2.4.1 :005 > (input + input[0]).chars.map(&:to_i) => [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1] 2.4.1 :006 > (input + input[0]).chars.map(&:to_i).each_cons(2) => # 2.4.1 :007 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .to_a => [[1, 2], [2, 2], [2, 3], [3, 3], [3, 3], [3, 4], [4, 5], [5, 5], [5, 1], [1, 1]] 2.4.1 :008 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .to_a

Slide 98

Slide 98 text

Slide № 98 2.4.1 :005 > (input + input[0]).chars.map(&:to_i) => [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1] 2.4.1 :006 > (input + input[0]).chars.map(&:to_i).each_cons(2) => # 2.4.1 :007 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .to_a => [[1, 2], [2, 2], [2, 3], [3, 3], [3, 3], [3, 4], [4, 5], [5, 5], [5, 1], [1, 1]] 2.4.1 :008 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .

Slide 99

Slide 99 text

Slide № 99 2.4.1 :005 > (input + input[0]).chars.map(&:to_i) => [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1] 2.4.1 :006 > (input + input[0]).chars.map(&:to_i).each_cons(2) => # 2.4.1 :007 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .to_a => [[1, 2], [2, 2], [2, 3], [3, 3], [3, 3], [3, 4], [4, 5], [5, 5], [5, 1], [1, 1]] 2.4.1 :008 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .filter { So I’m gonna filter this Enumerator…

Slide 100

Slide 100 text

Slide № 100 2.4.1 :005 > (input + input[0]).chars.map(&:to_i) => [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1] 2.4.1 :006 > (input + input[0]).chars.map(&:to_i).each_cons(2) => # 2.4.1 :007 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .to_a => [[1, 2], [2, 2], [2, 3], [3, 3], [3, 3], [3, 4], [4, 5], [5, 5], [5, 1], [1, 1]] 2.4.1 :008 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .filter { |a, …taking 2 consecutive digits

Slide 101

Slide 101 text

Slide № 101 2.4.1 :005 > (input + input[0]).chars.map(&:to_i) => [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1] 2.4.1 :006 > (input + input[0]).chars.map(&:to_i).each_cons(2) => # 2.4.1 :007 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .to_a => [[1, 2], [2, 2], [2, 3], [3, 3], [3, 3], [3, 4], [4, 5], [5, 5], [5, 1], [1, 1]] 2.4.1 :008 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .filter { |a, b| …taking 2 consecutive digits, a and b…

Slide 102

Slide 102 text

Slide № 102 2.4.1 :005 > (input + input[0]).chars.map(&:to_i) => [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1] 2.4.1 :006 > (input + input[0]).chars.map(&:to_i).each_cons(2) => # 2.4.1 :007 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .to_a => [[1, 2], [2, 2], [2, 3], [3, 3], [3, 3], [3, 4], [4, 5], [5, 5], [5, 1], [1, 1]] 2.4.1 :008 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .filter { |a, b| a == b } …and looking for pairs where a equals b.

Slide 103

Slide 103 text

Slide № 103 2.4.1 :005 > (input + input[0]).chars.map(&:to_i) => [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1] 2.4.1 :006 > (input + input[0]).chars.map(&:to_i).each_cons(2) => # 2.4.1 :007 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .to_a => [[1, 2], [2, 2], [2, 3], [3, 3], [3, 3], [3, 4], [4, 5], [5, 5], [5, 1], [1, 1]] 2.4.1 :008 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .filter { |a, b| a == b } NoMethodError: undefined method `filter' for # from (irb):8

Slide 104

Slide 104 text

Slide № 104 2.4.1 :008 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .filter { |a, b| a == b } NoMethodError: undefined method `filter' for # from (irb):8 from /Users/dtinth/.rvm/rubies/ruby-2.4.1/bin/irb:11:in `' 2.4.1 :009 > As I’m a JavaScript user, I always forget that in Ruby, `filter` is called `select`.

Slide 105

Slide 105 text

Slide № 105 2.4.1 :008 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .filter { |a, b| a == b } NoMethodError: undefined method `filter' for # from (irb):8 from /Users/dtinth/.rvm/rubies/ruby-2.4.1/bin/irb:11:in `' 2.4.1 :009 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .filter { |a, b| a == b }

Slide 106

Slide 106 text

Slide № 106 2.4.1 :008 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .filter { |a, b| a == b } NoMethodError: undefined method `filter' for # from (irb):8 from /Users/dtinth/.rvm/rubies/ruby-2.4.1/bin/irb:11:in `' 2.4.1 :009 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ . { |a, b| a == b }

Slide 107

Slide 107 text

Slide № 107 2.4.1 :008 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .filter { |a, b| a == b } NoMethodError: undefined method `filter' for # from (irb):8 from /Users/dtinth/.rvm/rubies/ruby-2.4.1/bin/irb:11:in `' 2.4.1 :009 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .select { |a, b| a == b }

Slide 108

Slide 108 text

Slide № 108 2.4.1 :008 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .filter { |a, b| a == b } NoMethodError: undefined method `filter' for # from (irb):8 from /Users/dtinth/.rvm/rubies/ruby-2.4.1/bin/irb:11:in `' 2.4.1 :009 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .select { |a, b| a == b } => [[2, 2], [3, 3], [3, 3], [5, 5], [1, 1]] 2.4.1 :010 > Now I have the pairs of consecutive digits that match.

Slide 109

Slide 109 text

Slide № 109 2.4.1 :008 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .filter { |a, b| a == b } NoMethodError: undefined method `filter' for # from (irb):8 from /Users/dtinth/.rvm/rubies/ruby-2.4.1/bin/irb:11:in `' 2.4.1 :009 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .select { |a, b| a == b } => [[2, 2], [3, 3], [3, 3], [5, 5], [1, 1]] 2.4.1 :010 >

Slide 110

Slide 110 text

Slide № 110 2.4.1 :008 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .filter { |a, b| a == b } NoMethodError: undefined method `filter' for # from (irb):8 from /Users/dtinth/.rvm/rubies/ruby-2.4.1/bin/irb:11:in `' 2.4.1 :009 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .select { |a, b| a == b } => [[2, 2], [3, 3], [3, 3], [5, 5], [1, 1]] 2.4.1 :010 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .select { |a, b| a == b } I’m only interested in the digits, so I’m going to…

Slide 111

Slide 111 text

Slide № 111 2.4.1 :008 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .filter { |a, b| a == b } NoMethodError: undefined method `filter' for # from (irb):8 from /Users/dtinth/.rvm/rubies/ruby-2.4.1/bin/irb:11:in `' 2.4.1 :009 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .select { |a, b| a == b } => [[2, 2], [3, 3], [3, 3], [5, 5], [1, 1]] 2.4.1 :010 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .select { |a, b| a == b }.map(&:first) …take the first item out of each pair.

Slide 112

Slide 112 text

Slide № 112 2.4.1 :008 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .filter { |a, b| a == b } NoMethodError: undefined method `filter' for # from (irb):8 from /Users/dtinth/.rvm/rubies/ruby-2.4.1/bin/irb:11:in `' 2.4.1 :009 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .select { |a, b| a == b } => [[2, 2], [3, 3], [3, 3], [5, 5], [1, 1]] 2.4.1 :010 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .select { |a, b| a == b }.map(&:first) => [2, 3, 3, 5, 1]

Slide 113

Slide 113 text

Slide № 113 2.4.1 :010 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .select { |a, b| a == b }.map(&:first) => [2, 3, 3, 5, 1] 2.4.1 :011 >

Slide 114

Slide 114 text

Slide № 114 2.4.1 :010 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .select { |a, b| a == b }.map(&:first) => [2, 3, 3, 5, 1] 2.4.1 :011 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .select { |a, b| a == b }.map(&:first) Finally, the last thing I have to do is…

Slide 115

Slide 115 text

Slide № 115 2.4.1 :010 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .select { |a, b| a == b }.map(&:first) => [2, 3, 3, 5, 1] 2.4.1 :011 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .select { |a, b| a == b }.map(&:first).inject(&:+) …to sum em up. I’m gonna inject the + sign between each element. Note from audience: Enumerables also have `sum` method since Ruby 2.4. (Thanks, Michael!)

Slide 116

Slide 116 text

Slide № 116 2.4.1 :010 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .select { |a, b| a == b }.map(&:first) => [2, 3, 3, 5, 1] 2.4.1 :011 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .select { |a, b| a == b }.map(&:first).inject(&:+) => 14 …and there’s the answer.

Slide 117

Slide 117 text

Slide № 117 2.4.1 :010 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .select { |a, b| a == b }.map(&:first) => [2, 3, 3, 5, 1] 2.4.1 :011 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .select { |a, b| a == b }.map(&:first).inject(&:+) => 14 One-liner that solves the problem

Slide 118

Slide 118 text

Slide № 118 2.4.1 :010 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .select { |a, b| a == b }.map(&:first) => [2, 3, 3, 5, 1] 2.4.1 :011 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .select { |a, b| a == b }.map(&:first).inject(&:+) => 14 One-liner that solves the problem Very fast feedback loop Get immediate feedback whenever something goes wrong.

Slide 119

Slide 119 text

Slide № 119 2.4.1 :010 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .select { |a, b| a == b }.map(&:first) => [2, 3, 3, 5, 1] 2.4.1 :011 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .select { |a, b| a == b }.map(&:first).inject(&:+) => 14 One-liner that solves the problem Very fast feedback loop Iterative and incremental Instead of having to solve the whole problem at once, I start with the problem, and gradually transform it until a solution is reached.

Slide 120

Slide 120 text

Slide № 120 Ruby allows for very fast feedback loop

Slide 121

Slide 121 text

Slide № 121 If you use a modern text editor…

Slide 122

Slide 122 text

Slide № 122 …you can edit your code inside the text editor…

Slide 123

Slide 123 text

Slide № 123 …and run a command that will send the current line into the terminal…

Slide 124

Slide 124 text

Slide № 124 …this is how I like to solve problems in Ruby.

Slide 125

Slide 125 text

Slide № 125 (input + input[0]).chars.map(&:to_i).each_cons(2) .select { |a, b| a == b }.map(&:first).inject(&:+) Let’s come back and take a look at this code again…

Slide 126

Slide 126 text

Slide № 126 (input + input[0]).chars.map(&:to_i).each_cons(2) .select { |a, b| a == b }.map(&:first).inject(&:+) String#[] String#+ String#chars Enumerable#map Enumerable#each_cons Enumerable#select Enumerable#inject I used a lot of methods to solve this problem. This brings me to the second point:

Slide 127

Slide 127 text

Slide № 127 Ruby has a very rich core library Compared to many other languages, I can do a lot using only built-in methods, without having to `npm install` anything or import any library. A lot is built-in.

Slide 128

Slide 128 text

Slide № 128 My favorite tricks Skipping map, select, inject, compact, sort_by, and most basic FP tricks found in most languages. Next, I will talk about my favorite tricks. I will skip the most basic functional programming techniques like `map`, `reduce`, `filter`, and will instead focus on the patterns that I use very often.

Slide 129

Slide 129 text

Slide № 129 Generate array contents programmatically. 3 123 456 789 stdin When solving programming challenges, I often have to read an array of numbers from an input file. The first line is the number of elements, and subsequent lines contain each element’s value.

Slide 130

Slide 130 text

Slide № 130 n = gets.to_i a = Array.new(n) (0...n).each do |i| a[i] = gets.to_i end => [123, 456, 789] Generate array contents programmatically. 3 123 456 789 stdin Most people I know would write a loop like this. Again, in their favorite language.

Slide 131

Slide 131 text

Slide № 131 Array.new(gets.to_i) { gets.to_i } => [123, 456, 789] Generate array contents programmatically. 3 123 456 789 stdin But you can also pass a block to Array initializer, and it’s going to run the block for each element. So, that loop has been reduced to a single line.

Slide 132

Slide 132 text

Slide № 132 2.4.1 :001 > tally = Hash.new(0) => {} 2.4.1 :002 > tally['b'] => 0 # normal hash: nil 2.4.1 :003 > tally['a'] += 1 => 1 # normal hash: undefined method `+` for nil 2.4.1 :004 > tally => {"a"=>1} Hash can have default value Hashes in Ruby can have a default value, so it’s really easy to do tallying without having to worry about `nil` when reading nonexistent keys from the hash.

Slide 133

Slide 133 text

Slide № 133 2.4.1 :001 > visited = Hash.new(false) => {} 2.4.1 :002 > visited[[0, 0]] = true => true Array can be hash keys In Ruby you can also use arrays as hash keys. This is very useful for representing sparse matrices. Also useful when walking on a 2D map, keeping track of which (x, y) coordinates have been visited.

Slide 134

Slide 134 text

Slide № 134 Useful array methods 2.4.1 :001 > [1, 2, 3, 4, 5].rotate(2) => [3, 4, 5, 1, 2] 2.4.1 :002 > [1, 2, 3, 4, 5].rotate(-2) => [4, 5, 1, 2, 3] Ruby has a very rich array methods, such as rotate…

Slide 135

Slide 135 text

Slide № 135 Useful array methods 2.4.1 :001 > [1, 2, 3, 4].permutation.to_a => [[1, 2, 3, 4], [1, 2, 4, 3], [1, 3, 2, 4], [1, 3, 4, 2], [1, 4, 2, 3], [1, 4, 3, 2], [2, 1, 3, 4], [2, 1, 4, 3], [2, 3, 1, 4], [2, 3, 4, 1], [2, 4, 1, 3], [2, 4, 3, 1], [3, 1, 2, 4], [3, 1, 4, 2], [3, 2, 1, 4], [3, 2, 4, 1], [3, 4, 1, 2], [3, 4, 2, 1], [4, 1, 2, 3], [4, 1, 3, 2], [4, 2, 1, 3], [4, 2, 3, 1], [4, 3, 1, 2], [4, 3, 2, 1]] …or permutation, which gives you all possible permutations of array elements.

Slide 136

Slide 136 text

Slide № 136 Useful array methods 2.4.1 :001 > [1, 2, 3, 4].permutation(2).to_a => [ [1, 2], [1, 3], [1, 4], [2, 1], [2, 3], [2, 4], [3, 1], [3, 2], [3, 4], [4, 1], [4, 2], [4, 3]] You can specify the number of elements to permute…

Slide 137

Slide 137 text

Slide № 137 Useful array methods 2.4.1 :001 > [1, 2, 3, 4].repeated_permutation(2).to_a => [[1, 1], [1, 2], [1, 3], [1, 4], [2, 1], [2, 2], [2, 3], [2, 4], [3, 1], [3, 2], [3, 3], [3, 4], [4, 1], [4, 2], [4, 3], [4, 4]] …and whether you want repeated elements or not.

Slide 138

Slide 138 text

Slide № 138 Useful array methods 2.4.1 :001 > [1, 2, 3, 4].combination(2).to_a => [ [1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]] `combination` gives you all possible ways to choose N elements from an array…

Slide 139

Slide 139 text

Slide № 139 Useful array methods 2.4.1 :001 > [1, 2, 3, 4].repeated_combination(2).to_a => [[1, 1], [1, 2], [1, 3], [1, 4], [2, 2], [2, 3], [2, 4], [3, 3], [3, 4], [4, 4]] …and again, you can choose whether you want repeated elements.

Slide 140

Slide 140 text

Slide № 140 Useful enumerable methods 2.4.1 :001 > [1, 2, 3, 4, 5, 6, 7].each_cons(3).to_a => [[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6], [5, 6, 7]] For things that are Enumerable, I find myself using `each_cons` a lot.

Slide 141

Slide 141 text

Slide № 141 Useful enumerable methods 2.4.1 :001 > [1, 2, 3, 4, 5, 6, 7].each_slice(3).to_a => [[1, 2, 3], [4, 5, 6], [7]] `each_slice` too, useful when processing things in chunks.

Slide 142

Slide 142 text

Slide № 142 Rational numbers 2.4.1 :001 > 4/6r => (2/3) Rational numbers in Ruby are super useful when I have to deal with fractions but don’t want to deal with floating point rounding errors.

Slide 143

Slide 143 text

Slide № 143 Hash <=> Enumerable <=> Array File.readlines('/usr/share/dict/words') .group_by { |w| w.chars.first.downcase } .map { |char, words| [char, words.count] } .to_h => {"a"=>17096, "b"=>11070, "c"=>19901, "d"=>10896, "e"=>8736, "f"=>6860, "g"=>6861, "h"=>9027, "i"=>8799, "j"=>1642, "k"=>2281, "l"=>6284, "m"=>12616, "n"=>6780, "o"=>7849, "p"=>24461, "q"=>1152, "r"=>9671, "s"=>25162, "t"=>12966, "u"=>16387, "v"=>3440, "w"=>3944, "x"=>385, "y"=>671, "z"=>949} Enumerable#to_a Enumerable#to_h One thing I especially like about Ruby is that both Arrays and Hashes are Enumerable. And you can convert an Enumerable into both an Array and a Hash.

Slide 144

Slide 144 text

Slide № 144 Lambdas 2.4.1 :001 > -> x, y { x*x + 2*x*y + y*y }[15, 2] => 289 And finally, lambda expressions are really useful when I want to temporarily refer to a value multiple times.

Slide 145

Slide 145 text

Slide № 145 Day-to-day programming Apart from competitive programming, I find myself using Ruby a lot in… …again, I mostly write JavaScript.

Slide 146

Slide 146 text

Slide № 146 JavaScript refactoring

Slide 147

Slide 147 text

Slide № 147 export const TYPE_TASK_CREATE = 1 export const TYPE_TASK_DELETE = 2 export const TYPE_PROJECT_CREATE_OR_UPDATE = 3 export const TYPE_TASK_UPDATE = 5 export const TYPE_PROJECT_UPDATE = 6 export const TYPE_PROJECT_DELETE = 7 export const TYPE_DIRECT_MESSAGE = 11 export const TYPE_TASK_COMMENT = 12 export const TYPE_UPCOMING = 13 export const TYPE_OVERDUE = 14 (This comes from actual production app) We are converting part of our app to TypeScript.

Slide 148

Slide 148 text

Slide № 148 export const TYPE_TASK_CREATE = 1 export const TYPE_TASK_DELETE = 2 export const TYPE_PROJECT_CREATE_OR_UPDATE = 3 export const TYPE_TASK_UPDATE = 5 export const TYPE_PROJECT_UPDATE = 6 export const TYPE_PROJECT_DELETE = 7 export const TYPE_DIRECT_MESSAGE = 11 export const TYPE_TASK_COMMENT = 12 export const TYPE_UPCOMING = 13 export const TYPE_OVERDUE = 14 Convert to TypeScript enum

Slide 149

Slide 149 text

Slide № 149 export const TYPE_TASK_CREATE = 1 export const TYPE_TASK_DELETE = 2 export const TYPE_PROJECT_CREATE_OR_UPDATE = 3 export const TYPE_TASK_UPDATE = 5 export const TYPE_PROJECT_UPDATE = 6 export const TYPE_PROJECT_DELETE = 7 export const TYPE_DIRECT_MESSAGE = 11 export const TYPE_TASK_COMMENT = 12 export const TYPE_UPCOMING = 13 export const TYPE_OVERDUE = 14 export enum NotificationType { TaskCreate = 1, TaskDelete = 2, ProjectCreateOrUpdate = 3, TaskUpdate = 5, ProjectUpdate = 6, ProjectDelete = 7, DirectMessage = 11, TaskComment = 12, Upcoming = 13, Overdue = 14, Convert to TypeScript enum

Slide 150

Slide 150 text

Slide № 150 export const TYPE_TASK_CREATE = 1 export const TYPE_TASK_DELETE = 2 export const TYPE_PROJECT_CREATE_OR_UPDATE = 3 export const TYPE_TASK_UPDATE = 5 export const TYPE_PROJECT_UPDATE = 6 export const TYPE_PROJECT_DELETE = 7 export const TYPE_DIRECT_MESSAGE = 11 export const TYPE_TASK_COMMENT = 12 export const TYPE_UPCOMING = 13 export const TYPE_OVERDUE = 14 export enum NotificationType { TaskCreate = 1, TaskDelete = 2, ProjectCreateOrUpdate = 3, TaskUpdate = 5, ProjectUpdate = 6, ProjectDelete = 7, DirectMessage = 11, TaskComment = 12, Upcoming = 13, Overdue = 14, Convert to TypeScript enum TYPE_TASK_CREATE => TaskCreate Following TypeScript’s naming convention, some naming scheme has to be changed.

Slide 151

Slide 151 text

Slide № 151 export const TYPE_TASK_CREATE = 1 export const TYPE_TASK_DELETE = 2 export const TYPE_PROJECT_CREATE_OR_UPDATE = 3 export const TYPE_TASK_UPDATE = 5 export const TYPE_PROJECT_UPDATE = 6 export const TYPE_PROJECT_DELETE = 7 export const TYPE_DIRECT_MESSAGE = 11 export const TYPE_TASK_COMMENT = 12 export const TYPE_UPCOMING = 13 export const TYPE_OVERDUE = 14 export enum NotificationType { TaskCreate = 1, TaskDelete = 2, ProjectCreateOrUpdate = 3, TaskUpdate = 5, ProjectUpdate = 6, ProjectDelete = 7, DirectMessage = 11, TaskComment = 12, Upcoming = 13, Overdue = 14,

Slide 152

Slide 152 text

Slide № 152 export const TYPE_TASK_CREATE = 1 export const TYPE_TASK_DELETE = 2 export const TYPE_PROJECT_CREATE_OR_UPDATE = 3 export const TYPE_TASK_UPDATE = 5 export const TYPE_PROJECT_UPDATE = 6 export const TYPE_PROJECT_DELETE = 7 export const TYPE_DIRECT_MESSAGE = 11 export const TYPE_TASK_COMMENT = 12 export const TYPE_UPCOMING = 13 export const TYPE_OVERDUE = 14 export enum NotificationType { TaskCreate = 1, TaskDelete = 2, ProjectCreateOrUpdate = 3, TaskUpdate = 5, ProjectUpdate = 6, ProjectDelete = 7, DirectMessage = 11, TaskComment = 12, Upcoming = 13, Overdue = 14, Deduplicate

Slide 153

Slide 153 text

Slide № 153 export const TYPE_TASK_CREATE = NotificationType.TaskCreate export const TYPE_TASK_DELETE = NotificationType.TaskDelete export const TYPE_PROJECT_CREATE_OR_UPDATE = NotificationType.ProjectCreateOrUpdate export const TYPE_TASK_UPDATE = NotificationType.TaskUpdate export const TYPE_PROJECT_UPDATE = NotificationType.ProjectUpdate export const TYPE_PROJECT_DELETE = NotificationType.ProjectDelete export const TYPE_DIRECT_MESSAGE = NotificationType.DirectMessage export const TYPE_TASK_COMMENT = NotificationType.TaskComment export const TYPE_UPCOMING = NotificationType.Upcoming export const TYPE_OVERDUE = NotificationType.Overdue export enum NotificationType { TaskCreate = 1, TaskDelete = 2, ProjectCreateOrUpdate = 3, TaskUpdate = 5, ProjectUpdate = 6, ProjectDelete = 7, DirectMessage = 11, TaskComment = 12, Upcoming = 13, Overdue = 14, (The old API is still available to minimize breaking change, though deprecated)

Slide 154

Slide 154 text

Slide № 154 Live demo puts code.scan(/(TYPE_\w+) = (\d+)/)
 .map { |name, value| "#{name.split('_').map(&:capitalize).drop(1).join} = {value}" } .join(",\n") Part 1: Generate the enum entries. puts code.gsub(/(TYPE_\w+) = (\d+)/) { "#{$1} = NotificationType.#{$1.split('_').map(&:capitalize).drop(1).join}" } Part 2: Use the enum in legacy API.

Slide 155

Slide 155 text

Slide № 155 Conclusions

Slide 156

Slide 156 text

Slide № 156 Tight feedback loop Ruby has… via irb

Slide 157

Slide 157 text

Slide № 157 Tight feedback loop + Simple, expressive, not too strict syntax Ruby has…

Slide 158

Slide 158 text

Slide № 158 Tight feedback loop + Simple, expressive, not too strict syntax + Great and intuitive built-in library Ruby has…

Slide 159

Slide 159 text

Slide № 159 Tight feedback loop + Simple, expressive, not too strict syntax + Great and intuitive built-in library ↓ ↓ ↓ Great language for solving problems! Ruby has…

Slide 160

Slide 160 text

Slide № 160 Effective problem solving in Ruby API docs for: String, Numeric, Rational, Range, Array, Hash, Enumerable, Proc stdlib: matrix, prime, pp For me to solve problems effectively in Ruby,
 I find myself reading through the API docs of common classes every now and then… …reading through each methods in a class, from top to bottom… I recommend that you do this too — This may sound like an obvious thing to do, but I don’t see developers doing this enough. Me included — I was not aware of the recently-added `sum` method…

Slide 161

Slide 161 text

Slide № 161 Thank you!!