diff --git a/2024/README.md b/2024/README.md index 34bb3f8..7c87909 100644 --- a/2024/README.md +++ b/2024/README.md @@ -4,7 +4,7 @@ | :-: | :-: | :-: | :-: | :-: | :-: | :-: | | [1] | [2] | [3] | [4] | [5] | [6] | [7] | | [8] | [9] | [10]| [11]| [12]| [13]| [14]| -| 15 | 16 | 17 | 18 | 19 | 20 | 21 | +| [15]| 16 | 17 | 18 | 19 | 20 | 21 | | 22 | 23 | 24 | 25 | | | | [1]: ./lib/2024/1.ex @@ -21,3 +21,4 @@ [12]: ./lib/2024/12.ex [13]: ./lib/2024/13.ex [14]: ./lib/2024/14.ex +[15]: ./lib/2024/15.ex diff --git a/2024/lib/2024/15.ex b/2024/lib/2024/15.ex new file mode 100644 index 0000000..b4f71cf --- /dev/null +++ b/2024/lib/2024/15.ex @@ -0,0 +1,155 @@ +import AOC +import AOC.Prelude + +aoc 2024, 15 do + def p1(input) do + {grid, moves} = read_input(input) + + grid + |> apply_moves(moves) + |> boxes_coords() + |> Enum.map(&gps/1) + |> Enum.sum() + end + + def p2(input) do + {grid, moves} = + input + |> String.replace("#", "##") + |> String.replace("O", "[]") + |> String.replace(".", "..") + |> String.replace("@", "@.") + |> read_input() + + grid + |> apply_moves(moves) + |> boxes_coords() + |> Enum.map(&gps/1) + |> Enum.sum() + end + + ## calculations + + defp boxes_coords(grid) do + grid + |> Enum.filter(&match?({_, c} when c in ["O", "["], &1)) + |> Enum.map(&elem(&1, 0)) + end + + defp gps({x, y}), do: 100 * y + x + + ## movement + + defp apply_moves(grid, moves) do + Enum.reduce(moves, grid, fn direction, grid -> + # it would be more efficient to keep track of this and move it around but + # this is much easier to manage + robot = find_robot(grid) + {_, grid} = apply_move(grid, robot, direction) + grid + end) + end + + defp apply_move(grid, pos, direction, check_pair \\ true) do + dest = move(pos, direction) + + case {Map.get(grid, pos), direction, check_pair} do + {"#", _, _} -> + {:error, grid} + + # should never happen with the way inputs are constructed + {nil, _, _} -> + {:error, grid} + + {".", _, _} -> + {:ok, grid} + + # up and down movements of two-width boxes must be able to move both + # chars + {char, direction, true} when char in ["[", "]"] and direction in [:up, :down] -> + shift = + case char do + "[" -> :right + "]" -> :left + end + + with {:ok, grid} <- apply_move(grid, move(pos, shift), direction, false), + {:ok, grid} <- apply_move(grid, dest, direction) do + grid = + grid + |> Map.put(dest, char) + |> Map.put(pos, ".") + + {:ok, grid} + else + _ -> {:error, grid} + end + + # left and right movements of two-width boxes are no different from + # one-width boxes + {char, _, _} when char in ["@", "O", "[", "]"] -> + with {:ok, grid} <- apply_move(grid, dest, direction) do + grid = + grid + |> Map.put(dest, char) + |> Map.put(pos, ".") + + {:ok, grid} + end + end + end + + defp find_robot(grid) do + {pos, "@"} = Enum.find(grid, &match?({_, "@"}, &1)) + + pos + end + + ## traversal + + defp move({x, y}, :up), do: {x, y - 1} + defp move({x, y}, :down), do: {x, y + 1} + defp move({x, y}, :left), do: {x - 1, y} + defp move({x, y}, :right), do: {x + 1, y} + + ## input + + defp read_input(input) do + [grid_input, moves_input] = + String.split(input, "\n\n") + + grid = map_grid(grid_input) + + moves = + moves_input + |> lines() + |> Enum.join() + |> String.graphemes() + |> Enum.map(fn + "^" -> :up + "v" -> :down + "<" -> :left + ">" -> :right + end) + + {grid, moves} + end + + ## output + + # defp render(grid) do + # {{max_x, _}, _} = Enum.max_by(grid, fn {{x, _}, _} -> x end) + # {{_, max_y}, _} = Enum.max_by(grid, fn {{_, y}, _} -> y end) + + # for y <- 0..max_y do + # line = + # for x <- 0..max_x, into: "" do + # Map.get(grid, {x, y}) + # end + + # IO.puts(line) + # end + + # grid + # end +end diff --git a/README.md b/README.md index 2b318f3..32c022c 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ | [2021] | **43/50** 🌟 | Elixir | | [2022] | **14/50** 🌟 | Elixir, Haskell | | [2023] | **19/50** 🌟 | Elixir, Haskell | -| [2024] | **28/50** 🌟 | Elixir | -| **Total** | **184** 🌟| | +| [2024] | **30/50** 🌟 | Elixir | +| **Total** | **186** 🌟| | [2015]: ./2015 [2017]: ./2017