this repo has no description

Remaining day for AoC 2025

hauleth.dev cf4580a0 8a394815

verified
+4 -6
2025/day01.livemd
··· 33 33 ## Part 1 34 34 35 35 ```elixir 36 - Enum.reduce(instructions, {50, 0}, fn {_rot, val}, {curr, sum} -> 37 - next = Integer.mod(curr + val, 100) 38 - 39 - {next, sum + if(next == 0, do: 1, else: 0)} 36 + Enum.scan(instructions, 50, fn {_rot, val}, curr -> 37 + Integer.mod(curr + val, 100) 40 38 end) 41 - |> elem(1) 39 + |> Enum.count(& &1 == 0) 42 40 ``` 43 41 44 42 <!-- livebook:{"branch_parent_index":0} --> ··· 66 64 |> elem(1) 67 65 ``` 68 66 69 - <!-- livebook:{"offset":1324,"stamp":{"token":"XCP.pI2XE8mwBZJ1_5rOsosmoLh5-clSindOf0NN_piQhnU8r5baxfvJEHunCv7iXrvn3G43jfoHCFBZZK6KbxbIY0lzTJzKKWPrrl0bQ9S1RQvkk6dMv_LpnTv13HwNr3JYMw","version":2}} --> 67 + <!-- livebook:{"offset":1270,"stamp":{"token":"XCP.fAfBqYepop325WQHLC6mJPz_Wpl7bDiz0qRbslvjG31mRJWcHALfstxgj_iOj3nf9_GCzh_WmPiNR4MnzZidF2bz_zMMXHTEo18hp1z3gQDdi5hTSO8UG4YSYnUEtGxO2g","version":2}} -->
+1 -1
2025/day07.livemd
··· 114 114 |> Image.resize!(5, interpolate: :nearest) 115 115 ``` 116 116 117 - <!-- livebook:{"offset":2553,"stamp":{"token":"XCP.jUh_Ehkij-SpgOuY0sdHr5YT6IaJif0yd6mre9jiQpu7HHlbAvVeyKXHCNfcp0L0eLTRgut7HlWmzGi3AlT1SFWsJqgDuD-ePZl8JBbLYfW9j0leDSmN7-aQgVCmlMvFDw","version":2}} --> 117 + <!-- livebook:{"offset":2553,"stamp":{"token":"XCP.6lQNAY-yntHErm41fMi4xgwAxGSGZ7l6ekgYSe6b3Exm48kBKgyIoCK62LLVaeQP-1PfZFpd3tzHwAIA8OWTsnRBuWsxmex5qixMa-l4DaThm-jrSaVsE3Qj4aO-TrqdqA","version":2}} -->
+167
2025/day09.livemd
··· 1 + # Day 09 2 + 3 + ```elixir 4 + Mix.install([:kino_aoc, :image]) 5 + ``` 6 + 7 + ## Setup 8 + 9 + <!-- livebook:{"attrs":"eyJhc3NpZ25fdG8iOiJwdXp6bGVfaW5wdXQiLCJkYXkiOiI5Iiwic2Vzc2lvbl9zZWNyZXQiOiJBRFZFTlRfT0ZfQ09ERV9TRVNTSU9OIiwieWVhciI6IjIwMjUifQ","chunks":null,"kind":"Elixir.KinoAOC.HelperCell","livebook_object":"smart_cell"} --> 10 + 11 + ```elixir 12 + {:ok, puzzle_input} = 13 + KinoAOC.download_puzzle("2025", "9", System.fetch_env!("LB_ADVENT_OF_CODE_SESSION")) 14 + ``` 15 + 16 + ```elixir 17 + tiles = 18 + puzzle_input 19 + |> String.split() 20 + |> Enum.map(fn raw -> 21 + raw 22 + |> String.split(",") 23 + |> Enum.map(&String.to_integer/1) 24 + |> List.to_tuple() 25 + end) 26 + ``` 27 + 28 + ## Implementation 29 + 30 + ```elixir 31 + defmodule Combinatorics do 32 + def combinations2(list) do 33 + Stream.unfold(list, fn 34 + [] -> nil 35 + [x | rest] -> 36 + curr = for y <- rest, do: [x, y] 37 + 38 + {curr, rest} 39 + end) 40 + |> Stream.flat_map(& &1) 41 + end 42 + end 43 + ``` 44 + 45 + ```elixir 46 + defmodule Rect do 47 + require Record 48 + 49 + Record.defrecordp(:rect, l: 0, t: 0, r: 0, b: 0) 50 + 51 + def new({ax, ay}, {bx, by}) do 52 + rect(l: min(ax, bx), r: max(ax, bx), t: min(ay, by), b: max(ay, by)) 53 + end 54 + 55 + def area(rect() = r) do 56 + width(r) * height(r) 57 + end 58 + 59 + def intersect?( 60 + rect(l: al, r: ar, t: at, b: ab), 61 + rect(l: bl, r: br, t: bt, b: bb) 62 + ) do 63 + al < br and ar > bl and at < bb and ab > bt 64 + end 65 + 66 + def width(rect(r: r, l: l)), do: r - l + 1 67 + def height(rect(t: t, b: b)), do: b - t + 1 68 + 69 + def to_svg(rect(l: x, t: y) = r, opts \\ []) do 70 + ~s""" 71 + <rect x="#{x}" y="#{y}" width="#{width(r)}" height="#{height(r)}" 72 + #{Enum.map_join(opts, " ", fn {k, v} -> ~s(#{k}="#{v}") end)} /> 73 + """ 74 + end 75 + end 76 + ``` 77 + 78 + ```elixir 79 + rects = 80 + Combinatorics.combinations2(tiles) 81 + |> Stream.map(fn [a, b] -> Rect.new(a, b) end) 82 + |> Enum.sort() 83 + ``` 84 + 85 + <!-- livebook:{"branch_parent_index":1} --> 86 + 87 + ## Part 1 88 + 89 + ```elixir 90 + rects 91 + |> Enum.max_by(&Rect.area/1) 92 + |> IO.inspect() 93 + |> Rect.area() 94 + ``` 95 + 96 + <!-- livebook:{"branch_parent_index":1} --> 97 + 98 + ## Part 2 99 + 100 + ```elixir 101 + edges = 102 + tiles 103 + |> Enum.chunk_every(2, 1, tiles) 104 + |> Enum.map(&apply(Rect, :new, &1)) 105 + |> Enum.sort() 106 + ``` 107 + 108 + ```elixir 109 + # [{1916, 50285}, {94619, 50285}, {94619, 48466}, {1668, 48466}] 110 + # |> Stream.flat_map(fn a -> 111 + # for b <- tiles do 112 + # Rect.new(a, b) 113 + # end 114 + # end) 115 + rects 116 + |> Enum.reduce({0, nil}, fn r, {max, p} -> 117 + a = Rect.area(r) 118 + 119 + if a > max and not Enum.any?(edges, &Rect.intersect?(r, &1)) do 120 + {a, r} 121 + else 122 + {max, p} 123 + end 124 + end) 125 + ``` 126 + 127 + ## Draw 128 + 129 + ```elixir 130 + {{min_x, _}, {max_x, _}} = Enum.min_max(tiles) 131 + ``` 132 + 133 + ```elixir 134 + {{_, min_y}, {_, max_y}} = Enum.min_max_by(tiles, &elem(&1, 1)) 135 + ``` 136 + 137 + ```elixir 138 + h = max_y + min_y 139 + w = max_x + min_x 140 + ``` 141 + 142 + ```elixir 143 + {x, y} = hd(tiles) 144 + 145 + p1 = {:rect, 16055, 14805, 85282, 83613} 146 + p2 = {:rect, 5741, 50285, 94619, 67351} 147 + 148 + svg = """ 149 + <svg xmlns="http://www.w3.org/2000/svg" width="500" viewBox="0 0 #{w} #{h}"> 150 + <rect width="100%" height="100%" fill="black" /> 151 + <path d="M#{x} #{y}#{ 152 + for {x, y} <- tl(tiles), do: "L#{x} #{y} " 153 + } L#{x} #{y}" stroke="darkgreen" fill="green" stroke-width="200" /> 154 + #{Rect.to_svg(p1, stroke: :orange, fill: :transparent, "stroke-width": 200)} 155 + #{Rect.to_svg(p2, stroke: :yellow, fill: :transparent, "stroke-width": 200)} 156 + #{ 157 + for {x, y} <- tiles do 158 + ~s(<rect x="#{x - 100}" y="#{y - 100}" width="200" height="200" fill="red" />) 159 + end 160 + } 161 + </svg> 162 + """ 163 + 164 + Kino.Image.new(svg, :svg) 165 + ``` 166 + 167 + <!-- livebook:{"offset":3329,"stamp":{"token":"XCP.SSlM8wg30CucU7IP0n8MTbPIvnvvcZRXcglo9DY17kk0O0fwtLfUUiYJauWdspkXUlp0Axl5YscQNBKK5mSycPLd0iNdz8JFPfjCg4rS2pyM3JuQj73ipXd27t8Yd0ylig","version":2}} -->
+188
2025/day10.livemd
··· 1 + # Day 10 2 + 3 + ```elixir 4 + Mix.install([:kino_aoc, {:dantzig, github: "hauleth/dantzig", ref: "use-proper-binary"}], 5 + config: [ 6 + dantzig: [ 7 + highs_binary_path: System.find_executable("highs") 8 + ] 9 + ]) 10 + ``` 11 + 12 + ## Section 13 + 14 + <!-- livebook:{"attrs":"eyJhc3NpZ25fdG8iOiJwdXp6bGVfaW5wdXQiLCJkYXkiOiIxMCIsInNlc3Npb25fc2VjcmV0IjoiQURWRU5UX09GX0NPREVfU0VTU0lPTiIsInllYXIiOiIyMDI1In0","chunks":null,"kind":"Elixir.KinoAOC.HelperCell","livebook_object":"smart_cell"} --> 15 + 16 + ```elixir 17 + {:ok, puzzle_input} = 18 + KinoAOC.download_puzzle("2025", "10", System.fetch_env!("LB_ADVENT_OF_CODE_SESSION")) 19 + ``` 20 + 21 + ```elixir 22 + defmodule Decoder do 23 + def decode("[" <> pattern), do: do_lights(String.reverse(String.trim_trailing(pattern, "]")), 0) 24 + 25 + def decode("(" <> rest) do 26 + <<seq::binary-size(byte_size(rest) - 1), ")">> = rest 27 + 28 + seq 29 + |> String.split(",") 30 + |> Enum.map(&String.to_integer/1) 31 + end 32 + 33 + def decode("{" <> rest) do 34 + <<seq::binary-size(byte_size(rest) - 1), "}">> = rest 35 + 36 + seq 37 + |> String.split(",") 38 + |> Enum.map(&String.to_integer/1) 39 + end 40 + 41 + defp do_lights("", num), do: num 42 + defp do_lights("." <> rest, num), do: do_lights(rest, 2 * num) 43 + defp do_lights("#" <> rest, num), do: do_lights(rest, 2 * num + 1) 44 + end 45 + ``` 46 + 47 + ```elixir 48 + indicators = 49 + puzzle_input 50 + |> String.split("\n", trim: true) 51 + |> Enum.map(fn raw -> 52 + [lights | rest] = 53 + raw 54 + |> String.split() 55 + |> Enum.map(&Decoder.decode/1) 56 + 57 + {buttons, [whatever]} = Enum.split(rest, -1) 58 + 59 + {lights, buttons, whatever} 60 + end) 61 + ``` 62 + 63 + <!-- livebook:{"branch_parent_index":0} --> 64 + 65 + ## Part 1 66 + 67 + 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. 68 + 69 + Thanks to that observation we can see, that order in which we press buttons doesn't matter, as $\oplus$ is reflexive, i.e.: 70 + 71 + $$ 72 + \begin{equation} 73 + a \oplus b = b \oplus a 74 + \end{equation} 75 + $$ 76 + 77 + Additionally, wrt. $\oplus$ we have identity element $0$ and inverse element is the same as an original element, i.e. 78 + 79 + $$ 80 + \begin{align} 81 + a \oplus 0 = a \\ 82 + a \oplus a = 0 83 + \end{align} 84 + $$ 85 + 86 + Additionally we can observe that: 87 + 88 + $$ 89 + \begin{equation} 90 + (a \oplus b) \oplus c = a \oplus (b \oplus c) 91 + \end{equation} 92 + $$ 93 + 94 + 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. 95 + 96 + --- 97 + 98 + So now we look for set $A = \{a_n\}$ of buttons that 99 + 100 + $$ 101 + A \in \mathcal{P}(\text{Buttons}) \\ 102 + \forall_{i, j} \; i \ne j \implies a_i \ne a_j \\ 103 + \bigoplus A = p 104 + $$ 105 + 106 + ```elixir 107 + defmodule Comb do 108 + def all_possible([]), do: [[]] 109 + def all_possible([a | rest]) do 110 + sub = all_possible(rest) 111 + 112 + sub ++ Enum.map(sub, &[a | &1]) 113 + end 114 + end 115 + ``` 116 + 117 + ```elixir 118 + indicators 119 + |> Enum.sum_by(fn {p, a, _} -> 120 + a 121 + |> Enum.map(&Enum.sum_by(&1, fn p -> 2 ** p end)) 122 + |> Comb.all_possible() 123 + |> Enum.sort_by(&length/1) 124 + |> Enum.find(fn seq -> 125 + r = Enum.reduce(seq, 0, &Bitwise.bxor/2) 126 + 127 + p == r 128 + end) 129 + |> length() 130 + end) 131 + ``` 132 + 133 + <!-- livebook:{"branch_parent_index":0} --> 134 + 135 + ## Part 2 136 + 137 + ```elixir 138 + defmodule Joltage do 139 + alias Dantzig.Polynomial 140 + alias Dantzig.Constraint 141 + alias Dantzig.Problem 142 + 143 + def solve({_pat, buttons, goal}) do 144 + p = Problem.new(direction: :minimize) 145 + 146 + {vars, {p, map}} = 147 + buttons 148 + |> Enum.with_index() 149 + |> Enum.map_reduce({p, %{}}, fn {list, idx}, {p, acc} -> 150 + {p, var} = Problem.new_variable(p, "v#{idx}", min: 0, type: :integer) 151 + 152 + acc = 153 + Enum.reduce(list, acc, fn key, map -> 154 + Map.update(map, key, [var], &[var | &1]) 155 + end) 156 + 157 + {var, {p, acc}} 158 + end) 159 + 160 + p = 161 + map 162 + |> Enum.sort() 163 + |> Enum.map(&elem(&1, 1)) 164 + |> Enum.zip(goal) 165 + |> Enum.reduce(p, fn {vars, target}, p -> 166 + poly = Polynomial.sum(vars) 167 + const = Constraint.new(poly, :==, target) 168 + 169 + Problem.add_constraint(p, const) 170 + end) 171 + 172 + p = Problem.increment_objective(p, Polynomial.sum(vars)) 173 + 174 + {:ok, s} = Dantzig.HiGHS.solve(p) 175 + 176 + Enum.sum_by(vars, &Dantzig.Solution.evaluate(s, &1)) 177 + end 178 + end 179 + ``` 180 + 181 + ```elixir 182 + indicators 183 + |> Task.async_stream(&Joltage.solve/1, ordered: false) 184 + |> Enum.sum_by(&elem(&1, 1)) 185 + |> trunc() 186 + ``` 187 + 188 + <!-- livebook:{"offset":4336,"stamp":{"token":"XCP.sfoBGIHkFvWbEHVrY-dL-QiEcwRz_ZCIjn3lQ0RFHActIOwEapmOPBt0ygbQAmrnEjYPlFm5KrcWx4LfIPbSznxxcea0fYORG9GbBBpRCm-tYbGXYTCGgqgTvOsifyWDNg","version":2}} -->
+85
2025/day11.livemd
··· 1 + # Day 11 2 + 3 + ```elixir 4 + Mix.install([:kino_aoc]) 5 + ``` 6 + 7 + ## Parse 8 + 9 + <!-- livebook:{"attrs":"eyJhc3NpZ25fdG8iOiJwdXp6bGVfaW5wdXQiLCJkYXkiOiIxMSIsInNlc3Npb25fc2VjcmV0IjoiQURWRU5UX09GX0NPREVfU0VTU0lPTiIsInllYXIiOiIyMDI1In0","chunks":null,"kind":"Elixir.KinoAOC.HelperCell","livebook_object":"smart_cell"} --> 10 + 11 + ```elixir 12 + {:ok, puzzle_input} = 13 + KinoAOC.download_puzzle("2025", "11", System.fetch_env!("LB_ADVENT_OF_CODE_SESSION")) 14 + ``` 15 + 16 + ```elixir 17 + graph = 18 + puzzle_input 19 + |> String.split("\n", trim: true) 20 + |> Map.new(fn <<from::binary-3>> <> ": " <> rest -> 21 + {from, String.split(rest)} 22 + end) 23 + ``` 24 + 25 + ## Implementation 26 + 27 + ```elixir 28 + defmodule Servers do 29 + def paths(graph, from, to), do: paths(graph, from, to, [from]) 30 + 31 + defp paths(_graph, to, to, acc), do: [Enum.reverse(acc)] 32 + 33 + defp paths(graph, from, to, acc) do 34 + if next = graph[from] do 35 + Stream.flat_map(next, &paths(graph, &1, to, [&1 | acc])) 36 + else 37 + [] 38 + end 39 + end 40 + 41 + def paths_through(graph, from, to, required), 42 + do: path_through(graph, from, to, MapSet.new(required), %{}) 43 + 44 + defp path_through(_graph, to, to, required, memo), 45 + do: {if(Enum.empty?(required), do: 1, else: 0), memo} 46 + 47 + defp path_through(graph, from, to, required, memo) do 48 + state = MapSet.delete(required, from) 49 + 50 + with :error <- Map.fetch(memo, {from, state}), 51 + {:ok, next} <- Map.fetch(graph, from) do 52 + {sum, memo} = 53 + Enum.reduce(next, {0, memo}, fn n, {sum, acc} -> 54 + {c, next_acc} = path_through(graph, n, to, state, acc) 55 + 56 + {c + sum, next_acc} 57 + end) 58 + 59 + {sum, Map.put(memo, {from, state}, sum)} 60 + else 61 + :error -> {0, memo} 62 + {:ok, val} -> {val, memo} 63 + end 64 + end 65 + end 66 + ``` 67 + 68 + <!-- livebook:{"branch_parent_index":1} --> 69 + 70 + ## Part 1 71 + 72 + ```elixir 73 + Servers.paths(graph, "you", "out") |> Enum.count() 74 + ``` 75 + 76 + <!-- livebook:{"branch_parent_index":1} --> 77 + 78 + ## Part 2 79 + 80 + ```elixir 81 + Servers.paths_through(graph, "svr", "out", ["dac", "fft"]) 82 + |> elem(0) 83 + ``` 84 + 85 + <!-- livebook:{"offset":1933,"stamp":{"token":"XCP.fzzPot48c9xb6ftw8IEb1V6uTX6csBICnoXjN1PU6c3DylXjP-bco9PgawAoc2GSeNJRzS3NCPmJ9aO9Jm2ehPXnam5fN-bZvUbEcxKmNA8SqH2_fJ5o_qp8rIaxpH6DRQ","version":2}} -->
+73
2025/day12.livemd
··· 1 + # Day 12 2 + 3 + ```elixir 4 + Mix.install([:kino_aoc]) 5 + ``` 6 + 7 + ## Parsing 8 + 9 + <!-- livebook:{"attrs":"eyJhc3NpZ25fdG8iOiJwdXp6bGVfaW5wdXQiLCJkYXkiOiIxMiIsInNlc3Npb25fc2VjcmV0IjoiQURWRU5UX09GX0NPREVfU0VTU0lPTiIsInllYXIiOiIyMDI1In0","chunks":null,"kind":"Elixir.KinoAOC.HelperCell","livebook_object":"smart_cell"} --> 10 + 11 + ```elixir 12 + {:ok, puzzle_input} = 13 + KinoAOC.download_puzzle("2025", "12", System.fetch_env!("LB_ADVENT_OF_CODE_SESSION")) 14 + ``` 15 + 16 + ```elixir 17 + {areas, boxes} = 18 + puzzle_input 19 + |> String.split("\n\n") 20 + |> List.pop_at(-1) 21 + ``` 22 + 23 + ```elixir 24 + areas = 25 + areas 26 + |> String.split("\n") 27 + |> Enum.map(fn raw -> 28 + [area | counts] = String.split(raw) 29 + 30 + area = 31 + area 32 + |> String.trim(":") 33 + |> String.split("x") 34 + |> Enum.map(&String.to_integer/1) 35 + |> Enum.product() 36 + 37 + counts = Enum.map(counts, &String.to_integer/1) 38 + 39 + {area, counts} 40 + end) 41 + ``` 42 + 43 + ```elixir 44 + boxes = 45 + boxes 46 + |> Enum.map(fn <<_::binary-3>> <> rest -> 47 + rest 48 + |> String.to_charlist() 49 + |> Enum.count(&(&1 == ?#)) 50 + end) 51 + ``` 52 + 53 + <!-- livebook:{"branch_parent_index":0} --> 54 + 55 + ## Part 1 56 + 57 + ```elixir 58 + areas 59 + |> Enum.count(fn {max, counts} -> 60 + counts 61 + |> Enum.zip_with(boxes, &*/2) 62 + |> Enum.sum() 63 + |> then(& &1 <= max) 64 + end) 65 + ``` 66 + 67 + <!-- livebook:{"branch_parent_index":0} --> 68 + 69 + ## Part 2 70 + 71 + FIN 72 + 73 + <!-- livebook:{"offset":1266,"stamp":{"token":"XCP.VAO97d30rTEs0AWIHkPD4J0fLm3S60tQ3fKoA-riReFbzMnqL1jIoxttGhvNSnCfZVNfeUBuSYVe6PrIshxVGBwjr3pjNHCyFLSb4iSPNh277lkMmh6Gtrlfr8dvsYvw0g","version":2}} -->