Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Solving algorithmic problems in Ruby

Solving algorithmic problems in Ruby

In this presentation, I introduced Ruby as a great programming language for solving algorithmic problems.

This talk highlights:
- Tight feedback loop through irb.
- Rich and intuitive built-in libraries.
- Simple and expressive syntax.

47fd6e7bd2b7bf8f2e3f2a51c7ffa53d?s=128

Thai Pangsakulyanont

May 30, 2018
Tweet

Transcript

  1. Thai Pangsakulyanont 1 Solving algorithmic problems in Ruby Ruby Wednesday

    #4
  2. About Me Slide № 2 @dtinth Thai Pangsakulyanont

  3. About Me Slide № 3 JavaScript developer Thai Pangsakulyanont I

    am a… (speaking in a Ruby meetup)
  4. About Me Slide № 4 Front-end architect @ Taskworld Thai

    Pangsakulyanont
  5. 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!
  6. Slide № 6 Today: Using Ruby to solve problems

  7. 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)
  8. Slide № 8 Today: Using Ruby to solve problems -

    Competitive programming - Practical, daily life stuff
  9. Slide № 9 Advent of Code Last year, I’ve been

    playing with this thing called…
  10. Slide № 10 Advent of Code Yearly programming competition

  11. Slide № 11 Advent of Code Yearly programming competition December

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

    1~25 Each day @ 12:00, a new problem 12:00pm Thailand Time
  13. Slide № 13 Advent of Code Each problem, two puzzles

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

    Each puzzle solved gives you one star
  15. 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.
  16. Slide № 16 Goal Collect 50 stars Competition But there’s

    also this competition part, and it’s a really fierce competition.
  17. Slide № 17 Goal Collect 50 stars Competition Let’s look

    at the leaderboard.
  18. Slide № 18 Goal Collect 50 stars Competition Here’s how

    it works…
  19. Slide № 19 Goal Collect 50 stars Competition • The

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

    first person to collect
 each star gets 100 pts. • The second person, 99 pts • (…)
  21. 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.
  22. Slide № 22 Now, let’s go ahead and look at

    the first day’s problem.
  23. Slide № 23

  24. Slide № 24 When you enter, you will see a

    wall of text that you have to quickly read through.
  25. Slide № 25 Since it’s the first day, here it

    explains the rules of this game.
  26. Slide № 26 Scrolling down, you’ll encounter…

  27. Slide № 27 The problem statement…

  28. Slide № 28 …and some example cases. Before we dive

    in, let’s look at the leaderboard for this problem.
  29. 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.
  30. Slide № 30 Now let’s look at each example.

  31. Slide № 31

  32. Slide № 32 1122 I’ll explain how to solve this

    puzzle quickly. Basically, you have to look for…
  33. Slide № 33 1122 +1 +2 …the digits that match

    the next digit. Sum them all to obtain the puzzle’s answer.
  34. Slide № 34 1122 +1 +2 Solution: 3

  35. Slide № 35 1111

  36. Slide № 36 1111 +1 +1 +1 +1 As you

    can see, it also wraps around.
  37. Slide № 37 1111 +1 Solution: 4 +1 +1 +1

  38. Slide № 38 1234 How about this one?

  39. Slide № 39 1234 Solution: 0

  40. Slide № 40 91212129 And this one?

  41. Slide № 41 91212129 +9

  42. Slide № 42 91212129 Solution: 9 +9

  43. Slide № 43 Devising a solution After understanding the problem,

    now it’s time to devise a solution.
  44. Slide № 44 1223334551 Solution: 0 The most obvious way

    to solve this is to go through each digit, …
  45. Slide № 45 1223334551 Solution: 0 …comparing it to the

    next…
  46. Slide № 46 1223334551 Solution: 0

  47. Slide № 47 1223334551 Solution: 0

  48. Slide № 48 1223334551 +2 Solution: 2 …and if they

    match, add it to the accumulator…
  49. Slide № 49 1223334551 Solution: 2

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

  51. Slide № 51 1223334551 +3 Solution: 5

  52. Slide № 52 1223334551 +3 Solution: 8

  53. Slide № 53 1223334551 +5 Solution: 13

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

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

    in the accumulator.
  56. Slide № 56 Let’s quickly write some code for this!

  57. Slide № 57 input = "1223334551"

  58. Slide № 58 input = "1223334551" solution = 0

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

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

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

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

    do |i| if input[i] == input[(i + 1) % input.length] end end wrapping around
  63. 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
  64. 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
  65. 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.
  66. 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)
  67. 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.
  68. 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.
  69. Slide № 69 Solving the problem again, but with faster

    feedback loop
  70. Slide № 70 irb Interactive Ruby Interactive Ruby runs your

    code line-by-line, giving you immediate feedback after each statement.
  71. Slide № 71 $ irb

  72. Slide № 72 $ irb 2.4.1 :001 > Running `irb`

    puts me into the Ruby shell.
  73. Slide № 73 $ irb 2.4.1 :001 > input =

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

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

    "1223334551" => "1223334551" 2.4.1 :002 > input.chars Let’s try breaking it down by character.
  76. 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 >
  77. 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.
  78. 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…
  79. 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…
  80. 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.
  81. 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 >
  82. 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])
  83. 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.
  84. 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 >
  85. 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.
  86. 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
  87. 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.
  88. 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]
  89. 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 >
  90. 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…
  91. 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.
  92. 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) => #<Enumerator: [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1]:each_cons(2)> 2.4.1 :007 > It returns an Enumerator that lets me iterate through each 2 consecutive digits.
  93. 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) => #<Enumerator: [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1]:each_cons(2)> 2.4.1 :007 > (input + input[0]).chars.map(&:to_i).each_cons(2) To see how the iteration looks like…
  94. 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) => #<Enumerator: [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1]: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.
  95. 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) => #<Enumerator: [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1]: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 >
  96. 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) => #<Enumerator: [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1]: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.
  97. 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) => #<Enumerator: [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1]: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
  98. 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) => #<Enumerator: [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1]: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) \ .
  99. 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) => #<Enumerator: [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1]: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…
  100. 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) => #<Enumerator: [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1]: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
  101. 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) => #<Enumerator: [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1]: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…
  102. 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) => #<Enumerator: [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1]: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.
  103. 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) => #<Enumerator: [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1]: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 #<Enumerator: [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1]:each_cons(2)> from (irb):8
  104. 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 #<Enumerator: [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1]:each_cons(2)> from (irb):8 from /Users/dtinth/.rvm/rubies/ruby-2.4.1/bin/irb:11:in `<main>' 2.4.1 :009 > As I’m a JavaScript user, I always forget that in Ruby, `filter` is called `select`.
  105. 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 #<Enumerator: [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1]:each_cons(2)> from (irb):8 from /Users/dtinth/.rvm/rubies/ruby-2.4.1/bin/irb:11:in `<main>' 2.4.1 :009 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .filter { |a, b| a == b }
  106. 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 #<Enumerator: [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1]:each_cons(2)> from (irb):8 from /Users/dtinth/.rvm/rubies/ruby-2.4.1/bin/irb:11:in `<main>' 2.4.1 :009 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ . { |a, b| a == b }
  107. 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 #<Enumerator: [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1]:each_cons(2)> from (irb):8 from /Users/dtinth/.rvm/rubies/ruby-2.4.1/bin/irb:11:in `<main>' 2.4.1 :009 > (input + input[0]).chars.map(&:to_i).each_cons(2) \ .select { |a, b| a == b }
  108. 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 #<Enumerator: [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1]:each_cons(2)> from (irb):8 from /Users/dtinth/.rvm/rubies/ruby-2.4.1/bin/irb:11:in `<main>' 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.
  109. 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 #<Enumerator: [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1]:each_cons(2)> from (irb):8 from /Users/dtinth/.rvm/rubies/ruby-2.4.1/bin/irb:11:in `<main>' 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 >
  110. 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 #<Enumerator: [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1]:each_cons(2)> from (irb):8 from /Users/dtinth/.rvm/rubies/ruby-2.4.1/bin/irb:11:in `<main>' 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…
  111. 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 #<Enumerator: [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1]:each_cons(2)> from (irb):8 from /Users/dtinth/.rvm/rubies/ruby-2.4.1/bin/irb:11:in `<main>' 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.
  112. 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 #<Enumerator: [1, 2, 2, 3, 3, 3, 4, 5, 5, 1, 1]:each_cons(2)> from (irb):8 from /Users/dtinth/.rvm/rubies/ruby-2.4.1/bin/irb:11:in `<main>' 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]
  113. 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 >
  114. 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…
  115. 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!)
  116. 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.
  117. 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
  118. 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.
  119. 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.
  120. Slide № 120 Ruby allows for very fast feedback loop

  121. Slide № 121 If you use a modern text editor…

  122. Slide № 122 …you can edit your code inside the

    text editor…
  123. Slide № 123 …and run a command that will send

    the current line into the terminal…
  124. Slide № 124 …this is how I like to solve

    problems in Ruby.
  125. 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…
  126. 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:
  127. 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.
  128. 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.
  129. 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.
  130. 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.
  131. 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.
  132. 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.
  133. 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.
  134. 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…
  135. 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.
  136. 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…
  137. 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.
  138. 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…
  139. 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.
  140. 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.
  141. 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.
  142. 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.
  143. 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.
  144. 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.
  145. Slide № 145 Day-to-day programming Apart from competitive programming, I

    find myself using Ruby a lot in… …again, I mostly write JavaScript.
  146. Slide № 146 JavaScript refactoring

  147. 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.
  148. 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
  149. 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
  150. 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.
  151. 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,
  152. 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
  153. 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)
  154. 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.
  155. Slide № 155 Conclusions

  156. Slide № 156 Tight feedback loop Ruby has… via irb

  157. Slide № 157 Tight feedback loop + Simple, expressive, not

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

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

    too strict syntax + Great and intuitive built-in library ↓ ↓ ↓ Great language for solving problems! Ruby has…
  160. 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…
  161. Slide № 161 Thank you!!