diff --git a/day20/main.exs b/day20/main.exs new file mode 100644 index 0000000..3d99c3d --- /dev/null +++ b/day20/main.exs @@ -0,0 +1,112 @@ +defmodule Day20 do + def run do + {map, width, height, start, dest} = parse_input("data.txt") + + # create a map of visited nodes an a queue + visited = %{start => 100_000_000} + q = :queue.from_list([{start, 0, []}]) + + {path, shortest} = walk(map, q, dest, visited, [], 100_000_000_000, width, height) + path = Enum.reverse(path) + + part1 = run(2, path) + part2 = run(20, path) + dbg({part1, part2}) + end + + # walk every step along the path + # find coordinates that we can get to within number of steps, that save us 100 picoseconds + defp run(cheats, path) do + Enum.with_index(path) + |> Enum.reduce(0, fn {el, idx}, acc -> + res = + path + |> Enum.with_index() + |> Enum.filter(fn {el2, idx2} -> + dist = distance(el, el2) + dist <= cheats and idx + dist + 100 <= idx2 + end) + |> length + + acc + res + end) + end + + defp distance({x1, y1}, {x2, y2}) do + abs(x2 - x1) + abs(y2 - y1) + end + + defp walk(map, q, {dx, dy} = dest, visited, paths, shortest_path, width, height) do + # dequeue the node with the lowest distnce + # no priority queue, so just find the lowest one and delete it from the queue + ql = :queue.to_list(q) + + if length(ql) == 0 do + nil + else + # lowest = next_from_queue(ql, dest) + lowest = Enum.min_by(ql, fn {_, dist, _} -> dist end) + {{x, y}, dist, path} = lowest + new_q = :queue.delete(lowest, q) + + # if we reached the destination + if x == dx and y == dy do + {[{x, y} | path], dist} + else + dirs = [{1, 0}, {0, 1}, {-1, 0}, {0, -1}] + + # find neighbors to enqueue + neighbors = + dirs + |> Enum.map(fn {dx, dy} -> {x + dx, y + dy} end) + |> Enum.map(fn key -> {key, Map.get(map, key, ".")} end) + |> Enum.filter(fn {{x, y}, el} -> + el != "#" and x >= 0 and y >= 0 and x < width and y < height + end) + |> Enum.reject(fn {{nx, ny}, _el} -> Map.has_key?(visited, {nx, ny}) end) + |> Enum.reject(fn {{nx, ny}, _el} -> + Enum.any?(:queue.to_list(q), fn {pos, _, _} -> pos == {nx, ny} end) + end) + |> Enum.map(fn {key, _el} -> key end) + + new_q2 = + Enum.reduce(neighbors, new_q, fn {nx, ny}, acc -> + :queue.in({{nx, ny}, dist + 1, [{x, y} | path]}, acc) + end) + + new_visited = Map.put(visited, {x, y}, dist) + walk(map, new_q2, dest, new_visited, paths, shortest_path, width, height) + end + end + end + + defp parse_input(file) do + map_list = + File.read!(file) + |> String.trim() + |> String.split("\n") + |> Enum.map(&String.graphemes/1) + + height = length(map_list) + width = length(hd(map_list)) + + # convert the map List to a map Map, grabbing start and dest along the way + {map, start, dest} = + Enum.concat(map_list) + |> Enum.with_index() + |> Enum.reduce({%{}, nil, nil}, fn {el, idx}, {map, start, dest} -> + row = div(idx, height) + col = rem(idx, width) + + map = Map.put(map, {row, col}, el) + start = if el == "S", do: {row, col}, else: start + dest = if el == "E", do: {row, col}, else: dest + + {map, start, dest} + end) + + {map, width, height, start, dest} + end +end + +Day20.run()