add 2024 day 12
This commit is contained in:
parent
279f4c1f1a
commit
7aba3bab17
1 changed files with 271 additions and 0 deletions
271
elixir/livebook/2024/day12.livemd
Normal file
271
elixir/livebook/2024/day12.livemd
Normal file
|
@ -0,0 +1,271 @@
|
|||
# AOC2024 Day 12
|
||||
|
||||
## Section
|
||||
|
||||
```elixir
|
||||
defmodule Grid2D do
|
||||
defstruct width: nil, height: nil
|
||||
|
||||
@directions %{
|
||||
straight: [
|
||||
{1, 0},
|
||||
{-1, 0},
|
||||
{0, 1},
|
||||
{0, -1}
|
||||
],
|
||||
diagonal: [
|
||||
{1, 1},
|
||||
{1, -1},
|
||||
{-1, 1},
|
||||
{-1, -1}
|
||||
]
|
||||
}
|
||||
|
||||
def new(width, height) do
|
||||
%Grid2D{width: width, height: height}
|
||||
end
|
||||
|
||||
@doc """
|
||||
## Examples
|
||||
iex> Grid2D.index_to_coord(Grid2D.new(5,5), 0)
|
||||
{0,0}
|
||||
|
||||
iex> Grid2D.index_to_coord(Grid2D.new(5,5), 5)
|
||||
{0,1}
|
||||
|
||||
iex> Grid2D.index_to_coord(Grid2D.new(5,5), 6)
|
||||
{1,1}
|
||||
|
||||
iex> Grid2D.index_to_coord(Grid2D.new(5,5), 3)
|
||||
{3,0}
|
||||
|
||||
iex> Grid2D.index_to_coord(Grid2D.new(5,5), 24)
|
||||
{4, 4}
|
||||
"""
|
||||
def index_to_coord(%Grid2D{width: w}, index) do
|
||||
{rem(index, w), floor(index / w)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
## Examples
|
||||
iex> Grid2D.coord_to_index( Grid2D.new(5,5), {0,0})
|
||||
0
|
||||
|
||||
iex> Grid2D.coord_to_index( Grid2D.new(5,5), {4,4})
|
||||
24
|
||||
|
||||
iex> Grid2D.coord_to_index( Grid2D.new(5,5), {2,2})
|
||||
12
|
||||
"""
|
||||
def coord_to_index(%Grid2D{width: w}, {x, y}) do
|
||||
y * w + x
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
## Examples
|
||||
iex> Grid2D.neighbors({0, 0}, Grid2D.new(5,5)) |> MapSet.new()
|
||||
MapSet.new([{0, 1}, {1, 1}, {1,0}])
|
||||
|
||||
iex> Grid2D.neighbors({2, 2}, Grid2D.new(5,5)) |> MapSet.new()
|
||||
[{1, 1}, {1, 2}, {1, 3}, {2, 1}, {2, 3}, {3, 1}, {3, 2}, {3, 3}] |> MapSet.new()
|
||||
|
||||
iex> Grid2D.neighbors({4, 4}, Grid2D.new(5,5)) |> MapSet.new
|
||||
[{3, 3}, {3, 4}, {4, 3}] |> MapSet.new
|
||||
"""
|
||||
def neighbors(p, g, allowed_directions \\ :all) do
|
||||
directions(allowed_directions)
|
||||
|> Stream.map(&add(p, &1))
|
||||
|> Stream.filter(&is_in_grid?(&1, g))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Distance from two points in a grid
|
||||
|
||||
iex> Grid2D.distance(Grid2D.new(5,5), {0,0}, {0,1}, :radial)
|
||||
1
|
||||
|
||||
iex> Grid2D.distance(Grid2D.new(5,5), {0,0}, {1,1}, :radial)
|
||||
1
|
||||
|
||||
iex> Grid2D.distance(Grid2D.new(5,5), {0,0}, {1,2}, :radial)
|
||||
2
|
||||
"""
|
||||
def distance(grid, p1, p2, type)
|
||||
def distance(_, {x, y}, {a, b}, :radial), do: max(abs(x - a), abs(y - b))
|
||||
|
||||
@doc """
|
||||
Add two points together pairwise
|
||||
|
||||
iex> Grid2D.add({0,0}, {0,1})
|
||||
{0,1}
|
||||
|
||||
iex> Grid2D.add({2,3}, {3,2})
|
||||
{5,5}
|
||||
"""
|
||||
def add({x, y}, {j, k}), do: {x + j, y + k}
|
||||
|
||||
@doc """
|
||||
Test if a point is in a grid
|
||||
|
||||
iex> Grid2D.is_in_grid?({1, 2}, %Grid2D{width: 3, height: 4})
|
||||
true
|
||||
|
||||
iex> Grid2D.is_in_grid?({2, 2}, %Grid2D{width: 2, height: 4})
|
||||
false
|
||||
"""
|
||||
def is_in_grid?({x, y}, %Grid2D{width: w, height: h}), do: x < w and y < h and x >= 0 and y >= 0
|
||||
|
||||
@spec directions(allowed :: :all | :straight | :diagonal) :: any()
|
||||
def directions(allowed \\ :all)
|
||||
def directions(:all), do: directions(:straight) ++ directions(:diagonal)
|
||||
def directions(:straight), do: @directions |> Map.get(:straight)
|
||||
def directions(:diagonal), do: @directions |> Map.get(:diagonal)
|
||||
|
||||
def mul({x, y}, n), do: {n*x, n*y}
|
||||
end
|
||||
|
||||
```
|
||||
|
||||
```elixir
|
||||
defmodule EnumUtil do
|
||||
def pick_n(enum, n)
|
||||
def pick_n([], _), do: []
|
||||
def pick_n([a | rest], n) do
|
||||
pick_n(rest, n, n, []) ++ pick_n(rest, n, n - 1, [a])
|
||||
end
|
||||
defp pick_n([], n, _, acc) do
|
||||
if acc |> Enum.count == n do
|
||||
[acc]
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
defp pick_n(_, _, 0, acc), do: [acc]
|
||||
defp pick_n([a | rest], n, r, acc) do
|
||||
pick_n(rest, n, r, acc) ++ pick_n(rest, n, r - 1, [a | acc])
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
```elixir
|
||||
input = """
|
||||
"""
|
||||
```
|
||||
|
||||
```elixir
|
||||
lines = input |> String.trim() |> String.split("\n")
|
||||
height = lines |> Enum.count
|
||||
width = lines |> Enum.at(0) |> String.length()
|
||||
tiles = lines |> Stream.with_index() |> Stream.flat_map(fn {line, i} -> line |> String.graphemes() |> Stream.with_index() |> Enum.map(fn {c, j} -> {{j, i}, c} end) end) |> Enum.into(%{})
|
||||
grid = Grid2D.new(width, height)
|
||||
```
|
||||
|
||||
```elixir
|
||||
defmodule Crawl do
|
||||
def crawl([], _, _, acc), do: acc
|
||||
def crawl(points, tiles, grid, acc) do
|
||||
n = points
|
||||
|> Enum.flat_map(fn p -> Grid2D.neighbors(p, grid, :straight)
|
||||
|> Enum.reject(&(MapSet.member?(acc, &1) or Map.get(tiles, &1) != Map.get(tiles, p))) end)
|
||||
|> Enum.uniq()
|
||||
|
||||
# IO.puts("#{inspect(n)}")
|
||||
crawl(n, tiles, grid, points |> Enum.reduce(acc, fn x, s -> MapSet.put(s, x) end))
|
||||
end
|
||||
|
||||
def crawl(tiles, grid) do
|
||||
for {pt, _} <- tiles, reduce: {MapSet.new([]),[]} do
|
||||
{seen, list} ->
|
||||
if !MapSet.member?(seen, pt) do
|
||||
visited = Crawl.crawl([pt], tiles, grid, MapSet.new([pt]))
|
||||
{seen |> MapSet.union(visited), [visited | list]}
|
||||
else
|
||||
{seen, list}
|
||||
end
|
||||
end |> elem(1)
|
||||
end
|
||||
|
||||
def pricep1(regions) do
|
||||
for region <- regions, reduce: 0 do
|
||||
acc ->
|
||||
area = region |> MapSet.size()
|
||||
perimeter = 4*area - (region |> MapSet.to_list() |> EnumUtil.pick_n(2) |> Stream.map(
|
||||
fn [{a,b},{x,y}] ->
|
||||
if (a == x and abs(b-y) == 1) or (b == y and abs(a-x) == 1) do
|
||||
2
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
) |> Enum.sum())
|
||||
acc + area*perimeter
|
||||
end
|
||||
end
|
||||
|
||||
def rotate90({a, b}), do: {-b, a}
|
||||
def pricep2(regions, tiles, grid) do
|
||||
for region <- regions, reduce: 0 do
|
||||
acc ->
|
||||
sides = for direction <- Grid2D.directions(:straight) do
|
||||
|
||||
for tile <- region,
|
||||
neighbor = Grid2D.add(tile, direction),
|
||||
Map.get(tiles, neighbor) != Map.get(tiles, tile),
|
||||
reduce: {MapSet.new, 0} do
|
||||
{used, total} ->
|
||||
if !MapSet.member?(used, tile) do
|
||||
scan_dir = rotate90(direction)
|
||||
line = Stream.iterate(1, &(&1 + 1))
|
||||
|> Stream.map(fn n ->
|
||||
if rem(n, 2) == 0 do
|
||||
{:up, div(n,2)}
|
||||
else
|
||||
{:down, div(n+1, 2)*-1}
|
||||
end
|
||||
end)
|
||||
|> Stream.map(&({elem(&1, 0), Grid2D.mul(scan_dir, elem(&1, 1))}))
|
||||
|> Enum.reduce_while(
|
||||
%{line: MapSet.new, up: true, down: true},
|
||||
fn {dir, next}, %{line: line} = acc ->
|
||||
next = Grid2D.add(next, tile)
|
||||
|
||||
is_same? = Map.get(tiles, next) == Map.get(tiles, tile)
|
||||
next_external? = Map.get(tiles, Grid2D.add(next, direction)) != Map.get(tiles, next)
|
||||
acc = if acc[dir] and Grid2D.is_in_grid?(next, grid) and is_same? and next_external? do
|
||||
%{acc | line: MapSet.put(line, next)}
|
||||
else
|
||||
acc |> Map.put(dir, false)
|
||||
end
|
||||
|
||||
{(if acc[:up] or acc[:down], do: :cont, else: :halt), acc}
|
||||
end
|
||||
) |> Map.get(:line)
|
||||
|
||||
{used |> MapSet.union(line), total + 1}
|
||||
else
|
||||
{used, total}
|
||||
end
|
||||
|
||||
end |> elem(1)
|
||||
end |> Enum.sum()
|
||||
|
||||
area = region |> MapSet.size()
|
||||
|
||||
acc + area*sides
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
```elixir
|
||||
regions = Crawl.crawl(tiles, grid)
|
||||
```
|
||||
|
||||
```elixir
|
||||
Crawl.pricep1(regions)
|
||||
```
|
||||
|
||||
```elixir
|
||||
Crawl.pricep2(regions, tiles, grid)
|
||||
```
|
Loading…
Add table
Reference in a new issue