mirror of
https://github.com/rjNemo/ai_advent_code_2024
synced 2026-06-06 10:36:46 +00:00
Compare commits
19 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e6c0d59c2b | |||
| fbc39fe430 | |||
| 17655469a6 | |||
| d742148e96 | |||
| 901ad6c645 | |||
| 2d73d1bb51 | |||
| 04787bd7f9 | |||
| 143b8c0742 | |||
| 6132f3d3d2 | |||
| 542cb5b06d | |||
| 4f5ae71599 | |||
| 998b6f6405 | |||
| bacce02f21 | |||
| 6aec2849a1 | |||
| c2e2b4c31c | |||
| 34a89c44f7 | |||
| df41b278f8 | |||
| ae53d65980 | |||
| bdb91371cd |
8 changed files with 1807 additions and 2 deletions
111
lib/advent_code2024/solutions/day05/README.md
Normal file
111
lib/advent_code2024/solutions/day05/README.md
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
# Day 5: Print Queue
|
||||
|
||||
## Part 1
|
||||
|
||||
Print the safety manual pages in the correct order.
|
||||
The notation X|Y means that if both page number X and page number Y are to be produced
|
||||
as part of an update, page number X must be printed at some point before page number Y.
|
||||
|
||||
### Example
|
||||
|
||||
The input contains both the page ordering rules and the pages to produce in each update.
|
||||
|
||||
```txt
|
||||
47|53
|
||||
97|13
|
||||
97|61
|
||||
97|47
|
||||
75|29
|
||||
61|13
|
||||
75|53
|
||||
29|13
|
||||
97|29
|
||||
53|29
|
||||
61|53
|
||||
97|53
|
||||
61|29
|
||||
47|13
|
||||
75|47
|
||||
97|75
|
||||
47|61
|
||||
75|61
|
||||
47|29
|
||||
75|13
|
||||
53|13
|
||||
|
||||
75,47,61,53,29
|
||||
97,61,53,29,13
|
||||
75,29,13
|
||||
75,97,47,61,53
|
||||
61,13,29
|
||||
97,13,75,29,47
|
||||
```
|
||||
|
||||
The first section specifies the page ordering rules, one per line.
|
||||
The first rule, `47|53`, means that if an update includes both page number 47 and
|
||||
page number 53, then page number 47 must be printed at some point before page
|
||||
number 53. (47 doesn't necessarily need to be immediately before 53; other pages
|
||||
are allowed to be between them.)
|
||||
|
||||
The second section specifies the page numbers included in each update. Because most
|
||||
safety manuals are different, the pages needed in the updates are different too.
|
||||
The first update, `75,47,61,53,29`, means that the update consists of page
|
||||
numbers 75, 47, 61, 53, and 29.
|
||||
|
||||
To get the printers going as soon as possible, start by identifying which updates
|
||||
are already in the right order.
|
||||
|
||||
In the above example, the first update (75,47,61,53,29) is in the right order:
|
||||
|
||||
- 75 is correctly first because there are rules that put each other page after it: 75|47, 75|61, 75|53, and 75|29.
|
||||
- 47 is correctly second because 75 must be before it (75|47) and every other page must be after it according to 47|61, 47|53, and 47|29.
|
||||
- 61 is correctly in the middle because 75 and 47 are before it (75|61 and 47|61) and 53 and 29 are after it (61|53 and 61|29).
|
||||
- 53 is correctly fourth because it is before page number 29 (53|29).
|
||||
- 29 is the only page left and so is correctly last.
|
||||
|
||||
Because the first update does not include some page numbers, the ordering rules
|
||||
involving those missing page numbers are ignored.
|
||||
|
||||
The second and third updates are also in the correct order according to the rules.
|
||||
Like the first update, they also do not include every page number, and so only some
|
||||
of the ordering rules apply - within each update, the ordering rules that involve
|
||||
missing page numbers are not used.
|
||||
|
||||
The fourth update, 75,97,47,61,53, is not in the correct order: it would print 75
|
||||
before 97, which violates the rule 97|75.
|
||||
|
||||
The fifth update, 61,13,29, is also not in the correct order, since it breaks the
|
||||
rule 29|13.
|
||||
|
||||
The last update, 97,13,75,29,47, is not in the correct order due to breaking
|
||||
several rules.
|
||||
|
||||
For some reason, the Elves also need to know the middle page number of each update being printed. Because you are currently only printing the correctly-ordered updates, you will need to find the middle page number of each correctly-ordered update. In the above example, the correctly-ordered updates are:
|
||||
|
||||
```txt
|
||||
75,47,61,53,29
|
||||
97,61,53,29,13
|
||||
75,29,13
|
||||
```
|
||||
|
||||
These have middle page numbers of 61, 53, and 29 respectively. Adding these page numbers together gives `143`.
|
||||
|
||||
Of course, you'll need to be careful: the actual list of page ordering rules is bigger and more complicated than the above example.
|
||||
|
||||
Determine which updates are already in the correct order. What do you get if you add up the middle page number from those correctly-ordered updates?
|
||||
|
||||
## Part 2
|
||||
|
||||
While the Elves get to work printing the correctly-ordered updates, you have a little time to fix the rest of them.
|
||||
|
||||
For each of the incorrectly-ordered updates, use the page ordering rules to put the page numbers in the right order. For the above example, here are the three incorrectly-ordered updates and their correct orderings:
|
||||
|
||||
```txt
|
||||
75,97,47,61,53 becomes 97,75,47,61,53.
|
||||
61,13,29 becomes 61,29,13.
|
||||
97,13,75,29,47 becomes 97,75,47,29,13.
|
||||
```
|
||||
|
||||
After taking only the incorrectly-ordered updates and ordering them correctly, their middle page numbers are 47, 29, and 47. Adding these together produces `123`.
|
||||
|
||||
Find the updates which are not in the correct order. What do you get if you add up the middle page numbers after correctly ordering just those updates?
|
||||
109
lib/advent_code2024/solutions/day05/day5.ex
Normal file
109
lib/advent_code2024/solutions/day05/day5.ex
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
defmodule AdventCode2024.Solutions.Day05 do
|
||||
@moduledoc """
|
||||
Day 5: Solution for Advent of Code 2024
|
||||
"""
|
||||
|
||||
@behaviour AdventCode2024.Solution
|
||||
|
||||
@impl true
|
||||
def solve(""), do: {:error, :no_input}
|
||||
|
||||
def solve(path \\ "priv/inputs/day05/input.txt") do
|
||||
case File.read(path) do
|
||||
{:ok, content} -> solve_content(content)
|
||||
{:error, :enoent} -> {:error, :enoent}
|
||||
_ -> {:error, :no_input}
|
||||
end
|
||||
end
|
||||
|
||||
def solve_content(""), do: {:error, :no_input}
|
||||
|
||||
def solve_content(content) do
|
||||
{rules, updates} = parse_input(content)
|
||||
valid_updates = Enum.filter(updates, &is_valid_update?(&1, rules))
|
||||
sum = valid_updates |> Enum.map(&get_middle_number/1) |> Enum.sum()
|
||||
{:ok, sum}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def solve_part2(""), do: {:error, :no_input}
|
||||
|
||||
def solve_part2(path \\ "priv/inputs/day05/input.txt") do
|
||||
case File.read(path) do
|
||||
{:ok, content} -> solve_part2_content(content)
|
||||
{:error, :enoent} -> {:error, :enoent}
|
||||
_ -> {:error, :no_input}
|
||||
end
|
||||
end
|
||||
|
||||
def solve_part2_content(""), do: {:error, :no_input}
|
||||
|
||||
def solve_part2_content(content) do
|
||||
{rules, updates} = parse_input(content)
|
||||
invalid_updates = Enum.reject(updates, &is_valid_update?(&1, rules))
|
||||
|
||||
sum =
|
||||
invalid_updates
|
||||
|> Enum.map(&order_update(&1, rules))
|
||||
|> Enum.map(&get_middle_number/1)
|
||||
|> Enum.sum()
|
||||
|
||||
{:ok, sum}
|
||||
end
|
||||
|
||||
defp parse_input(content) do
|
||||
[rules_str, updates_str] = String.split(content, "\n\n", trim: true)
|
||||
rules = parse_rules(rules_str)
|
||||
updates = parse_updates(updates_str)
|
||||
{rules, updates}
|
||||
end
|
||||
|
||||
defp parse_rules(rules_str) do
|
||||
rules_str
|
||||
|> String.split("\n", trim: true)
|
||||
|> Enum.map(fn rule ->
|
||||
[first, second] = String.split(rule, "|")
|
||||
{String.to_integer(first), String.to_integer(second)}
|
||||
end)
|
||||
end
|
||||
|
||||
defp parse_updates(updates_str) do
|
||||
updates_str
|
||||
|> String.split("\n", trim: true)
|
||||
|> Enum.map(fn line ->
|
||||
line
|
||||
|> String.split(",")
|
||||
|> Enum.map(&String.to_integer/1)
|
||||
end)
|
||||
end
|
||||
|
||||
defp is_valid_update?(update, rules) do
|
||||
update
|
||||
|> Enum.chunk_every(2, 1, :discard)
|
||||
|> Enum.all?(fn [a, b] -> is_valid_order?(a, b, rules) end)
|
||||
end
|
||||
|
||||
defp is_valid_order?(a, b, rules) do
|
||||
not Enum.any?(rules, fn {x, y} -> x == b and y == a end)
|
||||
end
|
||||
|
||||
defp get_middle_number(list) do
|
||||
middle_index = div(length(list), 2)
|
||||
Enum.at(list, middle_index)
|
||||
end
|
||||
|
||||
defp order_update(update, rules) do
|
||||
update
|
||||
|> Enum.sort(fn a, b ->
|
||||
case {should_come_before?(a, b, rules), should_come_before?(b, a, rules)} do
|
||||
{true, false} -> true
|
||||
{false, true} -> false
|
||||
_ -> a > b
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp should_come_before?(a, b, rules) do
|
||||
Enum.any?(rules, fn {x, y} -> x == a and y == b end)
|
||||
end
|
||||
end
|
||||
1386
priv/inputs/day05/input.txt
Normal file
1386
priv/inputs/day05/input.txt
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -8,7 +8,7 @@ defmodule AdventCode2024.Solutions.Day03Test do
|
|||
end
|
||||
|
||||
test "returns error for invalid file" do
|
||||
assert Day03.solve("nonexistent.txt") == {:error, :enoent}
|
||||
assert Day03.solve("priv/nonexistent.txt") == {:error, :enoent}
|
||||
end
|
||||
|
||||
test "handles single valid multiplication" do
|
||||
|
|
@ -48,7 +48,7 @@ defmodule AdventCode2024.Solutions.Day03Test do
|
|||
end
|
||||
|
||||
test "returns error for invalid file" do
|
||||
assert Day03.solve_part2("nonexistent.txt") == {:error, :enoent}
|
||||
assert Day03.solve_part2("priv/nonexistent.txt") == {:error, :enoent}
|
||||
end
|
||||
|
||||
test "handles single multiplication with do/don't controls" do
|
||||
|
|
|
|||
184
test/advent_code2024/solutions/day05/day5_test.exs
Normal file
184
test/advent_code2024/solutions/day05/day5_test.exs
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
defmodule AdventCode2024.Solutions.Day05Test do
|
||||
use ExUnit.Case
|
||||
alias AdventCode2024.Solutions.Day05
|
||||
|
||||
@example_input """
|
||||
47|53
|
||||
97|13
|
||||
97|61
|
||||
97|47
|
||||
75|29
|
||||
61|13
|
||||
75|53
|
||||
29|13
|
||||
97|29
|
||||
53|29
|
||||
61|53
|
||||
97|53
|
||||
61|29
|
||||
47|13
|
||||
75|47
|
||||
97|75
|
||||
47|61
|
||||
75|61
|
||||
47|29
|
||||
75|13
|
||||
53|13
|
||||
|
||||
75,47,61,53,29
|
||||
97,61,53,29,13
|
||||
75,29,13
|
||||
75,97,47,61,53
|
||||
61,13,29
|
||||
97,13,75,29,47
|
||||
"""
|
||||
|
||||
setup_all do
|
||||
# Create a temporary file for testing
|
||||
path = "test/fixtures/day05/test_input.txt"
|
||||
File.mkdir_p!(Path.dirname(path))
|
||||
:ok = File.write(path, @example_input)
|
||||
|
||||
on_exit(fn ->
|
||||
File.rm!(path)
|
||||
end)
|
||||
|
||||
{:ok, test_file: path}
|
||||
end
|
||||
|
||||
describe "solve/1" do
|
||||
test "correctly identifies valid updates and sums middle pages", %{test_file: path} do
|
||||
assert {:ok, 143} = Day05.solve(path)
|
||||
end
|
||||
|
||||
test "returns error for empty input" do
|
||||
assert Day05.solve("") == {:error, :no_input}
|
||||
end
|
||||
|
||||
test "returns error for invalid file" do
|
||||
assert Day05.solve("priv/nonexistent.txt") == {:error, :enoent}
|
||||
end
|
||||
|
||||
test "validates first update is in correct order" do
|
||||
input = """
|
||||
75|47
|
||||
75|61
|
||||
75|53
|
||||
75|29
|
||||
47|61
|
||||
47|53
|
||||
47|29
|
||||
61|53
|
||||
61|29
|
||||
53|29
|
||||
|
||||
75,47,61,53,29
|
||||
"""
|
||||
path = "test/fixtures/day05/first_update.txt"
|
||||
:ok = File.write(path, input)
|
||||
assert {:ok, 61} = Day05.solve(path)
|
||||
File.rm!(path)
|
||||
end
|
||||
|
||||
test "validates second update is in correct order" do
|
||||
input = """
|
||||
97|13
|
||||
97|61
|
||||
97|53
|
||||
97|29
|
||||
61|13
|
||||
61|53
|
||||
61|29
|
||||
53|29
|
||||
29|13
|
||||
|
||||
97,61,53,29,13
|
||||
"""
|
||||
path = "test/fixtures/day05/second_update.txt"
|
||||
:ok = File.write(path, input)
|
||||
assert {:ok, 53} = Day05.solve(path)
|
||||
File.rm!(path)
|
||||
end
|
||||
|
||||
test "identifies invalid update due to rule violation" do
|
||||
input = """
|
||||
97|75
|
||||
|
||||
75,97,47,61,53
|
||||
"""
|
||||
path = "test/fixtures/day05/invalid_update.txt"
|
||||
:ok = File.write(path, input)
|
||||
assert {:ok, 0} = Day05.solve(path)
|
||||
File.rm!(path)
|
||||
end
|
||||
end
|
||||
|
||||
describe "solve_part2/1" do
|
||||
test "correctly orders invalid updates and sums middle pages", %{test_file: path} do
|
||||
assert {:ok, 123} = Day05.solve_part2(path)
|
||||
end
|
||||
|
||||
test "correctly orders first invalid update" do
|
||||
input = """
|
||||
75|47
|
||||
75|29
|
||||
47|29
|
||||
|
||||
75,47,29
|
||||
"""
|
||||
path = "test/fixtures/day05/part2_first_update.txt"
|
||||
:ok = File.write(path, input)
|
||||
assert {:ok, 47} = Day05.solve_part2(path)
|
||||
File.rm!(path)
|
||||
end
|
||||
|
||||
test "correctly orders second invalid update" do
|
||||
input = """
|
||||
97|13
|
||||
97|29
|
||||
29|13
|
||||
|
||||
97,29,13
|
||||
"""
|
||||
path = "test/fixtures/day05/part2_second_update.txt"
|
||||
:ok = File.write(path, input)
|
||||
assert {:ok, 29} = Day05.solve_part2(path)
|
||||
File.rm!(path)
|
||||
end
|
||||
|
||||
test "correctly orders third invalid update" do
|
||||
input = """
|
||||
97|75
|
||||
97|47
|
||||
75|47
|
||||
|
||||
97,75,47
|
||||
"""
|
||||
path = "test/fixtures/day05/part2_third_update.txt"
|
||||
:ok = File.write(path, input)
|
||||
assert {:ok, 47} = Day05.solve_part2(path)
|
||||
File.rm!(path)
|
||||
end
|
||||
|
||||
test "ignores already correctly ordered updates" do
|
||||
input = """
|
||||
97|13
|
||||
97|61
|
||||
97|53
|
||||
97|29
|
||||
61|13
|
||||
61|53
|
||||
61|29
|
||||
53|29
|
||||
29|13
|
||||
|
||||
97,61,53,29,13
|
||||
61,53,29,13
|
||||
"""
|
||||
path = "test/fixtures/day05/part2_ignore_valid.txt"
|
||||
:ok = File.write(path, input)
|
||||
assert {:ok, 0} = Day05.solve_part2(path)
|
||||
File.rm!(path)
|
||||
end
|
||||
end
|
||||
end
|
||||
5
test/fixtures/day05/part2_first_update.txt
vendored
Normal file
5
test/fixtures/day05/part2_first_update.txt
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
75|47
|
||||
75|29
|
||||
47|29
|
||||
|
||||
75,47,29
|
||||
5
test/fixtures/day05/part2_second_update.txt
vendored
Normal file
5
test/fixtures/day05/part2_second_update.txt
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
97|13
|
||||
97|29
|
||||
29|13
|
||||
|
||||
97,29,13
|
||||
5
test/fixtures/day05/part2_third_update.txt
vendored
Normal file
5
test/fixtures/day05/part2_third_update.txt
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
97|75
|
||||
97|47
|
||||
75|47
|
||||
|
||||
97,75,47
|
||||
Loading…
Reference in a new issue