my-solutions/advent-of-code-2023/elixir/day_07/lib/solution.ex

169 lines
3.2 KiB
Elixir

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