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 freqs = Enum.frequencies(hand) |> Enum.reject(fn {card, _count} -> card == ?J end) cond do Enum.any?(freqs, fn {_card, count} -> count == 4 end) -> {card, _} = Enum.find(freqs, fn {_card, count} -> count == 4 end) card Enum.any?(freqs, fn {_card, count} -> count == 3 end) -> {card, _} = Enum.find(freqs, fn {_card, count} -> count == 3 end) card Enum.any?(freqs, fn {_card, count} -> count == 2 end) -> doubles = Enum.filter(freqs, fn {_card, count} -> count == 2 end) if length(doubles) == 1 do hd(doubles) |> elem(0) else [{double1, _}, {double2, _}] = doubles if Map.fetch!(@card_to_power, double1) >= Map.fetch!(@card_to_power, double2) do double1 else double2 end end true -> freqs |> Enum.map(fn {card, _count} -> card end) |> Enum.sort(fn card1, card2 -> 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) case joker_count do 0 -> hand 5 -> ~c"AAAAA" _ -> joker_replacement = best_joker_replacement(hand) replace_jokers(hand, joker_replacement) end end def hand_to_type(hand, joker_rule) do 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) 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 Enum.count(freqs, &(&1 == 2)) == 2 -> :two_pair 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