Advent of Code 2024 - Day 09
Mix.install([
{:req, "~> 0.5"},
{:benchee, "~> 1.3"}
])
Input
opts = [headers: [{"cookie", "session=#{System.fetch_env!("LB_AOC_SESSION")}"}]]
puzzle_input = Req.get!("https://adventofcode.com/2024/day/9/input", opts).body
Helpers
defmodule DiskFragmenter do
def parse_disk_map(puzzle_input) do
String.trim(puzzle_input)
|> String.graphemes()
|> Enum.chunk_every(2, 2)
|> Enum.with_index()
|> Enum.flat_map(fn
{[file, space], i} ->
List.duplicate("#{i}", String.to_integer(file)) ++
List.duplicate(".", String.to_integer(space))
{[file], i} ->
List.duplicate("#{i}", String.to_integer(file))
end)
end
def calculate_checksum(list) do
Enum.with_index(list)
|> Enum.reduce(0, fn
{value, i}, acc when value != "." ->
i * String.to_integer(value) + acc
_el, acc ->
acc
end)
end
def fill_dots(list, acc \\ [])
def fill_dots([], acc), do: acc
def fill_dots([char | rest], acc) when char == "." do
case Enum.reverse(rest) |> Enum.find_index(&(&1 != ".")) do
nil ->
fill_dots(rest, acc ++ [char])
i ->
{value, rest} = List.pop_at(rest, length(rest) - 1 - i)
fill_dots(rest, acc ++ [value])
end
end
def fill_dots([char | rest], acc) do
fill_dots(rest, acc ++ [char])
end
def fill_blocks(blocks, acc \\ [])
def fill_blocks([], acc), do: List.flatten(acc)
def fill_blocks([[first | _rest] = block | rest], acc) when first == "." do
case find_suitable_block(rest, length(block)) do
nil ->
fill_blocks(rest, acc ++ [block])
{num_block, index} ->
extra_dots = length(block) - length(num_block)
remaining_dots = if extra_dots > 0, do: [List.duplicate(".", extra_dots)], else: []
new_rest = List.replace_at(rest, index, List.duplicate(".", length(num_block)))
fill_blocks(remaining_dots ++ new_rest, acc ++ [num_block])
end
end
def fill_blocks([block | rest], acc), do: fill_blocks(rest, acc ++ [block])
defp find_suitable_block(blocks, dot_size) do
blocks
|> Enum.reverse()
|> Enum.with_index()
|> Enum.find(fn {[first | _] = list, _} ->
first != "." && length(list) <= dot_size
end)
|> case do
nil -> nil
{block, i} -> {block, length(blocks) - 1 - i}
end
end
end
Puzzle 1
puzzle_1 = fn ->
DiskFragmenter.parse_disk_map(puzzle_input)
|> DiskFragmenter.fill_dots()
|> DiskFragmenter.calculate_checksum()
end
puzzle_1.()
Puzzle 2
puzzle_2 = fn ->
DiskFragmenter.parse_disk_map(puzzle_input)
|> Enum.chunk_by(&(&1 == "."))
|> Enum.flat_map(fn el ->
Enum.chunk_by(el, & &1)
end)
|> DiskFragmenter.fill_blocks()
|> DiskFragmenter.calculate_checksum()
end
puzzle_2.()