Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Advent of Code 2024

2024/day20.livemd

Advent of Code 2024

aoc_helpers_path =
  __ENV__.file
  |> String.split("#")
  |> hd()
  |> Path.dirname()
  |> then(fn dir ->
    [dir, "..", "aoc_helpers"]
  end)
  |> Path.join()

Mix.install([
  {:aoc_helpers, path: aoc_helpers_path}
])

Day 20

import AocHelpers

Kino.configure(inspect: [charlists: :as_lists])

input = download_puzzle(2024, 20, cookie: System.get_env("AOC_COOKIE"))

sample = """
###############
#...#...#.....#
#.#.#.#.#.###.#
#S#...#.#.#...#
#######.#.#.###
#######.#.#...#
#######.#.###.#
###..E#...#...#
###.#######.###
#...###...#...#
#.#####.#.###.#
#.#...#.#.#...#
#.#.#.#.#.#.###
#...#...#...###
###############
"""

Kino.nothing()
defmodule Day20 do
  def parse(input) do
    input
    |> grid()
  end

  def find_path(grid) do
    s = find_pos(grid, "S")

    find_path(grid, s, MapSet.new(), [])
  end

  def find_path(grid, pos, seen, path) do
    path = [pos | path]
    seen = MapSet.put(seen, pos)

    if Map.get(grid, pos) == "E" do
      path
    else
      next =
        neighbours(pos)
        |> Enum.filter(&(Map.get(grid, &1) in ["E", "."] && !MapSet.member?(seen, &1)))
        |> hd()

      find_path(grid, next, seen, path)
    end
  end

  def best_cheats(input, t, cutoff) do
    {grid, _w, _h} =
      input
      |> parse()

    # there is only one path
    path = find_path(grid)
    n = (Enum.count(path) - 1)

    # steps from a given cell to the end
    # ie. we can cheat and know how many steps are left without taking them
    memo =
      path
      |> Enum.with_index()
      |> Enum.into(Map.new())

    path
    |> Enum.flat_map(fn start ->
      start
      |> neighbours(t)
      |> Enum.filter(&Map.has_key?(memo, &1))
      |> Enum.map(&{start, &1, manhattan_dist(start, &1)})
    end)
    |> Enum.map(fn {a, b, d} ->
      t = n - Map.get(memo, a) + Map.get(memo, b) + d
      n - t
    end)
    |> Enum.filter(&(&1 >= cutoff))
    # |> Enum.frequencies()
    # |> Enum.sort_by(fn {k, _v} -> k end)
    |> Enum.count()
  end
end
import Day20

Part 1

input
|> best_cheats(2, 100)

Part 2

input
|> best_cheats(20, 100)