Refactor days 7-9 of AoC 2023

- Add `solve` task.
- Add tests for examples.
This commit is contained in:
Ivan R. 2024-12-21 13:40:06 +05:00
parent 669b914523
commit e994381b27
Signed by: lumin
GPG key ID: E0937DC7CD6D3817
40 changed files with 230 additions and 181 deletions

View file

@ -1,4 +1,8 @@
# Day 7: Camel Cards # Advent of Code 2023 day 7 solution in Elixir
## Camel Cards
[Task page](https://adventofcode.com/2023/day/7)
Your all-expenses-paid trip turns out to be a one-way, five-minute ride in an airship. Your all-expenses-paid trip turns out to be a one-way, five-minute ride in an airship.
(At least it's a cool airship!) It drops you off at the edge of a vast desert and descends back to Island Island. (At least it's a cool airship!) It drops you off at the edge of a vast desert and descends back to Island Island.
@ -19,7 +23,8 @@ but the machines have broken down because Desert Island recently stopped receivi
You've already assumed it'll be your job to figure out why the parts stopped when she asks if you can help. You agree automatically. You've already assumed it'll be your job to figure out why the parts stopped when she asks if you can help. You agree automatically.
Because the journey will take a few days, she offers to teach you the game of Camel Cards. Camel Cards is sort of similar to poker except it's designed to be easier to play while riding a camel. Because the journey will take a few days, she offers to teach you the game of Camel Cards.
Camel Cards is sort of similar to poker except it's designed to be easier to play while riding a camel.
In Camel Cards, you get a list of hands, and your goal is to order them based on the strength of each hand. In Camel Cards, you get a list of hands, and your goal is to order them based on the strength of each hand.
A hand consists of five cards labeled one of `A`, `K`, `Q`, `J`, `T`, `9`, `8`, `7`, `6`, `5`, `4`, `3`, or `2`. A hand consists of five cards labeled one of `A`, `K`, `Q`, `J`, `T`, `9`, `8`, `7`, `6`, `5`, `4`, `3`, or `2`.
@ -27,13 +32,14 @@ The relative strength of each card follows this order, where `A` is the highest
Every hand is exactly one type. From strongest to weakest, they are: Every hand is exactly one type. From strongest to weakest, they are:
- Five of a kind, where all five cards have the same label: AAAAA - Five of a kind, where all five cards have the same label: `AAAAA`
- Four of a kind, where four cards have the same label and one card has a different label: AA8AA - Four of a kind, where four cards have the same label and one card has a different label: `AA8AA`
- Full house, where three cards have the same label, and the remaining two cards share a different label: 23332 - Full house, where three cards have the same label, and the remaining two cards share a different label: `23332`
- Three of a kind, where three cards have the same label, and the remaining two cards are each different from any other card in the hand: TTT98 - Three of a kind, where three cards have the same label, and the remaining two cards are each different
- Two pair, where two cards share one label, two other cards share a second label, and the remaining card has a third label: 23432 from any other card in the hand: `TTT98`
- One pair, where two cards share one label, and the other three cards have a different label from the pair and each other: A23A4 - Two pair, where two cards share one label, two other cards share a second label, and the remaining card has a third label: `23432`
- High card, where all cards' labels are distinct: 23456 - One pair, where two cards share one label, and the other three cards have a different label from the pair and each other: `A23A4`
- High card, where all cards' labels are distinct: `23456`
Hands are primarily ordered based on type; for example, every full house is stronger than any three of a kind. Hands are primarily ordered based on type; for example, every full house is stronger than any three of a kind.
@ -65,13 +71,16 @@ Because there are five hands in this example, the strongest hand will have rank
So, the first step is to put the hands in order of strength: So, the first step is to put the hands in order of strength:
- `32T3K` is the only one pair and the other hands are all a stronger type, so it gets rank 1. - `32T3K` is the only one pair and the other hands are all a stronger type, so it gets rank 1.
- `KK677` and `KTJJT` are both two pair. Their first cards both have the same label, but the second card of `KK677` is stronger (K vs T), so `KTJJT` gets rank 2 and `KK677` gets rank 3. - `KK677` and `KTJJT` are both two pair. Their first cards both have the same label,
but the second card of `KK677` is stronger (K vs T), so `KTJJT` gets rank 2 and `KK677` gets rank 3.
- `T55J5` and `QQQJA` are both three of a kind. `QQQJA` has a stronger first card, so it gets rank 5 and `T55J5` gets rank 4. - `T55J5` and `QQQJA` are both three of a kind. `QQQJA` has a stronger first card, so it gets rank 5 and `T55J5` gets rank 4.
Now, you can determine the total winnings of this set of hands by adding up the result of multiplying each hand's bid with its rank (765 * 1 + 220 * 2 + 28 * 3 + 684 * 4 + 483 * 5). So the total winnings in this example are 6440. Now, you can determine the total winnings of this set of hands by adding up the result of multiplying each hand's bid with
its rank (765 * 1 + 220 * 2 + 28 * 3 + 684 * 4 + 483 * 5). So the total winnings in this example are 6440.
Find the rank of every hand in your set. What are the total winnings? Find the rank of every hand in your set. What are the total winnings?
## Part Two ## Part Two
To make things a little more interesting, the Elf introduces one additional rule. To make things a little more interesting, the Elf introduces one additional rule.

View file

@ -1,12 +1,5 @@
defmodule Solution do defmodule Cards do
@spec get_total_winnings(String.t()) :: integer() def get_total_winnings(hands, with_jokers \\ false) do
def get_total_winnings(filename, with_jokers \\ false) do
hands =
filename
|> File.stream!()
|> Enum.map(&parse_line/1)
|> Map.new()
sorted_cards = sorted_cards =
hands hands
|> Map.keys() |> Map.keys()
@ -17,15 +10,6 @@ defmodule Solution do
|> Enum.reduce(0, fn {el, idx}, acc -> acc + idx * hands[el] end) |> Enum.reduce(0, fn {el, idx}, acc -> acc + idx * hands[el] end)
end end
defp parse_line(line) do
[hand, bid] =
line
|> String.trim()
|> String.split(" ")
{hand, String.to_integer(bid)}
end
# Map a hand to a list that may be used for sorting. # Map a hand to a list that may be used for sorting.
defp get_sorting_order(hand, with_jokers) do defp get_sorting_order(hand, with_jokers) do
# Map cards to corresponding power, # Map cards to corresponding power,
@ -44,13 +28,13 @@ defmodule Solution do
## Examples ## Examples
iex> Solution.get_card_power("J") iex> Cards.get_card_power("J")
11 11
iex> Solution.get_card_power("J", true) iex> Cards.get_card_power("J", true)
1 1
iex> Solution.get_card_power("5") iex> Cards.get_card_power("5")
5 5
""" """
def get_card_power(card, with_jokers \\ false) do def get_card_power(card, with_jokers \\ false) do
@ -80,25 +64,25 @@ defmodule Solution do
## Examples ## Examples
iex> Solution.get_rank("AAAAA") iex> Cards.get_rank("AAAAA")
7 7
iex> Solution.get_rank("AA8AA") iex> Cards.get_rank("AA8AA")
6 6
iex> Solution.get_rank("23332") iex> Cards.get_rank("23332")
5 5
iex> Solution.get_rank("TTT98") iex> Cards.get_rank("TTT98")
4 4
iex> Solution.get_rank("23432") iex> Cards.get_rank("23432")
3 3
iex> Solution.get_rank("A23A4") iex> Cards.get_rank("A23A4")
2 2
iex> Solution.get_rank("23456") iex> Cards.get_rank("23456")
1 1
""" """
def get_rank(hand, with_jokers \\ false) do def get_rank(hand, with_jokers \\ false) do
@ -134,13 +118,13 @@ defmodule Solution do
## Examples ## Examples
iex> Solution.replace_joker("T55J5") iex> Cards.replace_joker("T55J5")
"T5555" "T5555"
iex> Solution.replace_joker("KTJJT") iex> Cards.replace_joker("KTJJT")
"KTTTT" "KTTTT"
iex> Solution.replace_joker("JJJJJ") iex> Cards.replace_joker("JJJJJ")
"AAAAA" "AAAAA"
""" """
def replace_joker(hand) do def replace_joker(hand) do

View file

@ -0,0 +1,17 @@
defmodule Parser do
def parse_file(filename) do
filename
|> File.stream!()
|> Stream.map(&parse_line/1)
|> Map.new()
end
defp parse_line(line) do
[hand, bid] =
line
|> String.trim()
|> String.split(" ")
{hand, String.to_integer(bid)}
end
end

View file

@ -0,0 +1,10 @@
defmodule Mix.Tasks.Solve do
use Mix.Task
def run(_) do
hands = Parser.parse_file("input.txt")
IO.puts("Part 1: #{Cards.get_total_winnings(hands)}")
IO.puts("Part 2: #{Cards.get_total_winnings(hands, true)}")
end
end

View file

@ -11,18 +11,13 @@ defmodule Day07.MixProject do
] ]
end end
# Run "mix help compile.app" to learn about applications.
def application do def application do
[ [
extra_applications: [:logger] extra_applications: [:logger]
] ]
end end
# Run "mix help deps" to learn about dependencies.
defp deps do defp deps do
[ []
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
]
end end
end end

