diff --git a/2025/day01.livemd b/2025/day01.livemd index f2ced72..5fd3dcd 100644 --- a/2025/day01.livemd +++ b/2025/day01.livemd @@ -33,12 +33,10 @@ instructions = ## Part 1 ```elixir -Enum.reduce(instructions, {50, 0}, fn {_rot, val}, {curr, sum} -> - next = Integer.mod(curr + val, 100) - - {next, sum + if(next == 0, do: 1, else: 0)} +Enum.scan(instructions, 50, fn {_rot, val}, curr -> + Integer.mod(curr + val, 100) end) -|> elem(1) +|> Enum.count(& &1 == 0) ``` @@ -66,4 +64,4 @@ end) |> elem(1) ``` - + diff --git a/2025/day07.livemd b/2025/day07.livemd index 1f17b1b..ea1e1bc 100644 --- a/2025/day07.livemd +++ b/2025/day07.livemd @@ -114,4 +114,4 @@ end) |> Image.resize!(5, interpolate: :nearest) ``` - + diff --git a/2025/day09.livemd b/2025/day09.livemd new file mode 100644 index 0000000..e5463f8 --- /dev/null +++ b/2025/day09.livemd @@ -0,0 +1,167 @@ +# Day 09 + +```elixir +Mix.install([:kino_aoc, :image]) +``` + +## Setup + + + +```elixir +{:ok, puzzle_input} = + KinoAOC.download_puzzle("2025", "9", System.fetch_env!("LB_ADVENT_OF_CODE_SESSION")) +``` + +```elixir +tiles = + puzzle_input + |> String.split() + |> Enum.map(fn raw -> + raw + |> String.split(",") + |> Enum.map(&String.to_integer/1) + |> List.to_tuple() + end) +``` + +## Implementation + +```elixir +defmodule Combinatorics do + def combinations2(list) do + Stream.unfold(list, fn + [] -> nil + [x | rest] -> + curr = for y <- rest, do: [x, y] + + {curr, rest} + end) + |> Stream.flat_map(& &1) + end +end +``` + +```elixir +defmodule Rect do + require Record + + Record.defrecordp(:rect, l: 0, t: 0, r: 0, b: 0) + + def new({ax, ay}, {bx, by}) do + rect(l: min(ax, bx), r: max(ax, bx), t: min(ay, by), b: max(ay, by)) + end + + def area(rect() = r) do + width(r) * height(r) + end + + def intersect?( + rect(l: al, r: ar, t: at, b: ab), + rect(l: bl, r: br, t: bt, b: bb) + ) do + al < br and ar > bl and at < bb and ab > bt + end + + def width(rect(r: r, l: l)), do: r - l + 1 + def height(rect(t: t, b: b)), do: b - t + 1 + + def to_svg(rect(l: x, t: y) = r, opts \\ []) do + ~s""" + ~s(#{k}="#{v}") end)} /> + """ + end +end +``` + +```elixir +rects = + Combinatorics.combinations2(tiles) + |> Stream.map(fn [a, b] -> Rect.new(a, b) end) + |> Enum.sort() +``` + + + +## Part 1 + +```elixir +rects +|> Enum.max_by(&Rect.area/1) +|> IO.inspect() +|> Rect.area() +``` + + + +## Part 2 + +```elixir +edges = + tiles + |> Enum.chunk_every(2, 1, tiles) + |> Enum.map(&apply(Rect, :new, &1)) + |> Enum.sort() +``` + +```elixir +# [{1916, 50285}, {94619, 50285}, {94619, 48466}, {1668, 48466}] +# |> Stream.flat_map(fn a -> +# for b <- tiles do +# Rect.new(a, b) +# end +# end) +rects +|> Enum.reduce({0, nil}, fn r, {max, p} -> + a = Rect.area(r) + + if a > max and not Enum.any?(edges, &Rect.intersect?(r, &1)) do + {a, r} + else + {max, p} + end +end) +``` + +## Draw + +```elixir +{{min_x, _}, {max_x, _}} = Enum.min_max(tiles) +``` + +```elixir +{{_, min_y}, {_, max_y}} = Enum.min_max_by(tiles, &elem(&1, 1)) +``` + +```elixir +h = max_y + min_y +w = max_x + min_x +``` + +```elixir +{x, y} = hd(tiles) + +p1 = {:rect, 16055, 14805, 85282, 83613} +p2 = {:rect, 5741, 50285, 94619, 67351} + +svg = """ + + + + #{Rect.to_svg(p1, stroke: :orange, fill: :transparent, "stroke-width": 200)} + #{Rect.to_svg(p2, stroke: :yellow, fill: :transparent, "stroke-width": 200)} + #{ + for {x, y} <- tiles do + ~s() + end + } + +""" + +Kino.Image.new(svg, :svg) +``` + + diff --git a/2025/day10.livemd b/2025/day10.livemd new file mode 100644 index 0000000..60f355f --- /dev/null +++ b/2025/day10.livemd @@ -0,0 +1,188 @@ +# Day 10 + +```elixir +Mix.install([:kino_aoc, {:dantzig, github: "hauleth/dantzig", ref: "use-proper-binary"}], +config: [ + dantzig: [ + highs_binary_path: System.find_executable("highs") + ] +]) +``` + +## Section + + + +```elixir +{:ok, puzzle_input} = + KinoAOC.download_puzzle("2025", "10", System.fetch_env!("LB_ADVENT_OF_CODE_SESSION")) +``` + +```elixir +defmodule Decoder do + def decode("[" <> pattern), do: do_lights(String.reverse(String.trim_trailing(pattern, "]")), 0) + + def decode("(" <> rest) do + <> = rest + + seq + |> String.split(",") + |> Enum.map(&String.to_integer/1) + end + + def decode("{" <> rest) do + <> = rest + + seq + |> String.split(",") + |> Enum.map(&String.to_integer/1) + end + + defp do_lights("", num), do: num + defp do_lights("." <> rest, num), do: do_lights(rest, 2 * num) + defp do_lights("#" <> rest, num), do: do_lights(rest, 2 * num + 1) +end +``` + +```elixir +indicators = + puzzle_input + |> String.split("\n", trim: true) + |> Enum.map(fn raw -> + [lights | rest] = + raw + |> String.split() + |> Enum.map(&Decoder.decode/1) + + {buttons, [whatever]} = Enum.split(rest, -1) + + {lights, buttons, whatever} + end) +``` + + + +## Part 1 + +We are looking for such sequence of buttons that will provide pattern we are looking for. It can be solved by brute force with simple observation that buttons $a_n$ as well as light pattern $p$ can be represented by binary pattern. This allows us to use $\oplus$ (exclusive or, aka `XOR`) as an operation for pressing button. + +Thanks to that observation we can see, that order in which we press buttons doesn't matter, as $\oplus$ is reflexive, i.e.: + +$$ +\begin{equation} +a \oplus b = b \oplus a +\end{equation} +$$ + +Additionally, wrt. $\oplus$ we have identity element $0$ and inverse element is the same as an original element, i.e. + +$$ +\begin{align} +a \oplus 0 = a \\ +a \oplus a = 0 +\end{align} +$$ + +Additionally we can observe that: + +$$ +\begin{equation} +(a \oplus b) \oplus c = a \oplus (b \oplus c) +\end{equation} +$$ + +With that observation we can deduce that each button will be pressed at most once and the order in which we press buttons doesn't matter. + +--- + +So now we look for set $A = \{a_n\}$ of buttons that + +$$ +A \in \mathcal{P}(\text{Buttons}) \\ +\forall_{i, j} \; i \ne j \implies a_i \ne a_j \\ +\bigoplus A = p +$$ + +```elixir +defmodule Comb do + def all_possible([]), do: [[]] + def all_possible([a | rest]) do + sub = all_possible(rest) + + sub ++ Enum.map(sub, &[a | &1]) + end +end +``` + +```elixir +indicators +|> Enum.sum_by(fn {p, a, _} -> + a + |> Enum.map(&Enum.sum_by(&1, fn p -> 2 ** p end)) + |> Comb.all_possible() + |> Enum.sort_by(&length/1) + |> Enum.find(fn seq -> + r = Enum.reduce(seq, 0, &Bitwise.bxor/2) + + p == r + end) + |> length() +end) +``` + + + +## Part 2 + +```elixir +defmodule Joltage do + alias Dantzig.Polynomial + alias Dantzig.Constraint + alias Dantzig.Problem + + def solve({_pat, buttons, goal}) do + p = Problem.new(direction: :minimize) + + {vars, {p, map}} = + buttons + |> Enum.with_index() + |> Enum.map_reduce({p, %{}}, fn {list, idx}, {p, acc} -> + {p, var} = Problem.new_variable(p, "v#{idx}", min: 0, type: :integer) + + acc = + Enum.reduce(list, acc, fn key, map -> + Map.update(map, key, [var], &[var | &1]) + end) + + {var, {p, acc}} + end) + + p = + map + |> Enum.sort() + |> Enum.map(&elem(&1, 1)) + |> Enum.zip(goal) + |> Enum.reduce(p, fn {vars, target}, p -> + poly = Polynomial.sum(vars) + const = Constraint.new(poly, :==, target) + + Problem.add_constraint(p, const) + end) + + p = Problem.increment_objective(p, Polynomial.sum(vars)) + + {:ok, s} = Dantzig.HiGHS.solve(p) + + Enum.sum_by(vars, &Dantzig.Solution.evaluate(s, &1)) + end +end +``` + +```elixir +indicators +|> Task.async_stream(&Joltage.solve/1, ordered: false) +|> Enum.sum_by(&elem(&1, 1)) +|> trunc() +``` + + diff --git a/2025/day11.livemd b/2025/day11.livemd new file mode 100644 index 0000000..7a5f83b --- /dev/null +++ b/2025/day11.livemd @@ -0,0 +1,85 @@ +# Day 11 + +```elixir +Mix.install([:kino_aoc]) +``` + +## Parse + + + +```elixir +{:ok, puzzle_input} = + KinoAOC.download_puzzle("2025", "11", System.fetch_env!("LB_ADVENT_OF_CODE_SESSION")) +``` + +```elixir +graph = + puzzle_input + |> String.split("\n", trim: true) + |> Map.new(fn <> <> ": " <> rest -> + {from, String.split(rest)} + end) +``` + +## Implementation + +```elixir +defmodule Servers do + def paths(graph, from, to), do: paths(graph, from, to, [from]) + + defp paths(_graph, to, to, acc), do: [Enum.reverse(acc)] + + defp paths(graph, from, to, acc) do + if next = graph[from] do + Stream.flat_map(next, &paths(graph, &1, to, [&1 | acc])) + else + [] + end + end + + def paths_through(graph, from, to, required), + do: path_through(graph, from, to, MapSet.new(required), %{}) + + defp path_through(_graph, to, to, required, memo), + do: {if(Enum.empty?(required), do: 1, else: 0), memo} + + defp path_through(graph, from, to, required, memo) do + state = MapSet.delete(required, from) + + with :error <- Map.fetch(memo, {from, state}), + {:ok, next} <- Map.fetch(graph, from) do + {sum, memo} = + Enum.reduce(next, {0, memo}, fn n, {sum, acc} -> + {c, next_acc} = path_through(graph, n, to, state, acc) + + {c + sum, next_acc} + end) + + {sum, Map.put(memo, {from, state}, sum)} + else + :error -> {0, memo} + {:ok, val} -> {val, memo} + end + end +end +``` + + + +## Part 1 + +```elixir +Servers.paths(graph, "you", "out") |> Enum.count() +``` + + + +## Part 2 + +```elixir +Servers.paths_through(graph, "svr", "out", ["dac", "fft"]) +|> elem(0) +``` + + diff --git a/2025/day12.livemd b/2025/day12.livemd new file mode 100644 index 0000000..a5f0433 --- /dev/null +++ b/2025/day12.livemd @@ -0,0 +1,73 @@ +# Day 12 + +```elixir +Mix.install([:kino_aoc]) +``` + +## Parsing + + + +```elixir +{:ok, puzzle_input} = + KinoAOC.download_puzzle("2025", "12", System.fetch_env!("LB_ADVENT_OF_CODE_SESSION")) +``` + +```elixir +{areas, boxes} = + puzzle_input + |> String.split("\n\n") + |> List.pop_at(-1) +``` + +```elixir +areas = + areas + |> String.split("\n") + |> Enum.map(fn raw -> + [area | counts] = String.split(raw) + + area = + area + |> String.trim(":") + |> String.split("x") + |> Enum.map(&String.to_integer/1) + |> Enum.product() + + counts = Enum.map(counts, &String.to_integer/1) + + {area, counts} + end) +``` + +```elixir +boxes = + boxes + |> Enum.map(fn <<_::binary-3>> <> rest -> + rest + |> String.to_charlist() + |> Enum.count(&(&1 == ?#)) + end) +``` + + + +## Part 1 + +```elixir +areas +|> Enum.count(fn {max, counts} -> + counts + |> Enum.zip_with(boxes, &*/2) + |> Enum.sum() + |> then(& &1 <= max) +end) +``` + + + +## Part 2 + +FIN + +