this repo has no description
at master 9.5 kB view raw
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"},"chunks":null,"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 continuous?(%__MODULE__{ranges: []}), do: true 112 def continuous?(%__MODULE__{ranges: [_]}), do: true 113 def continuous?(%__MODULE__{ranges: _}), do: false 114 115 def gaps(%__MODULE__{ranges: []} = set), do: set 116 def gaps(%__MODULE__{ranges: [_]}), do: %__MODULE__{ranges: []} 117 118 def gaps(%__MODULE__{ranges: ranges}) do 119 gaps = 120 ranges 121 |> Enum.chunk_every(2, 1, :discard) 122 |> Enum.flat_map(fn [_..a, b.._] -> [(a + 1)..(b - 1)] end) 123 |> squash() 124 125 %__MODULE__{ranges: gaps} 126 end 127 128 def empty?(%__MODULE__{ranges: ranges}), do: ranges == [] 129 130 def has?(%__MODULE__{ranges: ranges}, value), do: Enum.any?(ranges, &(value in &1)) 131 132 def length(%__MODULE__{ranges: ranges}), 133 do: Enum.reduce(ranges, 0, fn r, acc -> acc + Range.size(r) end) 134 135 def to_list(%__MODULE__{ranges: ranges}), 136 do: Enum.flat_map(ranges, & &1) 137 138 def remove(%__MODULE__{ranges: ranges}, int) when is_integer(int) do 139 new_ranges = 140 Enum.flat_map(ranges, fn a..b -> 141 cond do 142 a == int -> [(int + 1)..b] 143 b == int -> [a..(int - 1)] 144 int in a..b -> [a..(int - 1), (int + 1)..b] 145 true -> [a..b] 146 end 147 end) 148 149 %__MODULE__{ranges: new_ranges} 150 end 151 152 def remove(%__MODULE__{ranges: ranges}, c..d//1) when c <= d do 153 new_ranges = 154 Enum.flat_map(ranges, fn a..b -> 155 cond do 156 a in c..d and b in c..d -> [] 157 a < c and b in c..d -> [a..(c - 1)] 158 a in c..d and b > d -> [(d + 1)..b] 159 c in a..b and d in a..b -> [a..(c - 1), (d + 1)..b] 160 true -> [a..b] 161 end 162 end) 163 164 %__MODULE__{ranges: new_ranges} 165 end 166 167 def remove(%__MODULE__{} = set, %__MODULE__{ranges: ranges}), 168 do: Enum.reduce(ranges, set, &remove(&2, &1)) 169 170 def remove(%__MODULE__{} = set, list), 171 do: Enum.reduce(list, set, &remove(&2, &1)) 172 173 def add(set, int) when is_integer(int), do: add(set, int..int//1) 174 175 def add(set, a..b//-1), do: add(set, b..a//1) 176 177 def add(%__MODULE__{} = set, a..b//1) when a > b, do: set 178 179 def add(%__MODULE__{ranges: ranges}, a..b//1 = range) when a <= b do 180 ranges = 181 ranges 182 |> insert_sorted(range) 183 |> squash() 184 185 %__MODULE__{ranges: ranges} 186 end 187 188 defp insert_sorted([], val), do: [val] 189 190 defp insert_sorted([a.._ = x | rest], b.._ = y) when a < b, 191 do: [x | insert_sorted(rest, y)] 192 193 defp insert_sorted(rest, y), 194 do: [y | rest] 195 196 defp squash([]), do: [] 197 defp squash([_] = list), do: list 198 199 defp squash([a..b, c..d | rest]) when b >= c - 1, 200 do: squash([a..max(b, d) | rest]) 201 202 defp squash([x | rest]), do: [x | squash(rest)] 203end 204``` 205 206<!-- livebook:{"output":true} --> 207 208``` 209{:module, RangeSet, <<70, 79, 82, 49, 0, 0, 39, ...>>, {:squash, 1}} 210``` 211 212```elixir 213RangeSet.new([6..10, -8..26]) 214``` 215 216<!-- livebook:{"output":true} --> 217 218``` 219%RangeSet{ranges: [-8..26]} 220``` 221 222Our 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 223 224$$ 225r \ge d(C, a) 226$$ 227 228where $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: 229 230$$ 231d(a, b) = |a_x - b_x| + |a_y - b_y| 232$$ 233 234So after substitution at the above inequality we get: 235 236$$ 237r \ge |C_x - a_x| + |C_y - a_y| 238$$ 239 240In 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. 241 242That gives us: 243 244$$ 245|a_x - C_x| \le r - d_y 246$$ 247 248$$ 249\begin{align*} 250\therefore&\ -r + d_y &\le&\ a_x - C_x &\le&\ r - d_y \\ 251\therefore&\ -r + d_y + C_x &\le&\ a_x &\le&\ r - d_y + C_x 252\end{align*} 253$$ 254 255```elixir 256defmodule Beacons do 257 def covered_at_line(circles, a_y) do 258 Enum.reduce(circles, RangeSet.new(), fn {{c_x, c_y}, r}, set -> 259 d_y = abs(c_y - a_y) 260 RangeSet.add(set, (-r + d_y + c_x)..(r - d_y + c_x)//1) 261 end) 262 end 263end 264``` 265 266<!-- livebook:{"output":true} --> 267 268``` 269{:module, Beacons, <<70, 79, 82, 49, 0, 0, 9, ...>>, {:covered_at_line, 2}} 270``` 271 272## Task 1 273 274```elixir 275a_y = 2_000_000 276 277beacons_at_line = 278 Enum.flat_map(points, fn %{beacon: {x, y}} -> if y == a_y, do: [x], else: [] end) 279 280circles 281|> Beacons.covered_at_line(a_y) 282|> IO.inspect() 283|> RangeSet.remove(beacons_at_line) 284|> RangeSet.length() 285``` 286 287<!-- livebook:{"output":true} --> 288 289``` 290%RangeSet{ranges: [-101830..5006266]} 291``` 292 293<!-- livebook:{"output":true} --> 294 295``` 2965108096 297``` 298 299## Task 2 300 301```elixir 302range = 4_000_000..0//-1 303# range = 0..4_000_000 304 305{y, [x]} = 306 range 307 |> Enum.find_value(fn y -> 308 covered = Beacons.covered_at_line(circles, y) 309 310 if not RangeSet.continuous?(covered) do 311 gaps = RangeSet.gaps(covered) 312 {y, RangeSet.to_list(gaps)} 313 end 314 end) 315 |> Kino.render() 316 317x * 4_000_000 + y 318``` 319 320<!-- livebook:{"output":true} --> 321 322``` 323{2650264, [2638485]} 324``` 325 326<!-- livebook:{"output":true} --> 327 328``` 32910553942650264 330```