aoc: solve day 7
This commit is contained in:
parent
cc2f49a3b9
commit
27892ff860
9 changed files with 1359 additions and 0 deletions
4
advent-of-code-2023/elixir/day_07/.formatter.exs
Normal file
4
advent-of-code-2023/elixir/day_07/.formatter.exs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# Used by "mix format"
|
||||||
|
[
|
||||||
|
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||||
|
]
|
26
advent-of-code-2023/elixir/day_07/.gitignore
vendored
Normal file
26
advent-of-code-2023/elixir/day_07/.gitignore
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# The directory Mix will write compiled artifacts to.
|
||||||
|
/_build/
|
||||||
|
|
||||||
|
# If you run "mix test --cover", coverage assets end up here.
|
||||||
|
/cover/
|
||||||
|
|
||||||
|
# The directory Mix downloads your dependencies sources to.
|
||||||
|
/deps/
|
||||||
|
|
||||||
|
# Where third-party dependencies like ExDoc output generated docs.
|
||||||
|
/doc/
|
||||||
|
|
||||||
|
# Ignore .fetch files in case you like to edit your project deps locally.
|
||||||
|
/.fetch
|
||||||
|
|
||||||
|
# If the VM crashes, it generates a dump, let's ignore it too.
|
||||||
|
erl_crash.dump
|
||||||
|
|
||||||
|
# Also ignore archive artifacts (built via "mix archive.build").
|
||||||
|
*.ez
|
||||||
|
|
||||||
|
# Ignore package tarball (built via "mix hex.build").
|
||||||
|
day_07-*.tar
|
||||||
|
|
||||||
|
# Temporary files, for example, from tests.
|
||||||
|
/tmp/
|
104
advent-of-code-2023/elixir/day_07/README.md
Normal file
104
advent-of-code-2023/elixir/day_07/README.md
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
# Day 7: Camel Cards
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
"Did you bring the parts?"
|
||||||
|
|
||||||
|
You turn around to see an Elf completely covered in white clothing, wearing goggles, and riding a large camel.
|
||||||
|
|
||||||
|
"Did you bring the parts?" she asks again, louder this time. You aren't sure what parts she's looking for;
|
||||||
|
you're here to figure out why the sand stopped.
|
||||||
|
|
||||||
|
"The parts! For the sand, yes! Come with me; I will show you." She beckons you onto the camel.
|
||||||
|
|
||||||
|
After riding a bit across the sands of Desert Island, you can see what look like very large rocks covering half of the horizon.
|
||||||
|
The Elf explains that the rocks are all along the part of Desert Island that is directly above Island Island,
|
||||||
|
making it hard to even get there. Normally, they use big machines to move the rocks and filter the sand,
|
||||||
|
but the machines have broken down because Desert Island recently stopped receiving the parts they need to fix the machines.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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`.
|
||||||
|
The relative strength of each card follows this order, where `A` is the highest and `2` is the lowest.
|
||||||
|
|
||||||
|
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
|
||||||
|
- 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
|
||||||
|
- 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
|
||||||
|
- Two pair, where two cards share one label, two other cards share a second label, and the remaining card has a third label: 23432
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
If two hands have the same type, a second ordering rule takes effect.
|
||||||
|
Start by comparing the first card in each hand. If these cards are different, the hand with the stronger first card is considered stronger.
|
||||||
|
If the first card in each hand have the same label, however, then move on to considering the second card in each hand.
|
||||||
|
If they differ, the hand with the higher second card wins;
|
||||||
|
otherwise, continue with the third card in each hand, then the fourth, then the fifth.
|
||||||
|
|
||||||
|
So, `33332` and `2AAAA` are both four of a kind hands, but `33332` is stronger because its first card is stronger.
|
||||||
|
Similarly, `77888` and `77788` are both a full house, but `77888` is stronger because its third card is stronger
|
||||||
|
(and both hands have the same first and second card).
|
||||||
|
|
||||||
|
To play Camel Cards, you are given a list of hands and their corresponding bid (your puzzle input). For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
32T3K 765
|
||||||
|
T55J5 684
|
||||||
|
KK677 28
|
||||||
|
KTJJT 220
|
||||||
|
QQQJA 483
|
||||||
|
```
|
||||||
|
|
||||||
|
This example shows five hands; each hand is followed by its bid amount.
|
||||||
|
Each hand wins an amount equal to its bid multiplied by its rank, where the weakest hand gets rank 1,
|
||||||
|
the second-weakest hand gets rank 2, and so on up to the strongest hand.
|
||||||
|
Because there are five hands in this example, the strongest hand will have rank 5 and its bid will be multiplied by 5.
|
||||||
|
|
||||||
|
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.
|
||||||
|
- `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.
|
||||||
|
|
||||||
|
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?
|
||||||
|
|
||||||
|
## Part Two
|
||||||
|
|
||||||
|
To make things a little more interesting, the Elf introduces one additional rule.
|
||||||
|
Now, J cards are jokers - wildcards that can act like whatever card would make the hand the strongest type possible.
|
||||||
|
|
||||||
|
To balance this, `J` cards are now the weakest individual cards, weaker even than `2`.
|
||||||
|
The other cards stay in the same order: `A`, `K`, `Q`, `T`, `9`, `8`, `7`, `6`, `5`, `4`, `3`, `2`, `J`.
|
||||||
|
|
||||||
|
`J` cards can pretend to be whatever card is best for the purpose of determining hand type;
|
||||||
|
for example, `QJJQ2` is now considered four of a kind.
|
||||||
|
However, for the purpose of breaking ties between two hands of the same type, `J` is always treated as `J`,
|
||||||
|
not the card it's pretending to be: `JKKK2` is weaker than `QQQQ2` because `J` is weaker than `Q`.
|
||||||
|
|
||||||
|
Now, the above example goes very differently:
|
||||||
|
|
||||||
|
```
|
||||||
|
32T3K 765
|
||||||
|
T55J5 684
|
||||||
|
KK677 28
|
||||||
|
KTJJT 220
|
||||||
|
QQQJA 483
|
||||||
|
```
|
||||||
|
|
||||||
|
- 32T3K is still the only one pair; it doesn't contain any jokers, so its strength doesn't increase.
|
||||||
|
- KK677 is now the only two pair, making it the second-weakest hand.
|
||||||
|
- T55J5, KTJJT, and QQQJA are now all four of a kind! T55J5 gets rank 3, QQQJA gets rank 4, and KTJJT gets rank 5.
|
||||||
|
|
||||||
|
With the new joker rule, the total winnings in this example are 5905.
|
||||||
|
|
||||||
|
Using the new joker rule, find the rank of every hand in your set. What are the new total winnings?
|
169
advent-of-code-2023/elixir/day_07/lib/solution.ex
Normal file
169
advent-of-code-2023/elixir/day_07/lib/solution.ex
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
defmodule Solution do
|
||||||
|
@spec get_total_winnings(String.t()) :: integer()
|
||||||
|
def get_total_winnings(filename, with_jokers \\ false) do
|
||||||
|
hands =
|
||||||
|
filename
|
||||||
|
|> File.stream!()
|
||||||
|
|> Enum.map(&parse_line/1)
|
||||||
|
|> Map.new()
|
||||||
|
|
||||||
|
sorted_cards =
|
||||||
|
hands
|
||||||
|
|> Map.keys()
|
||||||
|
|> Enum.sort_by(fn hand -> get_sorting_order(hand, with_jokers) end)
|
||||||
|
|
||||||
|
sorted_cards
|
||||||
|
|> Enum.with_index(1)
|
||||||
|
|> Enum.reduce(0, fn {el, idx}, acc -> acc + idx * hands[el] 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.
|
||||||
|
defp get_sorting_order(hand, with_jokers) do
|
||||||
|
# Map cards to corresponding power,
|
||||||
|
# e.g. "57TA" -> [5, 7, 10, 14]
|
||||||
|
powers =
|
||||||
|
hand
|
||||||
|
|> String.graphemes()
|
||||||
|
|> Enum.map(fn card -> get_card_power(card, with_jokers) end)
|
||||||
|
|
||||||
|
# Prepend hand rank.
|
||||||
|
[get_rank(hand, with_jokers) | powers]
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Converts a card symbol to a number.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> Solution.get_card_power("J")
|
||||||
|
11
|
||||||
|
|
||||||
|
iex> Solution.get_card_power("J", true)
|
||||||
|
1
|
||||||
|
|
||||||
|
iex> Solution.get_card_power("5")
|
||||||
|
5
|
||||||
|
"""
|
||||||
|
def get_card_power(card, with_jokers \\ false) do
|
||||||
|
case card do
|
||||||
|
"A" ->
|
||||||
|
14
|
||||||
|
|
||||||
|
"K" ->
|
||||||
|
13
|
||||||
|
|
||||||
|
"Q" ->
|
||||||
|
12
|
||||||
|
|
||||||
|
"J" ->
|
||||||
|
if with_jokers, do: 1, else: 11
|
||||||
|
|
||||||
|
"T" ->
|
||||||
|
10
|
||||||
|
|
||||||
|
num ->
|
||||||
|
String.to_integer(num)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns the rank of a hand based on the number of identical cards.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> Solution.get_rank("AAAAA")
|
||||||
|
7
|
||||||
|
|
||||||
|
iex> Solution.get_rank("AA8AA")
|
||||||
|
6
|
||||||
|
|
||||||
|
iex> Solution.get_rank("23332")
|
||||||
|
5
|
||||||
|
|
||||||
|
iex> Solution.get_rank("TTT98")
|
||||||
|
4
|
||||||
|
|
||||||
|
iex> Solution.get_rank("23432")
|
||||||
|
3
|
||||||
|
|
||||||
|
iex> Solution.get_rank("A23A4")
|
||||||
|
2
|
||||||
|
|
||||||
|
iex> Solution.get_rank("23456")
|
||||||
|
1
|
||||||
|
"""
|
||||||
|
def get_rank(hand, with_jokers \\ false) do
|
||||||
|
hand = if with_jokers, do: replace_joker(hand), else: hand
|
||||||
|
|
||||||
|
freq =
|
||||||
|
hand
|
||||||
|
|> String.graphemes()
|
||||||
|
|> Enum.frequencies()
|
||||||
|
|> Map.values()
|
||||||
|
|> Enum.sort()
|
||||||
|
|
||||||
|
case freq do
|
||||||
|
# five of a kind
|
||||||
|
[5] -> 7
|
||||||
|
# four of a kind
|
||||||
|
[1, 4] -> 6
|
||||||
|
# full house
|
||||||
|
[2, 3] -> 5
|
||||||
|
# three of a kind
|
||||||
|
[1, 1, 3] -> 4
|
||||||
|
# two pair
|
||||||
|
[1, 2, 2] -> 3
|
||||||
|
# one pair
|
||||||
|
[1, 1, 1, 2] -> 2
|
||||||
|
# high card
|
||||||
|
_ -> 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Replaces all J cards in hand with the card that will result in the best rank.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> Solution.replace_joker("T55J5")
|
||||||
|
"T5555"
|
||||||
|
|
||||||
|
iex> Solution.replace_joker("KTJJT")
|
||||||
|
"KTTTT"
|
||||||
|
|
||||||
|
iex> Solution.replace_joker("JJJJJ")
|
||||||
|
"AAAAA"
|
||||||
|
"""
|
||||||
|
def replace_joker(hand) do
|
||||||
|
freq =
|
||||||
|
hand
|
||||||
|
|> String.replace("J", "")
|
||||||
|
|> String.graphemes()
|
||||||
|
|> Enum.frequencies()
|
||||||
|
|
||||||
|
if Enum.empty?(Map.keys(freq)) do
|
||||||
|
String.replace(hand, "J", "A")
|
||||||
|
else
|
||||||
|
max_count =
|
||||||
|
freq
|
||||||
|
|> Map.values()
|
||||||
|
|> Enum.max()
|
||||||
|
|
||||||
|
{most_used_symbol, _} =
|
||||||
|
freq
|
||||||
|
|> Map.to_list()
|
||||||
|
|> Enum.find(fn {_, count} -> count == max_count end)
|
||||||
|
|
||||||
|
String.replace(hand, "J", most_used_symbol)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
28
advent-of-code-2023/elixir/day_07/mix.exs
Normal file
28
advent-of-code-2023/elixir/day_07/mix.exs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
defmodule Day07.MixProject do
|
||||||
|
use Mix.Project
|
||||||
|
|
||||||
|
def project do
|
||||||
|
[
|
||||||
|
app: :day_07,
|
||||||
|
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
|
5
advent-of-code-2023/elixir/day_07/test/example.txt
Normal file
5
advent-of-code-2023/elixir/day_07/test/example.txt
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
32T3K 765
|
||||||
|
T55J5 684
|
||||||
|
KK677 28
|
||||||
|
KTJJT 220
|
||||||
|
QQQJA 483
|
1000
advent-of-code-2023/elixir/day_07/test/input.txt
Normal file
1000
advent-of-code-2023/elixir/day_07/test/input.txt
Normal file
File diff suppressed because it is too large
Load diff
22
advent-of-code-2023/elixir/day_07/test/solution_test.exs
Normal file
22
advent-of-code-2023/elixir/day_07/test/solution_test.exs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
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
|
1
advent-of-code-2023/elixir/day_07/test/test_helper.exs
Normal file
1
advent-of-code-2023/elixir/day_07/test/test_helper.exs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
ExUnit.start()
|
Loading…
Reference in a new issue