2020 Day 20 Part 1
This commit is contained in:
parent
a38c025ead
commit
19821cc5cc
2 changed files with 246 additions and 0 deletions
245
2020/day20/day20.ex
Normal file
245
2020/day20/day20.ex
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
defmodule Day20 do
|
||||
defmodule Tile do
|
||||
@enforce_keys [:id, :rows]
|
||||
defstruct [:id, :rows]
|
||||
|
||||
def row(tile, idx) do
|
||||
Enum.at(tile.rows, idx)
|
||||
end
|
||||
|
||||
def col(tile, idx) do
|
||||
Enum.map(tile.rows, fn row ->
|
||||
String.at(row, idx)
|
||||
end)
|
||||
|> Enum.join()
|
||||
end
|
||||
|
||||
def size(tile) do
|
||||
String.length(row(tile, 0))
|
||||
end
|
||||
|
||||
def side_hash(text) do
|
||||
min(binary(text), binary(String.reverse(text)))
|
||||
end
|
||||
|
||||
defp binary(text) do
|
||||
String.codepoints(text)
|
||||
|> Enum.reverse()
|
||||
|> Enum.with_index()
|
||||
|> Enum.map(fn {char, idx} ->
|
||||
case char do
|
||||
"#" -> Bitwise.bsl(1, idx)
|
||||
"." -> 0
|
||||
end
|
||||
end)
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
defp top(t), do: row(t, 0)
|
||||
defp right(t), do: col(t, 0)
|
||||
defp bottom(t), do: row(t, -1)
|
||||
defp left(t), do: col(t, -1)
|
||||
|
||||
def sides(tile) do
|
||||
[
|
||||
top(tile),
|
||||
right(tile),
|
||||
bottom(tile),
|
||||
left(tile)
|
||||
]
|
||||
end
|
||||
|
||||
def matching_side?(t1, t2) do
|
||||
h1 = sides(t1) |> Enum.map(&side_hash/1)
|
||||
h2 = sides(t2) |> Enum.map(&side_hash/1)
|
||||
uniq = MapSet.new(h1 ++ h2) |> MapSet.size()
|
||||
uniq < Enum.count(h1) + Enum.count(h2)
|
||||
end
|
||||
|
||||
def transformations do
|
||||
[
|
||||
:f0,
|
||||
:f90,
|
||||
:f180,
|
||||
:f270,
|
||||
:b0,
|
||||
:b90,
|
||||
:b180,
|
||||
:b270
|
||||
]
|
||||
end
|
||||
|
||||
defp flip(tile) do
|
||||
%Tile{id: tile.id, rows: Enum.reverse(tile.rows)}
|
||||
end
|
||||
|
||||
defp rotate(tile) do
|
||||
l = size(tile)
|
||||
new_rows = Enum.map(1..l, &col(tile, l - &1))
|
||||
%Tile{id: tile.id, rows: new_rows}
|
||||
end
|
||||
|
||||
def transform(tile, op) do
|
||||
case op do
|
||||
:f0 -> tile
|
||||
:f90 -> rotate(tile)
|
||||
:f180 -> rotate(rotate(tile))
|
||||
:f270 -> rotate(rotate(rotate(tile)))
|
||||
:b0 -> flip(tile)
|
||||
:b90 -> flip(rotate(tile))
|
||||
:b180 -> flip(rotate(rotate(tile)))
|
||||
:b270 -> flip(rotate(rotate(rotate(tile))))
|
||||
end
|
||||
end
|
||||
|
||||
def fits?(%Tile{}, nil), do: true
|
||||
def fits?(t1, :below, t2), do: top(t1) == bottom(t2)
|
||||
def fits?(t1, :above, t2), do: bottom(t1) == top(t2)
|
||||
def fits?(t1, :leftof, t2), do: right(t1) == left(t2)
|
||||
def fits?(t1, :rightof, t2), do: left(t1) == right(t2)
|
||||
end
|
||||
|
||||
defmodule Grid do
|
||||
@enforce_keys [:size, :map]
|
||||
defstruct [:size, :map]
|
||||
|
||||
def full?(grid) do
|
||||
for r <- 0..(grid.size - 1) do
|
||||
for c <- 0..(grid.size - 1) do
|
||||
{r, c}
|
||||
end
|
||||
end
|
||||
|> Enum.all?(&Map.get(grid.map, &1))
|
||||
end
|
||||
|
||||
def empty(size), do: %Grid{size: size, map: %{}}
|
||||
|
||||
def corners(grid) do
|
||||
Enum.map(
|
||||
[
|
||||
{0, 0},
|
||||
{0, grid.size - 1},
|
||||
{grid.size - 1, 0},
|
||||
{grid.size - 1, grid.size - 1}
|
||||
],
|
||||
&Map.get(grid.map, &1)
|
||||
)
|
||||
end
|
||||
|
||||
def fill_candidates(grid) do
|
||||
for r <- 0..grid.size(-1),
|
||||
c <- 0..(grid.size - 1) do
|
||||
case Map.get(grid.map, {r, c}) do
|
||||
nil ->
|
||||
[]
|
||||
|
||||
_ ->
|
||||
[
|
||||
[
|
||||
{r + 1, c},
|
||||
{r - 1, c},
|
||||
{r, c + 1},
|
||||
{r, c - 1}
|
||||
]
|
||||
]
|
||||
end
|
||||
end
|
||||
|> Enum.concat()
|
||||
|> Enum.uniq()
|
||||
|> Enum.filter(&can_fill?(grid, &1))
|
||||
end
|
||||
|
||||
defp can_fill?(grid, cell) do
|
||||
in_bounds?(grid, cell) and Map.get(grid.map, cell) == nil
|
||||
end
|
||||
|
||||
defp in_bounds?(%Grid{size: size}, {r, c}) do
|
||||
0 <= r and r < size and 0 <= c and c < size
|
||||
end
|
||||
|
||||
def tile_ids(grid) do
|
||||
Enum.map(grid.map, fn {_, {%Tile{id: id}, _op}} -> id end)
|
||||
end
|
||||
|
||||
def can_place?(grid, tile, op, {r, c}) do
|
||||
north = Map.get(grid.map, {r - 1, c})
|
||||
south = Map.get(grid.map, {r + 1, c})
|
||||
east = Map.get(grid.map, {r, c + 1})
|
||||
west = Map.get(grid.map, {r, c - 1})
|
||||
|
||||
Enum.all?([
|
||||
fits?({tile, op}, :below, north),
|
||||
fits?({tile, op}, :above, south),
|
||||
fits?({tile, op}, :leftof, east),
|
||||
fits?({tile, op}, :rightof, west)
|
||||
])
|
||||
end
|
||||
|
||||
defp fits?({_tile, _t_op}, _rel, nil), do: true
|
||||
|
||||
defp fits?({tile, t_op}, rel, {neighbor, n_op}) do
|
||||
t = Tile.transform(tile, t_op)
|
||||
n = Tile.transform(neighbor, n_op)
|
||||
Tile.fits?(t, rel, n)
|
||||
end
|
||||
|
||||
def place(grid, tile, op, rc) do
|
||||
%{grid | map: Map.put(grid.map, rc, {tile, op})}
|
||||
end
|
||||
|
||||
def show(grid, tile_size) do
|
||||
for grid_r <- 0..(grid.size - 1) do
|
||||
for tile_r <- 0..(tile_size - 1) do
|
||||
for grid_c <- 0..(grid.size - 1) do
|
||||
case Map.get(grid.map, {grid_r, grid_c}) do
|
||||
{tile, op} -> Tile.transform(tile, op) |> Tile.row(tile_r)
|
||||
nil -> Enum.join(List.duplicate("o", tile_size))
|
||||
end
|
||||
end
|
||||
|> Enum.join(" ")
|
||||
end
|
||||
|> Enum.join("\n")
|
||||
end
|
||||
|> Enum.join("\n\n")
|
||||
|> IO.puts()
|
||||
end
|
||||
end
|
||||
|
||||
defp read_input do
|
||||
Path.expand('input', Path.dirname(__ENV__.file))
|
||||
|> File.read!()
|
||||
end
|
||||
|
||||
defp parse_tiles(text) do
|
||||
text
|
||||
|> String.split("\n\n", trim: true)
|
||||
|> Enum.map(&String.split(&1, "\n", trim: true))
|
||||
|> Enum.map(fn [title | lines] ->
|
||||
# Tile 1234:
|
||||
id = String.slice(title, 5..-2)
|
||||
%Tile{id: String.to_integer(id), rows: lines}
|
||||
end)
|
||||
end
|
||||
|
||||
def part1 do
|
||||
read_input()
|
||||
|> parse_tiles()
|
||||
|> neighbor_counts()
|
||||
|> Enum.filter(fn {_id, count} -> count == 2 end)
|
||||
|> Enum.reduce(1, fn {id, _}, prod -> id * prod end)
|
||||
end
|
||||
|
||||
defp neighbor_counts(tileset) do
|
||||
for tile <- tileset do
|
||||
count =
|
||||
Enum.count(tileset -- [tile], fn neighbor ->
|
||||
Tile.matching_side?(tile, neighbor)
|
||||
end)
|
||||
|
||||
{tile.id, count}
|
||||
end
|
||||
|> Enum.into(%{})
|
||||
end
|
||||
end
|
||||
|
||||
Day20.part1() |> IO.inspect()
|
||||
Loading…
Reference in a new issue