2024 Day 12
This commit is contained in:
parent
26731083a3
commit
283fa8b34c
1 changed files with 176 additions and 0 deletions
176
2024/day12.exs
Executable file
176
2024/day12.exs
Executable file
|
@ -0,0 +1,176 @@
|
|||
#!/usr/bin/env elixir
|
||||
defmodule Day12 do
|
||||
defmodule Region do
|
||||
defstruct label: nil, plots: MapSet.new(), perimeter: 0
|
||||
end
|
||||
|
||||
def part1({grid, size_x, size_y}) do
|
||||
find_regions(grid, size_x, size_y)
|
||||
|> Enum.map(fn region -> MapSet.size(region.plots) * region.perimeter end)
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
def find_regions(grid, size_x, size_y) do
|
||||
for x <- 0..(size_x - 1), y <- 0..(size_y - 1), reduce: {[], MapSet.new()} do
|
||||
{regions, seen} ->
|
||||
case find_region(x, y, grid, seen) do
|
||||
{:already_found, _seen} -> {regions, seen}
|
||||
{region, seen} -> {[region | regions], seen}
|
||||
end
|
||||
end
|
||||
|> elem(0)
|
||||
end
|
||||
|
||||
def find_region(x, y, grid, seen, region \\ nil) do
|
||||
if MapSet.member?(seen, {x, y}) do
|
||||
{region || :already_found, seen}
|
||||
else
|
||||
region = region || %Region{label: Map.fetch!(grid, {x, y})}
|
||||
|
||||
seen = MapSet.put(seen, {x, y})
|
||||
region_neighbours = get_region_neighbours(x, y, region.label, grid)
|
||||
|
||||
region = %Region{
|
||||
region
|
||||
| plots: MapSet.put(region.plots, {x, y}),
|
||||
perimeter: region.perimeter + (4 - length(region_neighbours))
|
||||
}
|
||||
|
||||
Enum.reduce(region_neighbours, {region, seen}, fn {x, y}, {region, seen} ->
|
||||
find_region(x, y, grid, seen, region)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
def get_region_neighbours(x, y, label, grid) do
|
||||
grid
|
||||
|> Map.take([{x - 1, y}, {x + 1, y}, {x, y - 1}, {x, y + 1}])
|
||||
|> Map.filter(&match?({_coord, ^label}, &1))
|
||||
|> Map.keys()
|
||||
end
|
||||
|
||||
def part2({grid, size_x, size_y}) do
|
||||
find_regions(grid, size_x, size_y)
|
||||
|> Enum.map(fn region -> MapSet.size(region.plots) * count_sides(region) end)
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
def count_sides(region) do
|
||||
{min_x, _y} = Enum.min_by(region.plots, fn {x, _y} -> x end)
|
||||
{_x, min_y} = Enum.min_by(region.plots, fn {_x, y} -> y end)
|
||||
{max_x, _y} = Enum.max_by(region.plots, fn {x, _y} -> x end)
|
||||
{_x, max_y} = Enum.max_by(region.plots, fn {_x, y} -> y end)
|
||||
|
||||
left =
|
||||
Enum.reduce(min_x..max_x, 0, fn x, sides ->
|
||||
Enum.reduce(min_y..max_y, {sides, false}, fn y, {sides, sequence?} ->
|
||||
plot? = MapSet.member?(region.plots, {x, y})
|
||||
open? = MapSet.member?(region.plots, {x - 1, y})
|
||||
|
||||
cond do
|
||||
plot? and not open? and not sequence? -> {sides + 1, true}
|
||||
plot? and not open? and sequence? -> {sides, true}
|
||||
true -> {sides, false}
|
||||
end
|
||||
end)
|
||||
|> elem(0)
|
||||
end)
|
||||
|
||||
right =
|
||||
Enum.reduce(min_x..max_x, 0, fn x, sides ->
|
||||
Enum.reduce(min_y..max_y, {sides, false}, fn y, {sides, sequence?} ->
|
||||
plot? = MapSet.member?(region.plots, {x, y})
|
||||
open? = MapSet.member?(region.plots, {x + 1, y})
|
||||
|
||||
cond do
|
||||
plot? and not open? and not sequence? -> {sides + 1, true}
|
||||
plot? and not open? and sequence? -> {sides, true}
|
||||
true -> {sides, false}
|
||||
end
|
||||
end)
|
||||
|> elem(0)
|
||||
end)
|
||||
|
||||
top =
|
||||
Enum.reduce(min_y..max_y, 0, fn y, sides ->
|
||||
Enum.reduce(min_x..max_x, {sides, false}, fn x, {sides, sequence?} ->
|
||||
plot? = MapSet.member?(region.plots, {x, y})
|
||||
open? = MapSet.member?(region.plots, {x, y - 1})
|
||||
|
||||
cond do
|
||||
plot? and not open? and not sequence? -> {sides + 1, true}
|
||||
plot? and not open? and sequence? -> {sides, true}
|
||||
true -> {sides, false}
|
||||
end
|
||||
end)
|
||||
|> elem(0)
|
||||
end)
|
||||
|
||||
bottom =
|
||||
Enum.reduce(min_y..max_y, 0, fn y, sides ->
|
||||
Enum.reduce(min_x..max_x, {sides, false}, fn x, {sides, sequence?} ->
|
||||
plot? = MapSet.member?(region.plots, {x, y})
|
||||
open? = MapSet.member?(region.plots, {x, y + 1})
|
||||
|
||||
cond do
|
||||
plot? and not open? and not sequence? -> {sides + 1, true}
|
||||
plot? and not open? and sequence? -> {sides, true}
|
||||
true -> {sides, false}
|
||||
end
|
||||
end)
|
||||
|> elem(0)
|
||||
end)
|
||||
|
||||
left + right + top + bottom
|
||||
end
|
||||
|
||||
def input do
|
||||
with [input_filename] <- System.argv(),
|
||||
{:ok, input} <- File.read(input_filename) do
|
||||
{grid, _, x, y} =
|
||||
for <<char::binary-1 <- input>>, reduce: {%{}, 0, 0, 0} do
|
||||
{grid, x, max_x, y} ->
|
||||
case char do
|
||||
"\n" -> {grid, 0, x, y + 1}
|
||||
char -> {Map.put(grid, {x, y}, char), x + 1, max_x, y}
|
||||
end
|
||||
end
|
||||
|
||||
{grid, x, y}
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# HERE BE BOILERPLATE #
|
||||
#######################
|
||||
|
||||
def run do
|
||||
case input() do
|
||||
:error -> print_usage()
|
||||
input -> run_parts_with_timer(input)
|
||||
end
|
||||
end
|
||||
|
||||
defp run_parts_with_timer(input) do
|
||||
run_with_timer(1, fn -> part1(input) end)
|
||||
run_with_timer(2, fn -> part2(input) end)
|
||||
end
|
||||
|
||||
defp run_with_timer(part, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
IO.puts("Part #{part} (completed in #{format_time(time)}):\n")
|
||||
IO.puts("#{inspect(result)}\n")
|
||||
end
|
||||
|
||||
defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs"
|
||||
defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms"
|
||||
defp format_time(μsec), do: "#{μsec / 1_000_000}s"
|
||||
|
||||
defp print_usage do
|
||||
IO.puts("Usage: elixir dayDay12.exs input_filename")
|
||||
end
|
||||
end
|
||||
|
||||
Day12.run()
|
Loading…
Reference in a new issue