Advent of Code 2020: Day 2 (Ruby solution)

This time, the tasks are to count valid passwords from a text file.

Each line of text contains two numbers, a letter, and a password. Example input:

1-8 n: dpwpmhknmnlglhjtrbpx
11-12 n: frpknnndpntnncnnnnn
4-8 t: tmttdtnttkr
12-18 v: vvvvvvvqvvvvvqvvgf
3-4 c: cccc
17-18 z: zzzzzzzzdzzzzzzgzr

In the first task, the password is valid if the given letter occurs between the first given number and the second given number of times.

In another task, the password is valid if that letter occurs on exactly one of positions specified by given numbers. If it appears on both positions, the password is invalid though ("exclusive or" operator).

I decided to use a strategy pattern to define two different password policy classes.

class PasswordPolicy
  def initialize policy_param_0, policy_param_1, letter
    @policy_param_0 = policy_param_0
    @policy_param_1 = policy_param_1
    @letter = letter
  end
end

class FirstPasswordPolicy < PasswordPolicy
  def valid? phrase
    phrase.scan(/#{@letter}/).size.between?(@policy_param_0, @policy_param_1)
  end
end

class SecondPasswordPolicy < PasswordPolicy
  def valid? phrase
    (phrase[@policy_param_0 - 1] == @letter) ^ (phrase[@policy_param_1 - 1] == @letter)
  end
end

def count_valid_passwords file, policy_class
  i = 0
  file.each_line do |line|
    param_0 = line.scan(/\d+/).first.to_i
    param_1 = line.scan(/\d+/).last.to_i
    letter = line.scan(/[[:alpha:]]/).first
    phrase = line.scan(/[[:alpha:]]+/).last
    policy = policy_class.new(param_0, param_1, letter)
    i += 1 if policy.valid?(phrase)
  end
  i
end

file = File.read('inputs/day2.txt')

puts count_valid_passwords(file, FirstPasswordPolicy)
puts count_valid_passwords(file, SecondPasswordPolicy)