Caleb Weeks

Posted on

# Advent of Code #14 (in Crystal)

My brother and I worked on this puzzle together. That is partly why the solution is more imperative than functional. Admittedly, though, I have found many of these Advent of Code puzzles easier to reason about with an imperative approach.

My brother suggested finding each stone and moving it up if the space above it was empty for each row. I was planning on over complicating it my looking how far the stone could move. In the end, I really like how the code turned out, and extending it to part 2 wasn't terrible.

Speaking of part 2, it was my brother's idea again that the stones would probably stabilize well before a billion cycles, and that we wouldn't have to get that far. We tested out the theory, and then after identifying a cycle in the outputs, reduced the potential answers down to eight candidates. We got the right answer after 5 guesses.

I tried to code up a solution to return the correct answer instead of just spitting out the weights for each cycle and having to guess. Admittedly, I'm not sure if the final code is correct, but it works for my input. It doesn't work for the example because the cycle for the example contains duplicate numbers, which I wasn't sure how to account for.

Here's my code, which may or may not work on your input:

``````input = File.read("input").strip

rows = input.split("\n").map(&.chars)

def tilt(rows)
(1...rows.size).each do |row_num|
(0...row_num).each do |prev|
current_row = row_num - prev
row_above = current_row - 1
stones = rows[current_row].join.scan(/O/).map(&.begin)
stones.each do |stone|
if rows[row_above][stone] == '.'
rows[row_above][stone] = 'O'
rows[current_row][stone] = '.'
end
end
end
end
end

def weigh(rows)
(0...rows.size).reduce(0) do |sum, row_num|
sum + rows[row_num].join.scan("O").size * (rows.size - row_num)
end
end

part1 = begin
tilt(rows)
weigh(rows)
end

puts part1

cache = Set(String).new

part2 = begin
last = -1
current = 0
i = 1
cycle_start = nil
cycle = Array(Int32).new
while !cycle_start || !cycle.group_by { |x| x }.values.map { |x| x.size > 4 }.all?
if i % 4 == 1
cycle_start = i if last == current
weight = weigh(rows)
cycle.push(weight) if cycle_start
last = current
current = cache.size
end
tilt(rows)
rows = rows.reverse.transpose
i += 1
end
cycle = cycle[...cycle.size // 5]
cycle[(4_000_000_000 - i + 4) % cycle.size]
end

puts part2
``````