Advent of Code 2020: Day 11 (Ruby solution)
This time the tasks were about modeling people taking seats in the waiting area. The seat layout is given as an input file.
The process of taking seats is iterative. Rules which are applied to every seat simultaneously differ for the first and second tasks.
In the first puzzle:
- If a seat is empty (L) and there are no occupied seats adjacent to it, the seat becomes occupied.
- If a seat is occupied (#) and four or more seats adjacent to it are also occupied, the seat becomes empty.
- Otherwise, the seat's state does not change.
- Floor (.) never changes; seats don't move, and nobody sits on the floor.
In the second one, instead of considering just the eight immediately adjacent seats, consider the first seat in each of the eight directions. It now takes five or more visible occupied seats for an occupied seat to become empty. The rest of the rules stay unchanged.
To deal with those tasks, I implemented WaitingArea class and two different taking seat policies.
require 'matrix'
class WaitingArea
def initialize file
@seats = Matrix.rows file.lines.map { |line| line.strip.chars }
end
def take_seats policy_class
policy = policy_class.new(@seats)
new_seats = @seats.dup
@seats.each_with_index do |e, row, col|
next if e == '.'
new_seats[row, col] = policy.take_seat?(row, col) ? '#' : 'L'
end
@seats = new_seats
end
def occupied_seats
@seats.select { |adjacent_seat| adjacent_seat == '#' }.size
end
end
class SimpleTakingSeatPolicy
def initialize seats
@seats = seats
end
def take_seat? row, col
seat = @seats[row, col]
adjacent_seats = @seats.minor(
[row - 1, 0].max..[row + 1, @seats.row_count - 1].min,
[col - 1, 0].max..[col + 1, @seats.column_count - 1].min
)
occupied_adjacent_seats = adjacent_seats.select { |adjacent_seat| adjacent_seat == '#' }.size
return occupied_adjacent_seats <= 4 if seat == '#'
occupied_adjacent_seats.zero?
end
end
class AlternativeTakingSeatPolicy
def initialize seats
@seats = seats
end
def take_seat? row, col
seat = @seats[row, col]
occupied_visible_seats = 0
([-1, 0, +1].repeated_permutation(2).to_a - [[0, 0]]).each do |row_mod, col_mod|
i = 0
while i += 1
x = row + (row_mod * i)
y = col + (col_mod * i)
break if x < 0 || x >= @seats.row_count
break if y < 0 || y >= @seats.column_count
tmp_seat = @seats[x, y]
next if tmp_seat == '.'
occupied_visible_seats += 1 if tmp_seat == '#'
break
end
end
return occupied_visible_seats < 5 if seat == '#'
occupied_visible_seats.zero?
end
end
file = File.read('inputs/day11.txt')
[SimpleTakingSeatPolicy, AlternativeTakingSeatPolicy].each_with_index do |policy, index|
craft = WaitingArea.new(file)
occupied_seats = nil
while occupied_seats != occupied_seats = craft.occupied_seats
craft.take_seats(policy)
end
puts craft.occupied_seats
end