From e9dda17235ab5e2689f5c615a17a8cbe53a5aa4e Mon Sep 17 00:00:00 2001 From: Caleb Webber Date: Mon, 9 Dec 2024 03:18:59 -0500 Subject: [PATCH] aoc 2024 day 8 and 9 --- elixir/livebook/2024/day8.livemd | 231 +++++++++++++++++++++++++++++++ elixir/livebook/2024/day9.livemd | 154 +++++++++++++++++++++ 2 files changed, 385 insertions(+) create mode 100644 elixir/livebook/2024/day8.livemd create mode 100644 elixir/livebook/2024/day9.livemd diff --git a/elixir/livebook/2024/day8.livemd b/elixir/livebook/2024/day8.livemd new file mode 100644 index 0000000..37fbd70 --- /dev/null +++ b/elixir/livebook/2024/day8.livemd @@ -0,0 +1,231 @@ +# AOC 2024 Day 8 + +## 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} + def sub({x, y}, {j, k}), do: {x - j, y - k} + def mul({x, y}, n), do: {n*x, n*y} + + @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 + + def span(grid, origin, vector) do + Stream.iterate(1, &(&1+1)) + |> Enum.reduce_while( + {[],false,false}, + fn i, {acc,pos,neg} -> + sign = if rem(i, 2) == 1, do: 1, else: -1 + dist = div(i, 2) + + point = Grid2D.add(origin, Grid2D.mul(vector, sign*dist)) + next = if Grid2D.is_in_grid?(point, grid) do + {[point | acc], pos, neg} + else + pos = if sign == 1, do: true, else: pos + neg = if sign == -1, do: true, else: neg + {acc, pos, neg} + end + case next do + {_, false, _} -> {:cont, next} + {_, _, false} -> {:cont, next} + {_, true, true} -> {:halt, next} + end + end + ) |> elem(0) + end + + @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) +end + +``` + +```elixir +input = """ +""" |> String.trim() +``` + +```elixir +lines = input |> String.split("\n") +height = lines |> Enum.count() +width = lines |> Enum.at(0) |> String.trim() |> String.length() +grid = Grid2D.new(width, height) +``` + +```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 = input |> String.replace("\n", "") +``` + +```elixir +defmodule Resonence do + def find_antinodes(input, grid, antenna) do + input |> String.to_charlist() + |> Stream.with_index() + |> Stream.filter(&(elem(&1, 0) == antenna)) + |> Stream.map(&elem(&1, 1)) + |> Enum.map(&Grid2D.index_to_coord(grid, &1)) + |> EnumUtil.pick_n(2) + |> Stream.map(fn + [a, b] -> { + a, + b, + a |> Grid2D.sub(b) + } + end) + |> Stream.flat_map(fn {a, b, dist} -> + Grid2D.span(grid, a, dist) + # uncomment this filter for pt 2 + |> Enum.filter(fn point -> + x = Grid2D.sub(point, a) + y = Grid2D.sub(point, b) + Grid2D.mul(x, 2) == y or Grid2D.mul(y, 2) == x + end) + end) + end + + def find_antennae(input) do + input |> String.to_charlist() |> Enum.reject(&(&1 == ?.)) |> Enum.uniq() + end +end + +``` + +```elixir +Resonence.find_antennae(input) +|> Stream.flat_map(&Resonence.find_antinodes(input, grid, &1)) +|> MapSet.new() +|> MapSet.size() +``` diff --git a/elixir/livebook/2024/day9.livemd b/elixir/livebook/2024/day9.livemd new file mode 100644 index 0000000..25fd932 --- /dev/null +++ b/elixir/livebook/2024/day9.livemd @@ -0,0 +1,154 @@ +# AOC 2024 Day 9 + +## Section + +```elixir +input = "" +``` + +```elixir +defmodule Fragmenter do + def parse_disk_map(map) do + map + |> String.to_charlist() + |> Stream.map(&(&1 - 48)) + |> Stream.chunk_every(2) + |> Stream.with_index() + |> Enum.flat_map(fn {[used, free], id} -> + (for _ <- Stream.cycle([0]) |> Enum.take(used) do id end) ++ (for _ <- Stream.cycle([0]) |> Enum.take(free) do nil end) + {[used], id} -> + (for _ <- Stream.cycle([0]) |> Enum.take(used) do id end) + end) + end + + @doc """ + ## Examples + iex> "2333133121414131402" |> Fragmenter.parse_disk_map() |> Fragmenter.defrag() |> Fragmenter.checksum() + 1928 + """ + def defrag(disk) do + cl_disk = disk |> Stream.with_index() + bak = cl_disk |> Enum.reverse() |> Enum.reject(&(elem(&1,0) == nil)) + + cl_disk + |> Enum.reduce( + {[], bak }, + fn + {c, i}, {fwd, [{_, j} | _] = bak} when c != nil and i <= j -> + { [c | fwd], bak} + {c, i}, {fwd, [{_, j} | _] = bak} when c != nil and i > j -> + { [nil | fwd], bak} + {nil, i}, {fwd, [{c, j} | rest]} when i <= j -> + { [c | fwd], rest } + {nil, i}, {fwd, [{_, j} | rest]} when i > j -> + { [nil | fwd], rest } + end + ) |> elem(0) |> Enum.reverse() + end + + def checksum(disk) do + disk + |> Stream.with_index() + |> Stream.reject(&(elem(&1, 0) == nil)) + |> Enum.reduce( + 0, + fn {c, i}, acc -> acc + c*i end + ) + end +end +``` + +```elixir +Fragmenter.parse_disk_map(input) +|> Fragmenter.defrag() +|> Fragmenter.checksum() +``` + +```elixir +defmodule Fragmenter2 do + def parse_disk_map(map) do + map + |> String.to_charlist() + |> Stream.map(&(&1 - 48)) + |> Stream.chunk_every(2) + |> Stream.with_index() + |> Stream.flat_map(fn {[used, free], id} -> + [Stream.cycle([id]) |> Enum.take(used), Stream.cycle([nil]) |> Enum.take(free)] + {[used], id} -> + [Stream.cycle([id]) |> Enum.take(used)] + end) + |> Stream.reject(&(&1 == [])) + end + + def defrag(disk) do + files = disk |> Enum.into([]) + x = Stream.flat_map(disk, &(&1)) |> Stream.reject(&(&1 == nil)) |> Enum.into(MapSet.new()) + defrag(files |> :queue.from_list(), :queue.new(), x) + end + + def merge_empty(file, empty) do + [file, empty |> Enum.take(Enum.count(empty) - Enum.count(file))] + end + + def defrag(dq, fragged, x) do + if :queue.is_empty(dq) do + fragged |> :queue.to_list() + else + case :queue.get(dq) do + [] -> defrag(:queue.drop(dq), fragged, x) + [n | _] = file when n != nil -> + n_available? = MapSet.member?(x, n) + defrag( + :queue.drop(dq), + :queue.in( + if n_available? do + file + else + Stream.cycle([nil]) |> Enum.take(file |> Enum.count()) + end, + fragged + ), + x + ) + [nil | _] = empty -> + fit_q = :queue.filter( + fn [n | _] = l -> MapSet.member?(x, n) and (l |> Enum.count()) <= (empty |> Enum.count()) + and (l |> Enum.at(0) != nil) + end, dq) + cond do + fit_q |> :queue.is_empty() -> defrag( + dq |> :queue.drop(), + :queue.in(empty, fragged), + x + ) + true -> + first_fit = fit_q |> :queue.get_r() + [first_fit, empty] = merge_empty(first_fit, empty) + defrag( + dq |> :queue.drop() |> then(&:queue.in_r(empty, &1)), + :queue.in(first_fit, fragged), + x |> MapSet.delete(first_fit |> Enum.at(0)) + ) + end + end + end + end + + def checksum(disk) do + disk + |> Stream.flat_map(&(&1)) + |> Stream.with_index() + |> Stream.reject(&(elem(&1, 0) == nil)) + |> Enum.reduce( + 0, + fn {c, i}, acc -> acc + c*i end + ) + end +end +``` + +```elixir +Fragmenter2.parse_disk_map(input) +|> Fragmenter2.defrag() +|> Fragmenter2.checksum() +```