this repo has no description
1<!-- livebook:{"persist_outputs":true} --> 2 3# Day 15 4 5```elixir 6Mix.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 34points = 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 50circles = 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 94defmodule 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)] 196end 197``` 198 199<!-- livebook:{"output":true} --> 200 201``` 202{:module, RangeSet, <<70, 79, 82, 49, 0, 0, 37, ...>>, {:squash, 1}} 203``` 204 205```elixir 206RangeSet.new([6..10, -8..26]) 207``` 208 209<!-- livebook:{"output":true} --> 210 211``` 212%RangeSet{ranges: [-8..26]} 213``` 214 215Our 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$$ 218r \ge d(C, a) 219$$ 220 221where $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$$ 224d(a, b) = |a_x - b_x| + |a_y - b_y| 225$$ 226 227So after substitution at the above inequality we get: 228 229$$ 230r \ge |C_x - a_x| + |C_y - a_y| 231$$ 232 233In 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 235That 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 249defmodule 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 256end 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 268a_y = 2_000_000 269 270beacons_at_line = 271 Enum.flat_map(points, fn %{beacon: {x, y}} -> if y == a_y, do: [x], else: [] end) 272 273circles 274|> Beacons.covered_at_line(a_y) 275|> RangeSet.remove(beacons_at_line) 276|> RangeSet.length() 277``` 278 279<!-- livebook:{"output":true} --> 280 281``` 2825108096 283``` 284 285## Task 2 286 287```elixir 288range = 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 303x * 4_000_000 + y 304``` 305 306<!-- livebook:{"output":true} --> 307 308``` 30910553942650264 310```