Day 03
Mix.install([
{:kino, "~> 0.7.0"}
])
IEx.Helpers.c("/Users/johnb/dev/2023adventOfCode/advent_of_code.ex")
alias AdventOfCode, as: AOC
alias Kino.Input
# Note: when making the next template, something like this works well:
# `cat day03.livemd | sed 's/03/04/' > day04.livemd`
#
Installation and Data
input_p1example = Kino.Input.textarea("Example Data")
input_p1puzzleInput = Kino.Input.textarea("Puzzle Input")
input_source_select =
Kino.Input.select("Source", [{:example, "example"}, {:puzzle_input, "puzzle input"}])
p1data = fn ->
(Kino.Input.read(input_source_select) == :example &&
Kino.Input.read(input_p1example)) ||
Kino.Input.read(input_p1puzzleInput)
end
Part 1
defmodule Day03 do
@island %{cells: [], part_number: 0, perimeter: []}
@non_symbols [".", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", nil]
@gear "*"
def no_adjacent_symbols?(island, grid) do
island.perimeter
|> Enum.all?(fn cell -> grid[cell] in @non_symbols end)
end
def find_numeric_islands(grid) do
width = grid.grid_width
all =
0..grid.last_cell
|> Enum.reduce(
%{islands: [], in_progress: @island},
fn cell,
%{
islands: islands,
in_progress:
%{cells: cells, part_number: part_number, perimeter: perimeter} = in_progress
} = acc ->
case {grid[cell] =~ ~r/\d/, part_number} do
# assume no parts start with a 0
{true, 0} ->
%{
acc
| in_progress: %{
cells: [cell | cells],
part_number: String.to_integer(grid[cell]),
perimeter: [
cell - 1 - width,
cell - width,
cell - 1,
cell - 1 + width,
cell + width
]
}
}
{true, n} ->
%{
acc
| in_progress: %{
cells: [cell | cells],
part_number: 10 * n + String.to_integer(grid[cell]),
perimeter: [cell - width, cell + width | perimeter]
}
}
{false, 0} ->
acc
{false, _n} ->
%{
islands: [
%{in_progress | perimeter: [cell - width, cell, cell + width | perimeter]}
| islands
],
in_progress: @island
}
end
end
)
all[:islands]
end
def solve(text) do
grid = AOC.as_grid(text)
find_numeric_islands(grid)
|> Enum.reject(fn island -> no_adjacent_symbols?(island, grid) end)
|> Enum.map(fn %{part_number: part_number} -> part_number end)
|> Enum.sum()
end
def find_gear_ratios(islands, grid) do
0..grid.last_cell
|> Enum.reduce([], fn cell, acc ->
if grid[cell] == @gear do
neighbors = AOC.neighbors8(grid, cell) |> MapSet.new()
overlaps =
Enum.filter(islands, fn island ->
MapSet.intersection(neighbors, MapSet.new(island.cells))
|> Enum.any?()
end)
if Enum.count(overlaps) == 2 do
[List.first(overlaps).part_number * List.last(overlaps).part_number | acc]
else
acc
end
else
acc
end
end)
|> Enum.uniq()
end
def solve2(text) do
grid = AOC.as_grid(text)
islands = find_numeric_islands(grid)
find_gear_ratios(islands, grid)
|> Enum.sum()
end
end
p1data.()
|> Day03.solve()
|> IO.inspect(label: "\n*** Part 1 solution (example: 4361)")
# 533784
p1data.()
|> Day03.solve2()
|> IO.inspect(label: "\n*** Part 2 solution (example: 467835)")
# 78826761