View file

@ -0,0 +1,14 @@
defmodule CardsTest do
use ExUnit.Case
doctest Cards
test "example 1" do
hands = Parser.parse_file("test/example.txt")
assert Cards.get_total_winnings(hands) == 6440
end
test "example 2" do
hands = Parser.parse_file("test/example.txt")
assert Cards.get_total_winnings(hands, true) == 5905
end
end

View file

@ -1,4 +1,8 @@
# Day 8 # Advent of Code 2023 day 8 solution in Elixir
## Haunted Wasteland
[Task page](https://adventofcode.com/2023/day/8)
You're still riding a camel across Desert Island when you spot a sandstorm quickly approaching. You're still riding a camel across Desert Island when you spot a sandstorm quickly approaching.
When you turn to warn the Elf, she disappears before your eyes! To be fair, she had just finished warning you about ghosts a few minutes ago. When you turn to warn the Elf, she disappears before your eyes! To be fair, she had just finished warning you about ghosts a few minutes ago.
@ -45,6 +49,7 @@ ZZZ = (ZZZ, ZZZ)
Starting at AAA, follow the left/right instructions. How many steps are required to reach ZZZ? Starting at AAA, follow the left/right instructions. How many steps are required to reach ZZZ?
## Part Two ## Part Two
The sandstorm is upon you and you aren't any closer to escaping the wasteland. The sandstorm is upon you and you aren't any closer to escaping the wasteland.
@ -79,15 +84,13 @@ Repeat this process until all of the nodes you're currently on end with Z.
(If only some of the nodes you're on end with Z, they act like any other node and you continue as normal.) (If only some of the nodes you're on end with Z, they act like any other node and you continue as normal.)
In this example, you would proceed as follows: In this example, you would proceed as follows:
``` - Step 0: You are at 11A and 22A.
Step 0: You are at 11A and 22A. - Step 1: You choose all of the left paths, leading you to 11B and 22B.
Step 1: You choose all of the left paths, leading you to 11B and 22B. - Step 2: You choose all of the right paths, leading you to 11Z and 22C.
Step 2: You choose all of the right paths, leading you to 11Z and 22C. - Step 3: You choose all of the left paths, leading you to 11B and 22Z.
Step 3: You choose all of the left paths, leading you to 11B and 22Z. - Step 4: You choose all of the right paths, leading you to 11Z and 22B.
Step 4: You choose all of the right paths, leading you to 11Z and 22B. - Step 5: You choose all of the left paths, leading you to 11B and 22C.
Step 5: You choose all of the left paths, leading you to 11B and 22C. - Step 6: You choose all of the right paths, leading you to 11Z and 22Z.
Step 6: You choose all of the right paths, leading you to 11Z and 22Z.
```
So, in this example, you end up entirely on nodes that end in Z after 6 steps. So, in this example, you end up entirely on nodes that end in Z after 6 steps.

View file

@ -7,7 +7,6 @@ defmodule Navigator do
iex> Navigator.count_steps(["R"], %{"AAA" => ["AAA", "ZZZ"], "ZZZ" => ["ZZZ", "ZZZ"]}) iex> Navigator.count_steps(["R"], %{"AAA" => ["AAA", "ZZZ"], "ZZZ" => ["ZZZ", "ZZZ"]})
1 1
""" """
@spec count_steps(list(String.t()), map()) :: integer()
def count_steps(instructions, maps) do def count_steps(instructions, maps) do
count_steps(instructions, instructions, maps, "AAA", 0) count_steps(instructions, instructions, maps, "AAA", 0)
end end

View file

@ -1,16 +1,12 @@
defmodule NavigatorTest do defmodule Mix.Tasks.Solve do
use ExUnit.Case use Mix.Task
doctest Navigator
doctest Parser
test "part 1" do def run(_) do
{instructions, maps} = Parser.parse_file("input.txt") {instructions, maps} = Parser.parse_file("input.txt")
res = Navigator.count_steps(instructions, maps) res = Navigator.count_steps(instructions, maps)
IO.puts("Part 1: #{res}") IO.puts("Part 1: #{res}")
end
test "part 2" do
{instructions, maps} = Parser.parse_file("input.txt")
res = Navigator.count_ghost_steps(instructions, maps) res = Navigator.count_ghost_steps(instructions, maps)
IO.puts("Part 2: #{res}") IO.puts("Part 2: #{res}")
end end

View file

@ -0,0 +1,25 @@
defmodule Day08.MixProject do
use Mix.Project
def project do
[
app: :day_08,
version: "0.1.0",
elixir: "~> 1.16",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
def application do
[
extra_applications: [:logger]
]
end
defp deps do
[
{:math, "~> 0.7.0"}
]
end
end

View file

@ -0,0 +1,3 @@
%{
"math": {:hex, :math, "0.7.0", "12af548c3892abf939a2e242216c3e7cbfb65b9b2fe0d872d05c6fb609f8127b", [:mix], [], "hexpm", "7987af97a0c6b58ad9db43eb5252a49fc1dfe1f6d98f17da9282e297f594ebc2"},
}

View file

@ -0,0 +1,9 @@
RL
AAA = (BBB, CCC)
BBB = (DDD, EEE)
CCC = (ZZZ, GGG)
DDD = (DDD, DDD)
EEE = (EEE, EEE)
GGG = (GGG, GGG)
ZZZ = (ZZZ, ZZZ)

View file

@ -0,0 +1,10 @@
LR
11A = (11B, XXX)
11B = (XXX, 11Z)
11Z = (11B, XXX)
22A = (22B, XXX)
22B = (22C, 22C)
22C = (22Z, 22Z)
22Z = (22B, 22B)
XXX = (XXX, XXX)

View file

@ -0,0 +1,15 @@
defmodule NavigatorTest do
use ExUnit.Case
doctest Navigator
doctest Parser
test "example 1" do
{instructions, maps} = Parser.parse_file("test/input-1.txt")
assert Navigator.count_steps(instructions, maps) == 2
end
test "example 2" do
{instructions, maps} = Parser.parse_file("test/input-2.txt")
assert Navigator.count_ghost_steps(instructions, maps) == 6
end
end

View file

@ -1,4 +1,8 @@
# Oasis # Advent of Code 2023 day 9 solution in Elixir
## Mirage Maintenance
[Task page](https://adventofcode.com/2023/day/9)
You ride the camel through the sandstorm and stop where the ghost's maps told you to stop. You ride the camel through the sandstorm and stop where the ghost's maps told you to stop.
The sandstorm subsequently subsides, somehow seeing you standing at an oasis! The sandstorm subsequently subsides, somehow seeing you standing at an oasis!
@ -7,7 +11,7 @@ The camel goes to get some water and you stretch your neck.
As you look up, you discover what must be yet another giant floating island, this one made of metal! As you look up, you discover what must be yet another giant floating island, this one made of metal!
That must be where the parts to fix the sand machines come from. That must be where the parts to fix the sand machines come from.
There's even a hang glider partially buried in the sand here; once the sun rises and heats up the sand, There's even a hang glider partially buried in the sand here; once the sun rises and heats up the sand,
you might be able to use the glider and the hot air to get all the way up to the metal island! you might be able to use the glider and the hot air to get all the way up to the metal island!
While you wait for the sun to rise, you admire the oasis hidden here in the middle of Desert Island. While you wait for the sun to rise, you admire the oasis hidden here in the middle of Desert Island.

View file

@ -1,30 +1,14 @@
defmodule Oasis do defmodule Oasis do
@moduledoc """ def get_next_values_sum(lists) do
Day 9 of Advent of Code 2023. lists
|> Stream.map(fn list -> Oasis.predict(list, &Oasis.predict_next_value/2) end)
Link: https://adventofcode.com/2023/day/9.
"""
def solve() do
input =
File.stream!("input.txt")
|> Stream.map(&String.trim_trailing/1)
# Get lists of strings.
|> Stream.map(&String.split/1)
# Get lists of integers.
|> Stream.map(fn list -> Enum.map(list, &String.to_integer/1) end)
# Predict next value for each list.
input
|> Stream.map(fn list -> predict(list, &predict_next_value/2) end)
|> Enum.sum() |> Enum.sum()
|> IO.puts() end
# Predict previous value for each list. def get_prev_values_sum(lists) do
input lists
|> Stream.map(fn list -> predict(list, &predict_prev_value/2) end) |> Stream.map(fn list -> Oasis.predict(list, &Oasis.predict_prev_value/2) end)
|> Enum.sum() |> Enum.sum()
|> IO.puts()
end end
@doc """ @doc """

View file

@ -0,0 +1,8 @@
defmodule Parser do
def parse(filename) do
File.stream!(filename)
|> Stream.map(&String.trim_trailing/1)
|> Stream.map(&String.split/1)
|> Stream.map(fn list -> Enum.map(list, &String.to_integer/1) end)
end
end

View file

@ -0,0 +1,13 @@
defmodule Mix.Tasks.Solve do
use Mix.Task
def run(_) do
input = Parser.parse("input.txt")
res1 = Oasis.get_next_values_sum(input)
IO.puts("Part 1: #{res1}")
res2 = Oasis.get_prev_values_sum(input)
IO.puts("Part 2: #{res2}")
end
end

View file

@ -0,0 +1,23 @@
defmodule Day09.MixProject do
use Mix.Project
def project do
[
app: :day_09,
version: "0.1.0",
elixir: "~> 1.16",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
def application do
[
extra_applications: [:logger]
]
end
defp deps do
[]
end
end

View file

@ -0,0 +1,3 @@
0 3 6 9 12 15
1 3 6 10 15 21
10 13 16 21 30 45

View file

@ -0,0 +1,14 @@
defmodule OasisTest do
use ExUnit.Case
doctest Oasis
test "example 1" do
input = Parser.parse("test/input.txt")
assert Oasis.get_next_values_sum(input) == 114
end
test "example 2" do
input = Parser.parse("test/input.txt")
assert Oasis.get_prev_values_sum(input) == 2
end
end

View file

@ -1,22 +0,0 @@
defmodule SolutionTest do
use ExUnit.Case
doctest Solution
test "example 1" do
assert Solution.get_total_winnings("test/example.txt") == 6440
end
test "part 1" do
res = Solution.get_total_winnings("test/input.txt")
IO.puts("First answer: #{res}")
end
test "example 2" do
assert Solution.get_total_winnings("test/example.txt", true) == 5905
end
test "part 2" do
res = Solution.get_total_winnings("test/input.txt", true)
IO.puts("Second answer: #{res}")
end
end

View file

@ -1,30 +0,0 @@
defmodule Navigator.MixProject do
use Mix.Project
def project do
[
app: :navigator,
version: "0.1.0",
elixir: "~> 1.16",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger]
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false},
{:math, "~> 0.7.0"}
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
]
end
end

View file

@ -1,5 +0,0 @@
%{
"dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"},
"erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"},
"math": {:hex, :math, "0.7.0", "12af548c3892abf939a2e242216c3e7cbfb65b9b2fe0d872d05c6fb609f8127b", [:mix], [], "hexpm", "7987af97a0c6b58ad9db43eb5252a49fc1dfe1f6d98f17da9282e297f594ebc2"},
}

View file

@ -1,28 +0,0 @@
defmodule Oasis.MixProject do
use Mix.Project
def project do
[
app: :oasis,
version: "0.1.0",
elixir: "~> 1.16",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger]
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
]
end
end

View file

@ -1,4 +0,0 @@
defmodule OasisTest do
use ExUnit.Case
doctest Oasis
end