2 min read

Fun with Ruby Pattern Matching

Pattern matching emerged in experimental status in Ruby 2.7, I got my first chance to properly play with an implementation that uses it
Fun with Ruby Pattern Matching
Photo by Nick Fewings / Unsplash

Pattern matching emerged in experimental status in Ruby 2.7, I got my first chance to properly play with an implementation that uses it.

I love playing Advent of Code: every December a code puzzle every day of advent. This year's Day 12 has you processing a series of instructions each of which have an integer value attached.

There are seven commands, N, E, S, W, L, R anf F. Here's what they look like (from the published example):

F10
N3
F7
R90
F11

I'll let you have a little read of the puzzle text if you want the detail but in essence, the compass cardinal points move you in that direction. L and R rotate you a number of degrees (always 90, 180 or 270) and F moves you forward in the direction you are currently facing.

At this point, I've parsed the instructions out into an array of arrays, like this:

[
 ["F", 10],
 ["N", 3],
 ["F", 7],
 ["R", 90],
 ["F", 11]
]

There's lots of ways I can skip through matching commands but pattern matching makes this very readable:

steps.each do |step|
  case step
  in 'N', val
    # val equals step[1] here
  in 'S', val
    # val equals step[1] here
  end
end

I can both do something conditionally and bind part of the array to a variable. Very nice.

Now, in part 2 of the puzzle (which you can't see unless you solve part 1) I needed to do much the same but I also needed to treat two of the commands the same and change behaviour according to the value of val. I've seen some mention of using alternative patterns in matching but wasn't quite sure how this worked. For my case, it was perfect, here's some code:

steps.each do |step|
  case step
  in 'N', val
    # as before
  in 'S', val
    # as before
  in 'L', 90
    # match only ["L", 90]
  in 'L' | 'R', 180
    # match either ["L", 180] OR ["R", 180]
  in 'L', 270
    # match only ["L", 270]
  in 'R', 90
    # match only ["R", 90]
  in 'R', 270
    # match only ["R", 270]
  # ...
  end
end

Now that's pretty awesomely powerful, especially line 9 and saves the world from a mass of nested ifs.