Advent of Code - Day 15
Mix.install([
{:kino_aoc, "~> 0.1"}
])
Introduction
–> Content
Puzzle
{:ok, puzzle_input} =
KinoAOC.download_puzzle("2023", "15", System.fetch_env!("LB_AOC_SESSION"))
Parser
Code - Parser
defmodule Parser do
def parse(input) do
input
|> String.replace("\n", "")
|> String.split(",", trim: true)
end
end
Tests - Parser
ExUnit.start(autorun: false)
defmodule ParserTest do
use ExUnit.Case, async: true
import Parser
@input """
rn=1,cm-,qp=3,cm=2,\nqp-,pc=\n4,ot=9,ab=5,pc-,pc=6,ot=7
"""
@expected ["rn=1", "cm-", "qp=3", "cm=2", "qp-", "pc=4", "ot=9", "ab=5", "pc-", "pc=6", "ot=7"]
describe "parse/1" do
test "simple example" do
assert parse(@input) == @expected
end
end
end
ExUnit.run()
Part One
Code - Part 1
defmodule PartOne do
def solve(input) do
IO.puts("--- Part One ---")
IO.puts("Result: #{run(input)}")
end
def run(input_string) when is_bitstring(input_string), do: run(Parser.parse(input_string))
def run(inputs) do
inputs
|> Enum.map(fn input -> hash_alg(input) end)
|> Enum.sum()
end
def hash_alg(input) do
input
|> to_charlist()
|> Enum.reduce(0, fn char, res -> rem((res + char) * 17, 256) end)
end
end
Tests - Part 1
ExUnit.start(autorun: false)
defmodule PartOneTest do
use ExUnit.Case, async: true
import PartOne
@raw_input """
rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7
"""
@input Parser.parse(@raw_input)
describe "run/1" do
test "main example" do
assert run(@raw_input) == 1320
end
end
describe "hash_alg/1" do
test "main examples" do
assert hash_alg("rn=1") == 30
assert hash_alg("cm-") == 253
assert hash_alg("qp=3") == 97
assert hash_alg("cm=2") == 47
assert hash_alg("qp-") == 14
assert hash_alg("pc=4") == 180
assert hash_alg("ot=9") == 9
assert hash_alg("ab=5") == 197
assert hash_alg("pc-") == 48
assert hash_alg("pc=6") == 214
assert hash_alg("ot=7") == 231
end
end
end
ExUnit.run()
Solution - Part 1
PartOne.solve(puzzle_input)
Part Two
Code - Part 2
defmodule PartTwo do
def solve(input) do
IO.puts("--- Part Two ---")
IO.puts("Result: #{run(input)}")
end
def run(input_string) when is_bitstring(input_string), do: run(Parser.parse(input_string))
def run(inputs) do
inputs
|> Enum.reduce(%{}, fn input, boxes ->
input |> parse_step() |> perform_step_on_boxes(boxes)
end)
|> focusing_power()
end
def hash_alg(input) do
input
|> to_charlist()
|> Enum.reduce(0, fn char, res -> rem((res + char) * 17, 256) end)
end
def parse_step(input) do
String.split(input, ~r/[\=\-]/, trim: true, include_captures: true)
|> Enum.zip([:label, :operation, :focal_length])
|> Enum.into(%{}, fn {val, key} -> {key, val} end)
|> (fn hash ->
if Map.has_key?(hash, :focal_length) do
Map.update!(hash, :focal_length, fn val -> String.to_integer(val) end)
else
hash
end
end).()
end
def perform_step_on_boxes(step, boxes) do
box_number = hash_alg(step[:label])
lens = %{label: step[:label], focal_length: step[:focal_length]}
case step[:operation] do
"-" ->
Map.update(boxes, box_number, [], fn contents ->
if index = Enum.find_index(contents, fn content -> content[:label] == step[:label] end) do
List.delete_at(contents, index)
else
contents
end
end)
|> Map.reject(fn {_k, v} -> Enum.empty?(v) end)
"=" ->
Map.update(boxes, box_number, [lens], fn contents ->
if index = Enum.find_index(contents, fn content -> content[:label] == step[:label] end) do
List.replace_at(contents, index, lens)
else
contents ++ [lens]
end
end)
end
end
def focusing_power(boxes) do
boxes
|> Enum.map(fn {box_number, lenses} ->
lenses
|> Enum.with_index(1)
|> Enum.map(fn {%{label: _label, focal_length: focal_length}, slot_number} ->
(1 + box_number) * slot_number * focal_length
end)
end)
|> List.flatten()
|> Enum.sum()
end
end
Tests - Part 2
ExUnit.start(autorun: false)
defmodule PartTwoTest do
use ExUnit.Case, async: true
import PartTwo
@raw_input """
rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7
"""
@input Parser.parse(@raw_input)
@steps Enum.map(@input, fn step_string -> parse_step(step_string) end)
describe "run/1" do
test "main example" do
assert run(@raw_input) == 145
end
end
describe "hash_alg/1" do
test "main examples" do
assert hash_alg("rn") == 0
assert hash_alg("cm") == 0
assert hash_alg("qp") == 1
assert hash_alg("cm") == 0
assert hash_alg("qp") == 1
assert hash_alg("pc") == 3
assert hash_alg("ot") == 3
assert hash_alg("ab") == 3
assert hash_alg("pc") == 3
assert hash_alg("pc") == 3
assert hash_alg("ot") == 3
end
end
describe "parse_step/1" do
test "main examples" do
assert parse_step("rn=1") == %{label: "rn", operation: "=", focal_length: 1}
assert parse_step("cm-") == %{label: "cm", operation: "-"}
assert parse_step("qp=3") == %{label: "qp", operation: "=", focal_length: 3}
assert parse_step("cm=2") == %{label: "cm", operation: "=", focal_length: 2}
assert parse_step("qp-") == %{label: "qp", operation: "-"}
assert parse_step("pc=4") == %{label: "pc", operation: "=", focal_length: 4}
assert parse_step("ot=9") == %{label: "ot", operation: "=", focal_length: 9}
assert parse_step("ab=5") == %{label: "ab", operation: "=", focal_length: 5}
assert parse_step("pc-") == %{label: "pc", operation: "-"}
assert parse_step("pc=6") == %{label: "pc", operation: "=", focal_length: 6}
assert parse_step("ot=7") == %{label: "ot", operation: "=", focal_length: 7}
end
end
describe "perform_step_on_boxes/2" do
test "main example" do
boxes = %{}
assert (boxes = perform_step_on_boxes(parse_step("rn=1"), boxes)) == %{
0 => [%{label: "rn", focal_length: 1}]
}
assert (boxes = perform_step_on_boxes(parse_step("cm-"), boxes)) == %{
0 => [%{label: "rn", focal_length: 1}]
}
assert (boxes = perform_step_on_boxes(parse_step("qp=3"), boxes)) == %{
0 => [%{label: "rn", focal_length: 1}],
1 => [%{label: "qp", focal_length: 3}]
}
assert (boxes = perform_step_on_boxes(parse_step("cm=2"), boxes)) == %{
0 => [%{label: "rn", focal_length: 1}, %{label: "cm", focal_length: 2}],
1 => [%{label: "qp", focal_length: 3}]
}
assert (boxes = perform_step_on_boxes(parse_step("qp-"), boxes)) == %{
0 => [%{label: "rn", focal_length: 1}, %{label: "cm", focal_length: 2}]
}
assert (boxes = perform_step_on_boxes(parse_step("pc=4"), boxes)) == %{
0 => [%{label: "rn", focal_length: 1}, %{label: "cm", focal_length: 2}],
3 => [%{label: "pc", focal_length: 4}]
}
assert (boxes = perform_step_on_boxes(parse_step("ot=9"), boxes)) == %{
0 => [%{label: "rn", focal_length: 1}, %{label: "cm", focal_length: 2}],
3 => [%{label: "pc", focal_length: 4}, %{label: "ot", focal_length: 9}]
}
assert (boxes = perform_step_on_boxes(parse_step("ab=5"), boxes)) == %{
0 => [%{label: "rn", focal_length: 1}, %{label: "cm", focal_length: 2}],
3 => [
%{label: "pc", focal_length: 4},
%{label: "ot", focal_length: 9},
%{label: "ab", focal_length: 5}
]
}
assert (boxes = perform_step_on_boxes(parse_step("pc-"), boxes)) == %{
0 => [%{label: "rn", focal_length: 1}, %{label: "cm", focal_length: 2}],
3 => [%{label: "ot", focal_length: 9}, %{label: "ab", focal_length: 5}]
}
assert (boxes = perform_step_on_boxes(parse_step("pc=6"), boxes)) == %{
0 => [%{label: "rn", focal_length: 1}, %{label: "cm", focal_length: 2}],
3 => [
%{label: "ot", focal_length: 9},
%{label: "ab", focal_length: 5},
%{label: "pc", focal_length: 6}
]
}
assert (_boxes = perform_step_on_boxes(parse_step("ot=7"), boxes)) == %{
0 => [%{label: "rn", focal_length: 1}, %{label: "cm", focal_length: 2}],
3 => [
%{label: "ot", focal_length: 7},
%{label: "ab", focal_length: 5},
%{label: "pc", focal_length: 6}
]
}
end
end
describe "focusing_power/2" do
test "main example" do
boxes = %{
0 => [%{label: "rn", focal_length: 1}, %{label: "cm", focal_length: 2}],
3 => [
%{label: "ot", focal_length: 7},
%{label: "ab", focal_length: 5},
%{label: "pc", focal_length: 6}
]
}
assert focusing_power(boxes) == 145
assert focusing_power(boxes) == 1 + 4 + 28 + 40 + 72
end
end
end
ExUnit.run()
Solution - Part 2
PartTwo.solve(puzzle_input)