2023-12-07 11:08:34 +00:00
|
|
|
defmodule AOC.Day7 do
|
|
|
|
# use AOC.Day, day: 7, input: "example"
|
|
|
|
use AOC.Day, day: 7
|
|
|
|
|
|
|
|
@type_to_power %{
|
|
|
|
five_of_a_kind: 6,
|
|
|
|
four_of_a_kind: 5,
|
|
|
|
full_house: 4,
|
|
|
|
three_of_a_kind: 3,
|
|
|
|
two_pair: 2,
|
|
|
|
one_pair: 1,
|
|
|
|
high_card: 0
|
|
|
|
}
|
|
|
|
|
|
|
|
@card_to_power %{
|
|
|
|
?A => 14,
|
|
|
|
?K => 13,
|
|
|
|
?Q => 12,
|
|
|
|
?J => 11,
|
|
|
|
?T => 10,
|
|
|
|
?9 => 9,
|
|
|
|
?8 => 8,
|
|
|
|
?7 => 7,
|
|
|
|
?6 => 6,
|
|
|
|
?5 => 5,
|
|
|
|
?4 => 4,
|
|
|
|
?3 => 3,
|
|
|
|
?2 => 2
|
|
|
|
}
|
|
|
|
|
|
|
|
def parse_input(lines) do
|
|
|
|
lines
|
|
|
|
|> Enum.map(fn line ->
|
|
|
|
[hand, bid] = String.split(line, " ")
|
|
|
|
|
|
|
|
{hand, String.to_integer(bid)}
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
|
|
|
def replace_jokers(hand, card) do
|
|
|
|
Enum.map(hand, fn
|
|
|
|
?J -> card
|
|
|
|
c -> c
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
|
|
|
def best_joker_replacement(hand) do
|
2023-12-08 09:02:14 +00:00
|
|
|
freqs =
|
|
|
|
Enum.frequencies(hand)
|
|
|
|
|> Enum.reject(fn {card, _count} -> card == ?J end)
|
2023-12-07 11:08:34 +00:00
|
|
|
|
|
|
|
cond do
|
2023-12-08 09:02:14 +00:00
|
|
|
Enum.any?(freqs, fn {_card, count} -> count == 4 end) ->
|
2023-12-08 09:00:27 +00:00
|
|
|
{card, _} = Enum.find(freqs, fn {_card, count} -> count == 4 end)
|
2023-12-07 11:08:34 +00:00
|
|
|
card
|
2023-12-08 09:02:14 +00:00
|
|
|
|
|
|
|
Enum.any?(freqs, fn {_card, count} -> count == 3 end) ->
|
2023-12-08 09:00:27 +00:00
|
|
|
{card, _} = Enum.find(freqs, fn {_card, count} -> count == 3 end)
|
2023-12-07 11:08:34 +00:00
|
|
|
card
|
2023-12-08 09:02:14 +00:00
|
|
|
|
|
|
|
Enum.any?(freqs, fn {_card, count} -> count == 2 end) ->
|
2023-12-08 09:00:27 +00:00
|
|
|
doubles = Enum.filter(freqs, fn {_card, count} -> count == 2 end)
|
2023-12-08 09:02:14 +00:00
|
|
|
|
2023-12-07 11:08:34 +00:00
|
|
|
if length(doubles) == 1 do
|
|
|
|
hd(doubles) |> elem(0)
|
|
|
|
else
|
|
|
|
[{double1, _}, {double2, _}] = doubles
|
2023-12-08 09:02:14 +00:00
|
|
|
|
2023-12-07 11:08:34 +00:00
|
|
|
if Map.fetch!(@card_to_power, double1) >= Map.fetch!(@card_to_power, double2) do
|
|
|
|
double1
|
|
|
|
else
|
|
|
|
double2
|
|
|
|
end
|
|
|
|
end
|
2023-12-08 09:02:14 +00:00
|
|
|
|
2023-12-07 11:08:34 +00:00
|
|
|
true ->
|
|
|
|
freqs
|
2023-12-08 09:00:27 +00:00
|
|
|
|> Enum.map(fn {card, _count} -> card end)
|
2023-12-08 09:02:14 +00:00
|
|
|
|> Enum.sort(fn card1, card2 ->
|
2023-12-07 11:08:34 +00:00
|
|
|
Map.fetch!(@card_to_power, card1) >= Map.fetch!(@card_to_power, card2)
|
|
|
|
end)
|
|
|
|
|> hd()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def find_best_joker_configuration(hand) do
|
|
|
|
freqs = Enum.frequencies(hand)
|
|
|
|
joker_count = Map.get(freqs, ?J, 0)
|
2023-12-08 09:02:14 +00:00
|
|
|
|
2023-12-07 11:08:34 +00:00
|
|
|
case joker_count do
|
2023-12-08 09:02:14 +00:00
|
|
|
0 ->
|
|
|
|
hand
|
|
|
|
|
|
|
|
5 ->
|
|
|
|
~c"AAAAA"
|
|
|
|
|
2023-12-07 11:08:34 +00:00
|
|
|
_ ->
|
|
|
|
joker_replacement = best_joker_replacement(hand)
|
2023-12-08 09:02:14 +00:00
|
|
|
|
2023-12-07 11:08:34 +00:00
|
|
|
replace_jokers(hand, joker_replacement)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def hand_to_type(hand, joker_rule) do
|
2023-12-08 09:02:14 +00:00
|
|
|
freqs =
|
|
|
|
hand
|
|
|
|
|> String.to_charlist()
|
|
|
|
|> then(fn hand ->
|
|
|
|
if joker_rule do
|
|
|
|
find_best_joker_configuration(hand)
|
|
|
|
else
|
|
|
|
hand
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|> Enum.frequencies()
|
|
|
|
|> Enum.map(fn {_k, v} -> v end)
|
2023-12-07 11:08:34 +00:00
|
|
|
|
|
|
|
cond do
|
|
|
|
Enum.member?(freqs, 5) -> :five_of_a_kind
|
|
|
|
Enum.member?(freqs, 4) -> :four_of_a_kind
|
|
|
|
Enum.member?(freqs, 3) and Enum.member?(freqs, 2) -> :full_house
|
|
|
|
Enum.member?(freqs, 3) -> :three_of_a_kind
|
2023-12-08 09:02:14 +00:00
|
|
|
Enum.count(freqs, &(&1 == 2)) == 2 -> :two_pair
|
2023-12-07 11:08:34 +00:00
|
|
|
Enum.member?(freqs, 2) -> :one_pair
|
|
|
|
true -> :high_card
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def compare_same_type_hands(hand1, hand2, joker_rule) do
|
|
|
|
hand1 = String.to_charlist(hand1)
|
|
|
|
hand2 = String.to_charlist(hand2)
|
|
|
|
|
|
|
|
Enum.zip(hand1, hand2)
|
|
|
|
|> compare_same_type_hands2(joker_rule)
|
|
|
|
end
|
|
|
|
|
|
|
|
def compare_same_type_hands2([], _), do: true
|
|
|
|
|
|
|
|
def compare_same_type_hands2([{card1, card2} | tl], joker_rule) do
|
|
|
|
if card1 == card2 do
|
|
|
|
compare_same_type_hands2(tl, joker_rule)
|
|
|
|
else
|
|
|
|
cond do
|
|
|
|
joker_rule and card1 == ?J -> false
|
|
|
|
joker_rule and card2 == ?J -> true
|
|
|
|
true -> Map.fetch!(@card_to_power, card1) >= Map.fetch!(@card_to_power, card2)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def sort_hands(hands, joker_rule \\ false) do
|
|
|
|
Enum.sort(hands, fn {hand1, _bid1}, {hand2, _bid2} ->
|
|
|
|
type1 = hand_to_type(hand1, joker_rule)
|
|
|
|
type2 = hand_to_type(hand2, joker_rule)
|
|
|
|
|
|
|
|
if type1 == type2 do
|
|
|
|
compare_same_type_hands(hand1, hand2, joker_rule)
|
|
|
|
else
|
|
|
|
Map.fetch!(@type_to_power, type1) >= Map.fetch!(@type_to_power, type2)
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
|
|
|
def part1(input) do
|
|
|
|
input
|
|
|
|
|> sort_hands()
|
|
|
|
|> Enum.reverse()
|
|
|
|
|> Enum.with_index(1)
|
|
|
|
|> Enum.map(fn {{_card, bid}, rank} -> rank * bid end)
|
|
|
|
|> Enum.sum()
|
|
|
|
end
|
|
|
|
|
|
|
|
def part2(input) do
|
|
|
|
input
|
|
|
|
|> sort_hands(true)
|
|
|
|
|> Enum.reverse()
|
|
|
|
|> Enum.with_index(1)
|
|
|
|
|> Enum.map(fn {{_card, bid}, rank} -> rank * bid end)
|
|
|
|
|> Enum.sum()
|
|
|
|
end
|
|
|
|
end
|