2023 Day 11
Mix.install([
{:req, "~> 0.4.5"},
{:vega_lite, "~> 0.1.8"},
{:kino_vega_lite, "~> 0.1.11"}
])
alias VegaLite, as: Vl
defmodule AOC do
@aoc_session System.fetch_env!("LB_AOC_SESSION")
def day(day, year) when day in 1..25 do
headers = [cookie: "session=#{@aoc_session}"]
req = Req.new(base_url: "https://adventofcode.com/#{year}/day/#{day}", headers: headers)
input = Req.get!(req, url: "/input").body
if input =~ "Please don't repeatedly request this endpoint before it unlocks",
do: {:error, "Day #{day} hasn't been unlocked yet"},
else: {:ok, %{req: req, input: input}}
end
def submit(answer, %{req: req}, part \\ :part_1) when part in [:part_1, :part_2] do
part = if part == :part_2, do: 2, else: 1
if !part_already_completed?(part, req) do
body = Req.post!(req, url: "/answer", form: [level: part, answer: answer]).body
cond do
body =~ "That's the right answer" -> {:correct, answer}
body =~ "your answer is too low" -> {:too_low, answer}
body =~ "your answer is too high" -> {:too_high, answer}
:else -> {:incorrect, answer}
end
else
{:already_completed_part, answer}
end
end
defp part_already_completed?(part, req) do
body = Req.get!(req).body
cond do
body =~ "Both parts of this puzzle are complete!" -> true
body =~ "The first half of this puzzle is complete!" -> part == 1
:else -> false
end
end
def nums(str) do
Regex.scan(~r/\d+/, str) |> Enum.map(fn [x] -> String.to_integer(x) end)
end
end
{:ok, day} = AOC.day(11, 2023)
Part 1
test_input = """
...#......
.......#..
#.........
..........
......#...
.#........
.........#
..........
.......#..
#...#.....
"""
defmodule P1 do
def solve(input) do
input
|> String.split("\n", trim: true)
|> to_expanded_grid()
|> find_galaxies()
|> to_pairs()
|> Enum.map(&manhattan_distance/1)
|> Enum.sum()
end
def to_expanded_grid(rows) do
rows =
Enum.flat_map(rows, fn row ->
if String.match?(row, ~r/^\.*$/), do: [row, row], else: [row]
end)
cols =
0..(String.length(hd(rows)) - 1)
|> Enum.flat_map(fn x ->
col = rows |> Enum.map(&String.at(&1, x)) |> Enum.join()
if String.match?(col, ~r/^\.*$/), do: [col, col], else: [col]
end)
for {col, x} <- Enum.with_index(cols),
{v, y} <- col |> String.graphemes() |> Enum.with_index(),
into: %{},
do: {{x, y}, v}
end
def find_galaxies(grid) do
grid
|> Enum.filter(fn {_xy, v} -> v == "#" end)
|> Enum.map(&elem(&1, 0))
end
def to_pairs(galaxies), do: combinations(galaxies, 2)
def manhattan_distance([{x1, y1}, {x2, y2}]), do: abs(x2 - x1) + abs(y2 - y1)
def combinations(_, 0), do: [[]]
def combinations([], _), do: []
def combinations([h | t], m),
do: for(l <- combinations(t, m - 1), do: [h | l]) ++ combinations(t, m)
end
# test_input |> P1.solve()
day.input |> P1.solve()
Part 2
defmodule P2 do
def solve(input, multiplier \\ 1_000_000) do
rows = input |> String.split("\n", trim: true)
galaxies = find_galaxies(rows)
empty_rows = empty_rows(rows)
empty_cols = empty_cols(rows)
galaxies
|> P1.to_pairs()
|> Enum.map(&distance(&1, empty_rows, empty_cols, multiplier))
|> Enum.sum()
end
def distance([{x1, y1}, {x2, y2}], empty_rows, empty_cols, multiplier) do
dist = abs(x2 - x1) + abs(y2 - y1)
r = Enum.count(empty_rows, fn r -> r in y1..y2 end)
c = Enum.count(empty_cols, fn c -> c in x1..x2 end)
dist + r * multiplier - r + c * multiplier - c
end
def find_galaxies(rows) do
for {row, y} <- Enum.with_index(rows),
{v, x} <- row |> String.graphemes() |> Enum.with_index(),
v == "#",
do: {x, y}
end
def empty_rows(rows) do
rows
|> Enum.with_index()
|> Enum.reduce([], fn {row, i}, acc ->
if String.match?(row, ~r/^\.*$/), do: [i | acc], else: acc
end)
|> Enum.reverse()
end
def empty_cols(rows) do
(String.length(hd(rows)) - 1)..0
|> Enum.reduce([], fn x, acc ->
col = rows |> Enum.map(&String.at(&1, x)) |> Enum.join()
if String.match?(col, ~r/^\.*$/), do: [x | acc], else: acc
end)
end
end
# test_input |> P2.solve(2)
day.input |> P2.solve()