Advent of Code - Day 3
Mix.install([
{:kino_aoc, "~> 0.1"}
])
Introduction
–> Content
Puzzle
{:ok, puzzle_input} =
KinoAOC.download_puzzle("2023", "3", System.fetch_env!("LB_AOC_SESSION"))
Parser
Code - Parser
defmodule Parser do
def parse(input) do
end
end
Tests - Parser
ExUnit.start(autorun: false)
defmodule ParserTest do
use ExUnit.Case, async: true
import Parser
@input ""
@expected nil
test "parse test" do
actual = parse(@input)
assert actual == @expected
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) do
rows = split_string_into_rows(input_string)
tuples = convert_to_node_tuples(rows)
symbol_tuples = get_symbol_tuples(tuples)
number_tuples = get_number_tuples(tuples)
part_cells =
Enum.filter(number_tuples, fn {x1, y1, _node, _node_id} ->
Enum.any?(symbol_tuples, fn {x2, y2, _node, _node_id} ->
x_diff = x1 - x2
y_diff = y1 - y2
-1 <= x_diff && x_diff <= 1 &&
-1 <= y_diff && y_diff <= 1
end)
end)
Enum.uniq_by(part_cells, fn {x, _y, _, {idx_range, _number}} -> [x, idx_range] end)
|> Enum.map(fn {_x, _y, _, {_idx_range, number}} -> String.to_integer(number) end)
|> Enum.sum()
end
@spec split_string_into_rows(String.t()) :: [[String.t()]]
def split_string_into_rows(input) do
input
|> String.split("\n", trim: true)
|> Enum.map(fn line ->
String.split(line, "", trim: true)
end)
end
@type node_id :: {Range.t(), String.t()}
@spec convert_to_node_tuples([[String.t()]]) :: [{integer(), integer(), String.t(), node_id}]
def convert_to_node_tuples(rows) do
Enum.reduce(Enum.with_index(rows), [], fn {nodes, x}, res ->
line = Enum.join(nodes)
number_indexes =
Regex.scan(~r/\d+/, line, return: :index)
|> Enum.map(fn idx_capture ->
{idx, offset} = hd(idx_capture)
idx..(idx + offset - 1)
end)
numbers = Regex.scan(~r/\d+/, line) |> Enum.map(fn idx_capture -> hd(idx_capture) end)
idx_to_number = Enum.zip(number_indexes, numbers)
tuples_in_row =
Enum.map(Enum.with_index(nodes), fn {node, y} ->
idx_number_pair =
Enum.find(idx_to_number, fn {range, _number} ->
y in range
end)
{x, y, node, idx_number_pair}
end)
res ++ tuples_in_row
end)
end
def get_symbol_tuples(tuples) do
Enum.filter(tuples, fn {_x, _y, node, _node_id} ->
Regex.match?(~r/^(?![0-9]|\.)/, node)
end)
end
def get_number_tuples(tuples) do
Enum.filter(tuples, fn {_x, _y, node, _node_id} ->
is_digit?(node)
end)
end
defp is_digit?(string) do
Regex.match?(~r/\d/, string)
end
end
Tests - Part 1
ExUnit.start(autorun: false)
defmodule PartOneTest do
use ExUnit.Case, async: true
import PartOne
@input """
467..114..
...*......
..35..633.
......#...
617*......
.....+.58.
..592.....
......755.
...$.*....
.664.598..
"""
@expected 4361
test "simple example" do
actual = run(@input)
assert actual == @expected
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) do
rows = PartOne.split_string_into_rows(input_string)
tuples = PartOne.convert_to_node_tuples(rows)
gear_tuples = get_gear_tuples(tuples)
number_tuples = PartOne.get_number_tuples(tuples)
cells =
Enum.reduce(gear_tuples, [], fn {x1, y1, _node, _node_id}, acc ->
close_number_tuples =
Enum.filter(number_tuples, fn {x2, y2, _node, _node_id} ->
close?(x1, y1, x2, y2)
end)
close_number_tuples_uniq =
Enum.uniq_by(close_number_tuples, fn {_x1, _y1, _node, node_id} -> node_id end)
if Enum.count(close_number_tuples_uniq) == 2 do
[close_number_tuples_uniq | acc]
else
acc
end
end)
Enum.map(cells, fn tuple_group ->
Enum.map(tuple_group, fn {_x, _y, _node, {_range, number}} ->
String.to_integer(number)
end)
|> Enum.product()
end)
|> Enum.sum()
end
def get_gear_tuples(tuples) do
Enum.filter(tuples, fn {_x, _y, node, _node_id} ->
Regex.match?(~r/\*/, node)
end)
end
def close?(x1, y1, x2, y2) do
x_diff = x1 - x2
y_diff = y1 - y2
-1 <= x_diff && x_diff <= 1 &&
-1 <= y_diff && y_diff <= 1
end
end
Tests - Part 2
ExUnit.start(autorun: false)
defmodule PartTwoTest do
use ExUnit.Case, async: true
import PartTwo
@input """
467..114..
...*......
..35..633.
......#...
617*......
.....+.58.
..592.....
......755.
...$.*....
.664.598..
"""
@expected 467_835
test "simple example" do
actual = run(@input)
assert actual == @expected
end
end
ExUnit.run()
Solution - Part 2
PartTwo.solve(puzzle_input)