this repo has no description

Remaining day for AoC 2025

hauleth.dev cf4580a0 8a394815

verified
+4 -6
2025/day01.livemd
···
## 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)}
end)
-
|> elem(1)
```
<!-- livebook:{"branch_parent_index":0} -->
···
|> elem(1)
```
-
<!-- livebook:{"offset":1324,"stamp":{"token":"XCP.pI2XE8mwBZJ1_5rOsosmoLh5-clSindOf0NN_piQhnU8r5baxfvJEHunCv7iXrvn3G43jfoHCFBZZK6KbxbIY0lzTJzKKWPrrl0bQ9S1RQvkk6dMv_LpnTv13HwNr3JYMw","version":2}} -->
···
## Part 1
```elixir
+
Enum.scan(instructions, 50, fn {_rot, val}, curr ->
+
Integer.mod(curr + val, 100)
end)
+
|> Enum.count(& &1 == 0)
```
<!-- livebook:{"branch_parent_index":0} -->
···
|> elem(1)
```
+
<!-- livebook:{"offset":1270,"stamp":{"token":"XCP.fAfBqYepop325WQHLC6mJPz_Wpl7bDiz0qRbslvjG31mRJWcHALfstxgj_iOj3nf9_GCzh_WmPiNR4MnzZidF2bz_zMMXHTEo18hp1z3gQDdi5hTSO8UG4YSYnUEtGxO2g","version":2}} -->
+1 -1
2025/day07.livemd
···
|> Image.resize!(5, interpolate: :nearest)
```
-
<!-- livebook:{"offset":2553,"stamp":{"token":"XCP.jUh_Ehkij-SpgOuY0sdHr5YT6IaJif0yd6mre9jiQpu7HHlbAvVeyKXHCNfcp0L0eLTRgut7HlWmzGi3AlT1SFWsJqgDuD-ePZl8JBbLYfW9j0leDSmN7-aQgVCmlMvFDw","version":2}} -->
···
|> Image.resize!(5, interpolate: :nearest)
```
+
<!-- livebook:{"offset":2553,"stamp":{"token":"XCP.6lQNAY-yntHErm41fMi4xgwAxGSGZ7l6ekgYSe6b3Exm48kBKgyIoCK62LLVaeQP-1PfZFpd3tzHwAIA8OWTsnRBuWsxmex5qixMa-l4DaThm-jrSaVsE3Qj4aO-TrqdqA","version":2}} -->
+167
2025/day09.livemd
···
···
+
# Day 09
+
+
```elixir
+
Mix.install([:kino_aoc, :image])
+
```
+
+
## Setup
+
+
<!-- livebook:{"attrs":"eyJhc3NpZ25fdG8iOiJwdXp6bGVfaW5wdXQiLCJkYXkiOiI5Iiwic2Vzc2lvbl9zZWNyZXQiOiJBRFZFTlRfT0ZfQ09ERV9TRVNTSU9OIiwieWVhciI6IjIwMjUifQ","chunks":null,"kind":"Elixir.KinoAOC.HelperCell","livebook_object":"smart_cell"} -->
+
+
```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"""
+
<rect x="#{x}" y="#{y}" width="#{width(r)}" height="#{height(r)}"
+
#{Enum.map_join(opts, " ", fn {k, v} -> ~s(#{k}="#{v}") end)} />
+
"""
+
end
+
end
+
```
+
+
```elixir
+
rects =
+
Combinatorics.combinations2(tiles)
+
|> Stream.map(fn [a, b] -> Rect.new(a, b) end)
+
|> Enum.sort()
+
```
+
+
<!-- livebook:{"branch_parent_index":1} -->
+
+
## Part 1
+
+
```elixir
+
rects
+
|> Enum.max_by(&Rect.area/1)
+
|> IO.inspect()
+
|> Rect.area()
+
```
+
+
<!-- livebook:{"branch_parent_index":1} -->
+
+
## 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 = """
+
<svg xmlns="http://www.w3.org/2000/svg" width="500" viewBox="0 0 #{w} #{h}">
+
<rect width="100%" height="100%" fill="black" />
+
<path d="M#{x} #{y}#{
+
for {x, y} <- tl(tiles), do: "L#{x} #{y} "
+
} L#{x} #{y}" stroke="darkgreen" fill="green" stroke-width="200" />
+
#{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(<rect x="#{x - 100}" y="#{y - 100}" width="200" height="200" fill="red" />)
+
end
+
}
+
</svg>
+
"""
+
+
Kino.Image.new(svg, :svg)
+
```
+
+
<!-- livebook:{"offset":3329,"stamp":{"token":"XCP.SSlM8wg30CucU7IP0n8MTbPIvnvvcZRXcglo9DY17kk0O0fwtLfUUiYJauWdspkXUlp0Axl5YscQNBKK5mSycPLd0iNdz8JFPfjCg4rS2pyM3JuQj73ipXd27t8Yd0ylig","version":2}} -->
+188
2025/day10.livemd
···
···
+
# 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
+
+
<!-- livebook:{"attrs":"eyJhc3NpZ25fdG8iOiJwdXp6bGVfaW5wdXQiLCJkYXkiOiIxMCIsInNlc3Npb25fc2VjcmV0IjoiQURWRU5UX09GX0NPREVfU0VTU0lPTiIsInllYXIiOiIyMDI1In0","chunks":null,"kind":"Elixir.KinoAOC.HelperCell","livebook_object":"smart_cell"} -->
+
+
```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
+
<<seq::binary-size(byte_size(rest) - 1), ")">> = rest
+
+
seq
+
|> String.split(",")
+
|> Enum.map(&String.to_integer/1)
+
end
+
+
def decode("{" <> rest) do
+
<<seq::binary-size(byte_size(rest) - 1), "}">> = 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)
+
```
+
+
<!-- livebook:{"branch_parent_index":0} -->
+
+
## 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)
+
```
+
+
<!-- livebook:{"branch_parent_index":0} -->
+
+
## 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()
+
```
+
+
<!-- livebook:{"offset":4336,"stamp":{"token":"XCP.sfoBGIHkFvWbEHVrY-dL-QiEcwRz_ZCIjn3lQ0RFHActIOwEapmOPBt0ygbQAmrnEjYPlFm5KrcWx4LfIPbSznxxcea0fYORG9GbBBpRCm-tYbGXYTCGgqgTvOsifyWDNg","version":2}} -->
+85
2025/day11.livemd
···
···
+
# Day 11
+
+
```elixir
+
Mix.install([:kino_aoc])
+
```
+
+
## Parse
+
+
<!-- livebook:{"attrs":"eyJhc3NpZ25fdG8iOiJwdXp6bGVfaW5wdXQiLCJkYXkiOiIxMSIsInNlc3Npb25fc2VjcmV0IjoiQURWRU5UX09GX0NPREVfU0VTU0lPTiIsInllYXIiOiIyMDI1In0","chunks":null,"kind":"Elixir.KinoAOC.HelperCell","livebook_object":"smart_cell"} -->
+
+
```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 <<from::binary-3>> <> ": " <> 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
+
```
+
+
<!-- livebook:{"branch_parent_index":1} -->
+
+
## Part 1
+
+
```elixir
+
Servers.paths(graph, "you", "out") |> Enum.count()
+
```
+
+
<!-- livebook:{"branch_parent_index":1} -->
+
+
## Part 2
+
+
```elixir
+
Servers.paths_through(graph, "svr", "out", ["dac", "fft"])
+
|> elem(0)
+
```
+
+
<!-- livebook:{"offset":1933,"stamp":{"token":"XCP.fzzPot48c9xb6ftw8IEb1V6uTX6csBICnoXjN1PU6c3DylXjP-bco9PgawAoc2GSeNJRzS3NCPmJ9aO9Jm2ehPXnam5fN-bZvUbEcxKmNA8SqH2_fJ5o_qp8rIaxpH6DRQ","version":2}} -->
+73
2025/day12.livemd
···
···
+
# Day 12
+
+
```elixir
+
Mix.install([:kino_aoc])
+
```
+
+
## Parsing
+
+
<!-- livebook:{"attrs":"eyJhc3NpZ25fdG8iOiJwdXp6bGVfaW5wdXQiLCJkYXkiOiIxMiIsInNlc3Npb25fc2VjcmV0IjoiQURWRU5UX09GX0NPREVfU0VTU0lPTiIsInllYXIiOiIyMDI1In0","chunks":null,"kind":"Elixir.KinoAOC.HelperCell","livebook_object":"smart_cell"} -->
+
+
```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)
+
```
+
+
<!-- livebook:{"branch_parent_index":0} -->
+
+
## Part 1
+
+
```elixir
+
areas
+
|> Enum.count(fn {max, counts} ->
+
counts
+
|> Enum.zip_with(boxes, &*/2)
+
|> Enum.sum()
+
|> then(& &1 <= max)
+
end)
+
```
+
+
<!-- livebook:{"branch_parent_index":0} -->
+
+
## Part 2
+
+
FIN
+
+
<!-- livebook:{"offset":1266,"stamp":{"token":"XCP.VAO97d30rTEs0AWIHkPD4J0fLm3S60tQ3fKoA-riReFbzMnqL1jIoxttGhvNSnCfZVNfeUBuSYVe6PrIshxVGBwjr3pjNHCyFLSb4iSPNh277lkMmh6Gtrlfr8dvsYvw0g","version":2}} -->