diff --git a/README.md b/README.md index cabbeb0..1ee0907 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ My Elixir solutions for [Advent of Code](https://adventofcode.com/) (all years). -
diff --git a/lib/y2023/README.md b/lib/y2023/README.md
index e7c9378..76f007c 100644
--- a/lib/y2023/README.md
+++ b/lib/y2023/README.md
@@ -2,7 +2,7 @@
My Elixir solutions for [Advent of Code 2023](https://adventofcode.com/2023).
-
+
## Benchmarks
@@ -47,5 +47,6 @@ day 15, part 1 1.97 K 0.51 ms ±58.77% 0.47 ms 1.
day 15, part 2 0.39 K 2.57 ms ±9.13% 2.56 ms 3.05 ms
day 16, part 1 97.00 10.31 ms ±14.20% 10.08 ms 13.65 ms
day 16, part 2 2.07 483.12 ms ±2.51% 482.96 ms 503.06 ms
-day 17, part 1 1.42 704.79 ms ±2.71% 713.38 ms 719.67 ms
+day 17, part 1 1.28 782.35 ms ±3.06% 789.96 ms 811.18 ms
+day 17, part 2 0.30 3345.17 ms ±2.14% 3345.17 ms 3395.69 ms
```
diff --git a/lib/y2023/day17.ex b/lib/y2023/day17.ex
index 1e94cb8..ec9f194 100644
--- a/lib/y2023/day17.ex
+++ b/lib/y2023/day17.ex
@@ -4,21 +4,18 @@ defmodule Y2023.Day17 do
alias Advent.Grid
def part1(grid) do
- find_best_path(grid, {1, 1})
+ find_best_path(grid, {1, 1}, 0..3)
end
- # @doc """
- # iex> Day17.part2("update or delete me")
- # "update or delete me"
- # """
- # def part2(input) do
- # input
- # end
+ def part2(grid) do
+ find_best_path(grid, {1, 1}, 4..10)
+ end
- defp find_best_path(grid, {row, col}) do
+ defp find_best_path(grid, {row, col}, length_range) do
PriorityQueue.new()
- |> add_to_queue([[{row, col, nil, 0}]])
- |> search(grid, Grid.size(grid), MapSet.new())
+ |> add_to_queue([[{row, col, :down, 0}]])
+ |> add_to_queue([[{row, col, :right, 0}]])
+ |> search(grid, Grid.size(grid), MapSet.new(), length_range)
end
defp add_to_queue(queue, states) do
@@ -27,42 +24,42 @@ defmodule Y2023.Day17 do
end)
end
- defp search(queue, grid, destination, cache) do
- do_search(PriorityQueue.pop(queue), grid, destination, cache)
+ defp search(queue, grid, destination, cache, length_range) do
+ do_search(PriorityQueue.pop(queue), grid, destination, cache, length_range)
end
- defp do_search({:empty, _queue}, _grid, _destination, _cache) do
+ defp do_search({:empty, _queue}, _grid, _destination, _cache, _straight_line_length) do
raise("No path found")
end
- defp do_search(
- {{:value, [{row, col, _direction, count} | _rest]}, _queue},
- _grid,
- {row, col},
- _cache
- ) do
- # Winner winner chicken dinner.
- count
- end
+ defp do_search({{:value, path}, queue}, grid, destination, cache, length_range) do
+ {row, col, _, count} = hd(path)
- defp do_search({{:value, path}, queue}, grid, destination, cache) do
- if MapSet.member?(cache, cache_key(path)) do
- # Been here before at a lower heat loss value, ignore
- search(queue, grid, destination, cache)
+ if {row, col} == destination && passes_final_straight_check?(path, length_range) do
+ # Winner winner chicken dinner.
+ # Grid.display(grid, Enum.map(path, fn {row, col, _, _} -> {row, col} end))
+ count
else
- cache = MapSet.put(cache, cache_key(path))
-
- queue
- |> add_to_queue(valid_moves(path, grid))
- |> search(grid, destination, cache)
+ cache_key = cache_key(path)
+
+ if MapSet.member?(cache, cache_key) do
+ # Been here before at a lower heat loss value, ignore
+ search(queue, grid, destination, cache, length_range)
+ else
+ cache = MapSet.put(cache, cache_key)
+
+ queue
+ |> add_to_queue(valid_moves(path, grid, length_range))
+ |> search(grid, destination, cache, length_range)
+ end
end
end
- defp valid_moves([{row, col, direction, value} | _rest] = path, grid) do
+ defp valid_moves([{row, col, direction, value} | _rest] = path, grid, length_range) do
[{row - 1, col, :up}, {row + 1, col, :down}, {row, col - 1, :left}, {row, col + 1, :right}]
|> Enum.filter(&in_grid?(grid, &1))
|> Enum.reject(&backwards?(direction, &1))
- |> Enum.reject(&too_far_straight?(path, &1))
+ |> Enum.filter(&passes_straight_check?(path, &1, length_range))
|> Enum.map(fn {row, col, dir} ->
[{row, col, dir, value + Map.fetch!(grid, {row, col})} | path]
end)
@@ -84,12 +81,34 @@ defmodule Y2023.Day17 do
{from, to} in [{:left, :right}, {:right, :left}, {:up, :down}, {:down, :up}]
end
- defp too_far_straight?([{_, _, dir, _}, {_, _, dir, _}, {_, _, dir, _} | _rest], {_, _, dir}) do
- true
+ defp passes_straight_check?(
+ [{_, _, from_dir, _} | _rest] = path,
+ {_, _, to_dir},
+ length_range
+ ) do
+ _min..max = length_range
+
+ straight = straight_line_length(path)
+
+ cond do
+ from_dir == to_dir -> straight < max
+ from_dir != to_dir -> straight in length_range
+ end
+ end
+
+ defp passes_final_straight_check?(path, length_range) do
+ straight_line_length(path) in length_range
end
- defp too_far_straight?(_, _), do: false
+ defp straight_line_length(path) do
+ {_, _, direction, _} = hd(path)
+
+ path
+ |> Enum.take_while(fn {_, _, dir, _} -> dir == direction end)
+ |> length
+ end
+ @spec parse_input(binary()) :: any()
def parse_input(input) do
input
|> Grid.new()
@@ -98,5 +117,5 @@ defmodule Y2023.Day17 do
end
def part1_verify, do: input() |> parse_input() |> part1()
- # def part2_verify, do: input() |> parse_input() |> part2()
+ def part2_verify, do: input() |> parse_input() |> part2()
end
diff --git a/test/y2023/day17_test.exs b/test/y2023/day17_test.exs
index c06ab96..d47e1cf 100644
--- a/test/y2023/day17_test.exs
+++ b/test/y2023/day17_test.exs
@@ -4,7 +4,7 @@ defmodule Y2023.Day17Test do
doctest Day17
test "verification, part 1", do: assert(Day17.part1_verify() == 886)
- # test "verification, part 2", do: assert(Day17.part2_verify() == "update or delete me")
+ test "verification, part 2", do: assert(Day17.part2_verify() == 1055)
@sample_input """
2413432311323
@@ -26,4 +26,24 @@ defmodule Y2023.Day17Test do
actual = Day17.parse_input(@sample_input) |> Day17.part1()
assert 102 == actual
end
+
+ test "part 2, sample 1" do
+ actual = Day17.parse_input(@sample_input) |> Day17.part2()
+ assert 94 == actual
+ end
+
+ test "part 2, sample 2" do
+ actual =
+ """
+ 111111111111
+ 999999999991
+ 999999999991
+ 999999999991
+ 999999999991
+ """
+ |> Day17.parse_input()
+ |> Day17.part2()
+
+ assert 71 == actual
+ end
end