Advent of Code 2024 - Day 20
Mix.install([
:kino_aoc
])
Introduction
2024 - Day 20
Puzzle
{:ok, puzzle_input} =
KinoAOC.download_puzzle("2024", "20", System.fetch_env!("LB_AOC_SESSION"))
Parser
Code - Parser
defmodule Parser do
def parse(input) do
input
|> String.split("\n", trim: true)
|> Enum.with_index()
|> Enum.flat_map(fn {line, y} ->
line
|> String.graphemes()
|> Enum.with_index()
|> Enum.filter(fn {char, _} -> char != "#" end)
|> Enum.map(fn {char, x} -> {{x, y}, char} end)
end)
|> Map.new()
end
end
Tests - Parser
ExUnit.start(autorun: false)
defmodule ParserTest do
use ExUnit.Case, async: true
import Parser
@input """
#####
#S.E#
#####
"""
@expected %{{1, 1} => "S", {2, 1} => ".", {3, 1} => "E"}
test "parse test" do
actual = parse(@input)
assert actual == @expected
end
end
ExUnit.run()
Shared
defmodule Shared do
def cheat([], _, _), do: 0
def cheat([{{sx, sy}, from} | others], cheat_time, at_least_save) do
found_cheat =
others
|> Enum.filter(fn {{dx, dy}, to} ->
manhattan_dist = abs(sx - dx) + abs(sy - dy)
default_dist = to - from
if manhattan_dist > cheat_time or default_dist <= cheat_time do
false
else
time_saved = default_dist - manhattan_dist
time_saved >= at_least_save
end
end)
|> Enum.count()
found_cheat + cheat(others, cheat_time, at_least_save)
end
def path(map) do
{start, _} = map |> Enum.find(fn {_, char} -> char == "S" end)
1..(Enum.count(map) - 1)
|> Enum.reduce([start], fn _, track = [{x, y} | _] ->
[{1, 0}, {0, -1}, {-1, 0}, {0, 1}]
|> Enum.map(fn {dx, dy} -> {x + dx, y + dy} end)
|> Enum.find(fn next ->
Map.has_key?(map, next) and !Enum.member?(track, next)
end)
|> then(fn next -> [next | track] end)
end)
|> Enum.reverse()
|> Enum.with_index()
end
end
Part One
Code - Part 1
defmodule PartOne do
import Shared
def solve(input) do
IO.puts("--- Part One ---")
IO.puts("Result: #{run(input)}")
end
def run(input, cheat_time \\ 2, at_least_save \\ 100) do
input
|> Parser.parse()
|> path()
|> cheat(cheat_time, at_least_save)
end
end
Tests - Part 1
ExUnit.start(autorun: false)
defmodule PartOneTest do
use ExUnit.Case, async: true
import PartOne
@input """
###############
#...#...#.....#
#.#.#.#.#.###.#
#S#...#.#.#...#
#######.#.#.###
#######.#.#...#
#######.#.###.#
###..E#...#...#
###.#######.###
#...###...#...#
#.#####.#.###.#
#.#...#.#.#...#
#.#.#.#.#.#.###
#...#...#...###
###############
"""
@expected 1
test "part one" do
actual = run(@input, 2, 64)
assert actual == @expected
end
end
ExUnit.run()
Solution - Part 1
PartOne.solve(puzzle_input)
Part Two
Code - Part 2
defmodule PartTwo do
import Shared
def solve(input) do
IO.puts("--- Part Two ---")
IO.puts("Result: #{run(input)}")
end
def run(input, cheat_time \\ 20, at_least_save \\ 100) do
input
|> Parser.parse()
|> path()
|> cheat(cheat_time, at_least_save)
end
end
Tests - Part 2
ExUnit.start(autorun: false)
defmodule PartTwoTest do
use ExUnit.Case, async: true
import PartTwo
@input """
###############
#...#...#.....#
#.#.#.#.#.###.#
#S#...#.#.#...#
#######.#.#.###
#######.#.#...#
#######.#.###.#
###..E#...#...#
###.#######.###
#...###...#...#
#.#####.#.###.#
#.#...#.#.#...#
#.#.#.#.#.#.###
#...#...#...###
###############
"""
@expected 3
test "part two" do
actual = run(@input, 20, 76)
assert actual == @expected
end
end
ExUnit.run()
Solution - Part 2
PartTwo.solve(puzzle_input)