this repo has no description

ft: add day 15.2022

+310
+310
2022/day15.livemd
··· 1 + <!-- livebook:{"persist_outputs":true} --> 2 + 3 + # Day 15 4 + 5 + ```elixir 6 + Mix.install([ 7 + {:kino_aoc, git: "https://github.com/ljgago/kino_aoc"} 8 + ]) 9 + ``` 10 + 11 + <!-- livebook:{"output":true} --> 12 + 13 + ``` 14 + :ok 15 + ``` 16 + 17 + ## Section 18 + 19 + <!-- livebook:{"attrs":{"day":"15","session_secret":"ADVENT_OF_CODE_SESSION","variable":"puzzle_input","year":"2022"},"kind":"Elixir.KinoAOC.HelperCell","livebook_object":"smart_cell"} --> 20 + 21 + ```elixir 22 + {:ok, puzzle_input} = 23 + KinoAOC.download_puzzle("2022", "15", System.fetch_env!("LB_ADVENT_OF_CODE_SESSION")) 24 + ``` 25 + 26 + <!-- livebook:{"output":true} --> 27 + 28 + ``` 29 + {:ok, 30 + "Sensor at x=2288642, y=2282562: closest beacon is at x=1581951, y=2271709\nSensor at x=2215505, y=2975419: closest beacon is at x=2229474, y=3709584\nSensor at x=275497, y=3166843: closest beacon is at x=-626874, y=3143870\nSensor at x=1189444, y=2115305: closest beacon is at x=1581951, y=2271709\nSensor at x=172215, y=2327851: closest beacon is at x=-101830, y=2000000\nSensor at x=3953907, y=1957660: closest beacon is at x=2882446, y=1934422\nSensor at x=685737, y=2465261: closest beacon is at x=1581951, y=2271709\nSensor at x=1458348, y=2739442: closest beacon is at x=1581951, y=2271709\nSensor at x=3742876, y=2811554: closest beacon is at x=3133845, y=3162635\nSensor at x=437819, y=638526: closest beacon is at x=-101830, y=2000000\nSensor at x=2537979, y=1762726: closest beacon is at x=2882446, y=1934422\nSensor at x=1368739, y=2222863: closest beacon is at x=1581951, y=2271709\nSensor at x=2743572, y=3976937: closest beacon is at x=2229474, y=3709584\nSensor at x=2180640, y=105414: closest beacon is at x=3011118, y=-101788\nSensor at x=3845753, y=474814: closest beacon is at x=3011118, y=-101788\nSensor at x=2493694, y=3828087: closest beacon is at x=2229474, y=3709584\nSensor at x=2786014, y=3388077: closest beacon is at x=3133845, y=3162635\nSensor at x=3593418, y=3761871: closest beacon is at x=3133845, y=3162635\nSensor at x=856288, y=3880566: closest beacon is at x=2229474, y=3709584\nSensor at x=1757086, y=2518373: closest beacon is at x=1581951, y=2271709\nSensor at x=2853518, y=2939097: closest beacon is at x=3133845, y=3162635\nSensor at x=1682023, y=1449902: closest beacon is at x=1581951, y=2271709\nSensor at x=3360575, y=1739100: closest beacon is at x=2882446, y=1934422\nSensor at x=2904259, y=1465606: closest beacon is at x=2882446, y=1934422\nSensor at x=3078500, y=3564862: closest beacon is at x=3133845, y=3162635\nSensor at x=2835288, y=1011055: closest beacon is at x=2882446, y=1934422\nSensor at x=2998762, y=2414323: closest beacon is at x=2882446, y=1934422\n"} 31 + ``` 32 + 33 + ```elixir 34 + points = 35 + puzzle_input 36 + |> String.split("\n", trim: true) 37 + |> Enum.map(fn line -> 38 + %{"sx" => sx, "sy" => sy, "bx" => bx, "by" => by} = 39 + Regex.named_captures( 40 + ~r/x=(?<sx>-?\d+), y=(?<sy>-?\d+):.*x=(?<bx>-?\d+), y=(?<by>-?\d+)/, 41 + line 42 + ) 43 + 44 + %{ 45 + sensor: {String.to_integer(sx), String.to_integer(sy)}, 46 + beacon: {String.to_integer(bx), String.to_integer(by)} 47 + } 48 + end) 49 + 50 + circles = 51 + points 52 + |> Enum.map(fn %{beacon: {bx, by}, sensor: {sx, sy}} -> 53 + radius = abs(bx - sx) + abs(by - sy) 54 + 55 + {{sx, sy}, radius} 56 + end) 57 + ``` 58 + 59 + <!-- livebook:{"output":true} --> 60 + 61 + ``` 62 + [ 63 + {{2288642, 2282562}, 717544}, 64 + {{2215505, 2975419}, 748134}, 65 + {{275497, 3166843}, 925344}, 66 + {{1189444, 2115305}, 548911}, 67 + {{172215, 2327851}, 601896}, 68 + {{3953907, 1957660}, 1094699}, 69 + {{685737, 2465261}, 1089766}, 70 + {{1458348, 2739442}, 591336}, 71 + {{3742876, 2811554}, 960112}, 72 + {{437819, 638526}, 1901123}, 73 + {{2537979, 1762726}, 516163}, 74 + {{1368739, 2222863}, 262058}, 75 + {{2743572, 3976937}, 781451}, 76 + {{2180640, 105414}, 1037680}, 77 + {{3845753, 474814}, 1411237}, 78 + {{2493694, 3828087}, 382723}, 79 + {{2786014, 3388077}, 573273}, 80 + {{3593418, 3761871}, 1058809}, 81 + {{856288, 3880566}, 1544168}, 82 + {{1757086, 2518373}, 421799}, 83 + {{2853518, 2939097}, 503865}, 84 + {{1682023, 1449902}, 921879}, 85 + {{3360575, 1739100}, 673451}, 86 + {{2904259, 1465606}, 490629}, 87 + {{3078500, 3564862}, 457572}, 88 + {{2835288, 1011055}, 970525}, 89 + {{2998762, 2414323}, 596217} 90 + ] 91 + ``` 92 + 93 + ```elixir 94 + defmodule RangeSet do 95 + defstruct ranges: [] 96 + 97 + def new(), do: %__MODULE__{} 98 + def new(%__MODULE__{} = set), do: set 99 + def new(a..b//1) when a <= b, do: %__MODULE__{ranges: [a..b//1]} 100 + def new(b..a//-1) when b > a, do: %__MODULE__{ranges: [a..b//1]} 101 + def new(list), do: %__MODULE__{ranges: list |> Enum.sort_by(& &1.first) |> squash()} 102 + 103 + def min(%__MODULE__{ranges: [a.._ | _]}), do: a 104 + 105 + def max(%__MODULE__{ranges: ranges}) do 106 + _..a = List.last(ranges) 107 + 108 + a 109 + end 110 + 111 + def gaps(%__MODULE__{ranges: ranges}) do 112 + gaps = 113 + ranges 114 + |> Enum.chunk_every(2, 1, :discard) 115 + |> Enum.flat_map(fn [_..a, b.._] -> [(a + 1)..(b - 1)] end) 116 + |> squash() 117 + 118 + %__MODULE__{ranges: gaps} 119 + end 120 + 121 + def empty?(%__MODULE__{ranges: ranges}), do: ranges == [] 122 + 123 + def has?(%__MODULE__{ranges: ranges}, value), do: Enum.any?(ranges, &(value in &1)) 124 + 125 + def length(%__MODULE__{ranges: ranges}), 126 + do: Enum.reduce(ranges, 0, fn r, acc -> acc + Range.size(r) end) 127 + 128 + def to_list(%__MODULE__{ranges: ranges}), 129 + do: Enum.flat_map(ranges, & &1) 130 + 131 + def remove(%__MODULE__{ranges: ranges}, int) when is_integer(int) do 132 + new_ranges = 133 + Enum.flat_map(ranges, fn a..b -> 134 + cond do 135 + a == int -> [(int + 1)..b] 136 + b == int -> [a..(int - 1)] 137 + int in a..b -> [a..(int - 1), (int + 1)..b] 138 + true -> [a..b] 139 + end 140 + end) 141 + 142 + %__MODULE__{ranges: new_ranges} 143 + end 144 + 145 + def remove(%__MODULE__{ranges: ranges}, c..d//1) when c <= d do 146 + new_ranges = 147 + Enum.flat_map(ranges, fn a..b -> 148 + cond do 149 + a in c..d and b in c..d -> [] 150 + a < c and b in c..d -> [a..(c - 1)] 151 + a in c..d and b > d -> [(d + 1)..b] 152 + c in a..b and d in a..b -> [a..(c - 1), (d + 1)..b] 153 + true -> [a..b] 154 + end 155 + end) 156 + 157 + %__MODULE__{ranges: new_ranges} 158 + end 159 + 160 + def remove(%__MODULE__{} = set, %__MODULE__{ranges: ranges}), 161 + do: Enum.reduce(ranges, set, &remove(&2, &1)) 162 + 163 + def remove(%__MODULE__{} = set, list), 164 + do: Enum.reduce(list, set, &remove(&2, &1)) 165 + 166 + def add(set, int) when is_integer(int), do: add(set, int..int//1) 167 + 168 + def add(set, a..b//-1), do: add(set, b..a//1) 169 + 170 + def add(%__MODULE__{} = set, a..b//1) when a > b, do: set 171 + 172 + def add(%__MODULE__{ranges: ranges}, a..b//1 = range) when a <= b do 173 + ranges = 174 + ranges 175 + |> insert_sorted(range) 176 + |> squash() 177 + 178 + %__MODULE__{ranges: ranges} 179 + end 180 + 181 + defp insert_sorted([], val), do: [val] 182 + 183 + defp insert_sorted([a.._ = x | rest], b.._ = y) when a < b, 184 + do: [x | insert_sorted(rest, y)] 185 + 186 + defp insert_sorted(rest, y), 187 + do: [y | rest] 188 + 189 + defp squash([]), do: [] 190 + defp squash([_] = list), do: list 191 + 192 + defp squash([a..b, c..d | rest]) when b >= c - 1, 193 + do: squash([a..max(b, d) | rest]) 194 + 195 + defp squash([x | rest]), do: [x | squash(rest)] 196 + end 197 + ``` 198 + 199 + <!-- livebook:{"output":true} --> 200 + 201 + ``` 202 + {:module, RangeSet, <<70, 79, 82, 49, 0, 0, 37, ...>>, {:squash, 1}} 203 + ``` 204 + 205 + ```elixir 206 + RangeSet.new([6..10, -8..26]) 207 + ``` 208 + 209 + <!-- livebook:{"output":true} --> 210 + 211 + ``` 212 + %RangeSet{ranges: [-8..26]} 213 + ``` 214 + 215 + Our list can be change into list of radii of the circles (in Taxicab metric these will be visually squares), so then using inequality for circle 216 + 217 + $$ 218 + r \ge d(C, a) 219 + $$ 220 + 221 + where $d(a, b)$ is distance function for given metric, we can compute possible ranges of values at given line in constant time. In Taxicab metric $d$ is defined as: 222 + 223 + $$ 224 + d(a, b) = |a_x - b_x| + |a_y - b_y| 225 + $$ 226 + 227 + So after substitution at the above inequality we get: 228 + 229 + $$ 230 + r \ge |C_x - a_x| + |C_y - a_y| 231 + $$ 232 + 233 + In the task $a_y = const$ and $C = const$, so we need to solve only for $a_x$. So we can define $d_y = |C_y - a_y| = const$ as well. 234 + 235 + That gives us: 236 + 237 + $$ 238 + |a_x - C_x| \le r - d_y 239 + $$ 240 + 241 + $$ 242 + \begin{align*} 243 + \therefore&\ -r + d_y &\le&\ a_x - C_x &\le&\ r - d_y \\ 244 + \therefore&\ -r + d_y + C_x &\le&\ a_x &\le&\ r - d_y + C_x 245 + \end{align*} 246 + $$ 247 + 248 + ```elixir 249 + defmodule Beacons do 250 + def covered_at_line(circles, a_y) do 251 + Enum.reduce(circles, RangeSet.new(), fn {{c_x, c_y}, r}, set -> 252 + d_y = abs(c_y - a_y) 253 + RangeSet.add(set, (-r + d_y + c_x)..(r - d_y + c_x)//1) 254 + end) 255 + end 256 + end 257 + ``` 258 + 259 + <!-- livebook:{"output":true} --> 260 + 261 + ``` 262 + {:module, Beacons, <<70, 79, 82, 49, 0, 0, 9, ...>>, {:covered_at_line, 2}} 263 + ``` 264 + 265 + ## Task 1 266 + 267 + ```elixir 268 + a_y = 2_000_000 269 + 270 + beacons_at_line = 271 + Enum.flat_map(points, fn %{beacon: {x, y}} -> if y == a_y, do: [x], else: [] end) 272 + 273 + circles 274 + |> Beacons.covered_at_line(a_y) 275 + |> RangeSet.remove(beacons_at_line) 276 + |> RangeSet.length() 277 + ``` 278 + 279 + <!-- livebook:{"output":true} --> 280 + 281 + ``` 282 + 5108096 283 + ``` 284 + 285 + ## Task 2 286 + 287 + ```elixir 288 + range = 4_000_000..0//-1 289 + # range = 0..4_000_000 290 + 291 + {y, [x]} = 292 + range 293 + |> Enum.find_value(fn y -> 294 + covered = Beacons.covered_at_line(circles, y) 295 + 296 + gaps = RangeSet.gaps(covered) 297 + 298 + if not RangeSet.empty?(gaps) do 299 + {y, RangeSet.to_list(gaps)} 300 + end 301 + end) 302 + 303 + x * 4_000_000 + y 304 + ``` 305 + 306 + <!-- livebook:{"output":true} --> 307 + 308 + ``` 309 + 10553942650264 310 + ```