···11+# Day 09
22+33+```elixir
44+Mix.install([:kino_aoc, :image])
55+```
66+77+## Setup
88+99+<!-- livebook:{"attrs":"eyJhc3NpZ25fdG8iOiJwdXp6bGVfaW5wdXQiLCJkYXkiOiI5Iiwic2Vzc2lvbl9zZWNyZXQiOiJBRFZFTlRfT0ZfQ09ERV9TRVNTSU9OIiwieWVhciI6IjIwMjUifQ","chunks":null,"kind":"Elixir.KinoAOC.HelperCell","livebook_object":"smart_cell"} -->
1010+1111+```elixir
1212+{:ok, puzzle_input} =
1313+ KinoAOC.download_puzzle("2025", "9", System.fetch_env!("LB_ADVENT_OF_CODE_SESSION"))
1414+```
1515+1616+```elixir
1717+tiles =
1818+ puzzle_input
1919+ |> String.split()
2020+ |> Enum.map(fn raw ->
2121+ raw
2222+ |> String.split(",")
2323+ |> Enum.map(&String.to_integer/1)
2424+ |> List.to_tuple()
2525+ end)
2626+```
2727+2828+## Implementation
2929+3030+```elixir
3131+defmodule Combinatorics do
3232+ def combinations2(list) do
3333+ Stream.unfold(list, fn
3434+ [] -> nil
3535+ [x | rest] ->
3636+ curr = for y <- rest, do: [x, y]
3737+3838+ {curr, rest}
3939+ end)
4040+ |> Stream.flat_map(& &1)
4141+ end
4242+end
4343+```
4444+4545+```elixir
4646+defmodule Rect do
4747+ require Record
4848+4949+ Record.defrecordp(:rect, l: 0, t: 0, r: 0, b: 0)
5050+5151+ def new({ax, ay}, {bx, by}) do
5252+ rect(l: min(ax, bx), r: max(ax, bx), t: min(ay, by), b: max(ay, by))
5353+ end
5454+5555+ def area(rect() = r) do
5656+ width(r) * height(r)
5757+ end
5858+5959+ def intersect?(
6060+ rect(l: al, r: ar, t: at, b: ab),
6161+ rect(l: bl, r: br, t: bt, b: bb)
6262+ ) do
6363+ al < br and ar > bl and at < bb and ab > bt
6464+ end
6565+6666+ def width(rect(r: r, l: l)), do: r - l + 1
6767+ def height(rect(t: t, b: b)), do: b - t + 1
6868+6969+ def to_svg(rect(l: x, t: y) = r, opts \\ []) do
7070+ ~s"""
7171+ <rect x="#{x}" y="#{y}" width="#{width(r)}" height="#{height(r)}"
7272+ #{Enum.map_join(opts, " ", fn {k, v} -> ~s(#{k}="#{v}") end)} />
7373+ """
7474+ end
7575+end
7676+```
7777+7878+```elixir
7979+rects =
8080+ Combinatorics.combinations2(tiles)
8181+ |> Stream.map(fn [a, b] -> Rect.new(a, b) end)
8282+ |> Enum.sort()
8383+```
8484+8585+<!-- livebook:{"branch_parent_index":1} -->
8686+8787+## Part 1
8888+8989+```elixir
9090+rects
9191+|> Enum.max_by(&Rect.area/1)
9292+|> IO.inspect()
9393+|> Rect.area()
9494+```
9595+9696+<!-- livebook:{"branch_parent_index":1} -->
9797+9898+## Part 2
9999+100100+```elixir
101101+edges =
102102+ tiles
103103+ |> Enum.chunk_every(2, 1, tiles)
104104+ |> Enum.map(&apply(Rect, :new, &1))
105105+ |> Enum.sort()
106106+```
107107+108108+```elixir
109109+# [{1916, 50285}, {94619, 50285}, {94619, 48466}, {1668, 48466}]
110110+# |> Stream.flat_map(fn a ->
111111+# for b <- tiles do
112112+# Rect.new(a, b)
113113+# end
114114+# end)
115115+rects
116116+|> Enum.reduce({0, nil}, fn r, {max, p} ->
117117+ a = Rect.area(r)
118118+119119+ if a > max and not Enum.any?(edges, &Rect.intersect?(r, &1)) do
120120+ {a, r}
121121+ else
122122+ {max, p}
123123+ end
124124+end)
125125+```
126126+127127+## Draw
128128+129129+```elixir
130130+{{min_x, _}, {max_x, _}} = Enum.min_max(tiles)
131131+```
132132+133133+```elixir
134134+{{_, min_y}, {_, max_y}} = Enum.min_max_by(tiles, &elem(&1, 1))
135135+```
136136+137137+```elixir
138138+h = max_y + min_y
139139+w = max_x + min_x
140140+```
141141+142142+```elixir
143143+{x, y} = hd(tiles)
144144+145145+p1 = {:rect, 16055, 14805, 85282, 83613}
146146+p2 = {:rect, 5741, 50285, 94619, 67351}
147147+148148+svg = """
149149+<svg xmlns="http://www.w3.org/2000/svg" width="500" viewBox="0 0 #{w} #{h}">
150150+ <rect width="100%" height="100%" fill="black" />
151151+ <path d="M#{x} #{y}#{
152152+ for {x, y} <- tl(tiles), do: "L#{x} #{y} "
153153+ } L#{x} #{y}" stroke="darkgreen" fill="green" stroke-width="200" />
154154+ #{Rect.to_svg(p1, stroke: :orange, fill: :transparent, "stroke-width": 200)}
155155+ #{Rect.to_svg(p2, stroke: :yellow, fill: :transparent, "stroke-width": 200)}
156156+ #{
157157+ for {x, y} <- tiles do
158158+ ~s(<rect x="#{x - 100}" y="#{y - 100}" width="200" height="200" fill="red" />)
159159+ end
160160+ }
161161+</svg>
162162+"""
163163+164164+Kino.Image.new(svg, :svg)
165165+```
166166+167167+<!-- livebook:{"offset":3329,"stamp":{"token":"XCP.SSlM8wg30CucU7IP0n8MTbPIvnvvcZRXcglo9DY17kk0O0fwtLfUUiYJauWdspkXUlp0Axl5YscQNBKK5mSycPLd0iNdz8JFPfjCg4rS2pyM3JuQj73ipXd27t8Yd0ylig","version":2}} -->
+188
2025/day10.livemd
···11+# Day 10
22+33+```elixir
44+Mix.install([:kino_aoc, {:dantzig, github: "hauleth/dantzig", ref: "use-proper-binary"}],
55+config: [
66+ dantzig: [
77+ highs_binary_path: System.find_executable("highs")
88+ ]
99+])
1010+```
1111+1212+## Section
1313+1414+<!-- livebook:{"attrs":"eyJhc3NpZ25fdG8iOiJwdXp6bGVfaW5wdXQiLCJkYXkiOiIxMCIsInNlc3Npb25fc2VjcmV0IjoiQURWRU5UX09GX0NPREVfU0VTU0lPTiIsInllYXIiOiIyMDI1In0","chunks":null,"kind":"Elixir.KinoAOC.HelperCell","livebook_object":"smart_cell"} -->
1515+1616+```elixir
1717+{:ok, puzzle_input} =
1818+ KinoAOC.download_puzzle("2025", "10", System.fetch_env!("LB_ADVENT_OF_CODE_SESSION"))
1919+```
2020+2121+```elixir
2222+defmodule Decoder do
2323+ def decode("[" <> pattern), do: do_lights(String.reverse(String.trim_trailing(pattern, "]")), 0)
2424+2525+ def decode("(" <> rest) do
2626+ <<seq::binary-size(byte_size(rest) - 1), ")">> = rest
2727+2828+ seq
2929+ |> String.split(",")
3030+ |> Enum.map(&String.to_integer/1)
3131+ end
3232+3333+ def decode("{" <> rest) do
3434+ <<seq::binary-size(byte_size(rest) - 1), "}">> = rest
3535+3636+ seq
3737+ |> String.split(",")
3838+ |> Enum.map(&String.to_integer/1)
3939+ end
4040+4141+ defp do_lights("", num), do: num
4242+ defp do_lights("." <> rest, num), do: do_lights(rest, 2 * num)
4343+ defp do_lights("#" <> rest, num), do: do_lights(rest, 2 * num + 1)
4444+end
4545+```
4646+4747+```elixir
4848+indicators =
4949+ puzzle_input
5050+ |> String.split("\n", trim: true)
5151+ |> Enum.map(fn raw ->
5252+ [lights | rest] =
5353+ raw
5454+ |> String.split()
5555+ |> Enum.map(&Decoder.decode/1)
5656+5757+ {buttons, [whatever]} = Enum.split(rest, -1)
5858+5959+ {lights, buttons, whatever}
6060+ end)
6161+```
6262+6363+<!-- livebook:{"branch_parent_index":0} -->
6464+6565+## Part 1
6666+6767+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.
6868+6969+Thanks to that observation we can see, that order in which we press buttons doesn't matter, as $\oplus$ is reflexive, i.e.:
7070+7171+$$
7272+\begin{equation}
7373+a \oplus b = b \oplus a
7474+\end{equation}
7575+$$
7676+7777+Additionally, wrt. $\oplus$ we have identity element $0$ and inverse element is the same as an original element, i.e.
7878+7979+$$
8080+\begin{align}
8181+a \oplus 0 = a \\
8282+a \oplus a = 0
8383+\end{align}
8484+$$
8585+8686+Additionally we can observe that:
8787+8888+$$
8989+\begin{equation}
9090+(a \oplus b) \oplus c = a \oplus (b \oplus c)
9191+\end{equation}
9292+$$
9393+9494+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.
9595+9696+---
9797+9898+So now we look for set $A = \{a_n\}$ of buttons that
9999+100100+$$
101101+A \in \mathcal{P}(\text{Buttons}) \\
102102+\forall_{i, j} \; i \ne j \implies a_i \ne a_j \\
103103+\bigoplus A = p
104104+$$
105105+106106+```elixir
107107+defmodule Comb do
108108+ def all_possible([]), do: [[]]
109109+ def all_possible([a | rest]) do
110110+ sub = all_possible(rest)
111111+112112+ sub ++ Enum.map(sub, &[a | &1])
113113+ end
114114+end
115115+```
116116+117117+```elixir
118118+indicators
119119+|> Enum.sum_by(fn {p, a, _} ->
120120+ a
121121+ |> Enum.map(&Enum.sum_by(&1, fn p -> 2 ** p end))
122122+ |> Comb.all_possible()
123123+ |> Enum.sort_by(&length/1)
124124+ |> Enum.find(fn seq ->
125125+ r = Enum.reduce(seq, 0, &Bitwise.bxor/2)
126126+127127+ p == r
128128+ end)
129129+ |> length()
130130+end)
131131+```
132132+133133+<!-- livebook:{"branch_parent_index":0} -->
134134+135135+## Part 2
136136+137137+```elixir
138138+defmodule Joltage do
139139+ alias Dantzig.Polynomial
140140+ alias Dantzig.Constraint
141141+ alias Dantzig.Problem
142142+143143+ def solve({_pat, buttons, goal}) do
144144+ p = Problem.new(direction: :minimize)
145145+146146+ {vars, {p, map}} =
147147+ buttons
148148+ |> Enum.with_index()
149149+ |> Enum.map_reduce({p, %{}}, fn {list, idx}, {p, acc} ->
150150+ {p, var} = Problem.new_variable(p, "v#{idx}", min: 0, type: :integer)
151151+152152+ acc =
153153+ Enum.reduce(list, acc, fn key, map ->
154154+ Map.update(map, key, [var], &[var | &1])
155155+ end)
156156+157157+ {var, {p, acc}}
158158+ end)
159159+160160+ p =
161161+ map
162162+ |> Enum.sort()
163163+ |> Enum.map(&elem(&1, 1))
164164+ |> Enum.zip(goal)
165165+ |> Enum.reduce(p, fn {vars, target}, p ->
166166+ poly = Polynomial.sum(vars)
167167+ const = Constraint.new(poly, :==, target)
168168+169169+ Problem.add_constraint(p, const)
170170+ end)
171171+172172+ p = Problem.increment_objective(p, Polynomial.sum(vars))
173173+174174+ {:ok, s} = Dantzig.HiGHS.solve(p)
175175+176176+ Enum.sum_by(vars, &Dantzig.Solution.evaluate(s, &1))
177177+ end
178178+end
179179+```
180180+181181+```elixir
182182+indicators
183183+|> Task.async_stream(&Joltage.solve/1, ordered: false)
184184+|> Enum.sum_by(&elem(&1, 1))
185185+|> trunc()
186186+```
187187+188188+<!-- livebook:{"offset":4336,"stamp":{"token":"XCP.sfoBGIHkFvWbEHVrY-dL-QiEcwRz_ZCIjn3lQ0RFHActIOwEapmOPBt0ygbQAmrnEjYPlFm5KrcWx4LfIPbSznxxcea0fYORG9GbBBpRCm-tYbGXYTCGgqgTvOsifyWDNg","version":2}} -->
+85
2025/day11.livemd
···11+# Day 11
22+33+```elixir
44+Mix.install([:kino_aoc])
55+```
66+77+## Parse
88+99+<!-- livebook:{"attrs":"eyJhc3NpZ25fdG8iOiJwdXp6bGVfaW5wdXQiLCJkYXkiOiIxMSIsInNlc3Npb25fc2VjcmV0IjoiQURWRU5UX09GX0NPREVfU0VTU0lPTiIsInllYXIiOiIyMDI1In0","chunks":null,"kind":"Elixir.KinoAOC.HelperCell","livebook_object":"smart_cell"} -->
1010+1111+```elixir
1212+{:ok, puzzle_input} =
1313+ KinoAOC.download_puzzle("2025", "11", System.fetch_env!("LB_ADVENT_OF_CODE_SESSION"))
1414+```
1515+1616+```elixir
1717+graph =
1818+ puzzle_input
1919+ |> String.split("\n", trim: true)
2020+ |> Map.new(fn <<from::binary-3>> <> ": " <> rest ->
2121+ {from, String.split(rest)}
2222+ end)
2323+```
2424+2525+## Implementation
2626+2727+```elixir
2828+defmodule Servers do
2929+ def paths(graph, from, to), do: paths(graph, from, to, [from])
3030+3131+ defp paths(_graph, to, to, acc), do: [Enum.reverse(acc)]
3232+3333+ defp paths(graph, from, to, acc) do
3434+ if next = graph[from] do
3535+ Stream.flat_map(next, &paths(graph, &1, to, [&1 | acc]))
3636+ else
3737+ []
3838+ end
3939+ end
4040+4141+ def paths_through(graph, from, to, required),
4242+ do: path_through(graph, from, to, MapSet.new(required), %{})
4343+4444+ defp path_through(_graph, to, to, required, memo),
4545+ do: {if(Enum.empty?(required), do: 1, else: 0), memo}
4646+4747+ defp path_through(graph, from, to, required, memo) do
4848+ state = MapSet.delete(required, from)
4949+5050+ with :error <- Map.fetch(memo, {from, state}),
5151+ {:ok, next} <- Map.fetch(graph, from) do
5252+ {sum, memo} =
5353+ Enum.reduce(next, {0, memo}, fn n, {sum, acc} ->
5454+ {c, next_acc} = path_through(graph, n, to, state, acc)
5555+5656+ {c + sum, next_acc}
5757+ end)
5858+5959+ {sum, Map.put(memo, {from, state}, sum)}
6060+ else
6161+ :error -> {0, memo}
6262+ {:ok, val} -> {val, memo}
6363+ end
6464+ end
6565+end
6666+```
6767+6868+<!-- livebook:{"branch_parent_index":1} -->
6969+7070+## Part 1
7171+7272+```elixir
7373+Servers.paths(graph, "you", "out") |> Enum.count()
7474+```
7575+7676+<!-- livebook:{"branch_parent_index":1} -->
7777+7878+## Part 2
7979+8080+```elixir
8181+Servers.paths_through(graph, "svr", "out", ["dac", "fft"])
8282+|> elem(0)
8383+```
8484+8585+<!-- livebook:{"offset":1933,"stamp":{"token":"XCP.fzzPot48c9xb6ftw8IEb1V6uTX6csBICnoXjN1PU6c3DylXjP-bco9PgawAoc2GSeNJRzS3NCPmJ9aO9Jm2ehPXnam5fN-bZvUbEcxKmNA8SqH2_fJ5o_qp8rIaxpH6DRQ","version":2}} -->