Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Day 7: Camel cards – Advent of Code 2023

2023/07.livemd

Day 7: Camel cards – Advent of Code 2023

input =
  File.stream!("/Users/pw/src/weiland/adventofcode/2023/input/07.txt")
  |> Stream.map(&String.trim/1)

test_input = "32T3K 765
T55J5 684
KK677 28
KTJJT 220
QQQJA 483" |> String.split("\n")

Parsing

parse_hand_joker = fn hand, with_joker ->
  String.graphemes(hand)
  |> Enum.map(fn grapheme ->
    case grapheme do
      "T" -> 10
      "J" -> if with_joker, do: 1, else: 11
      "Q" -> 12
      "K" -> 13
      "A" -> 14
      _ -> String.to_integer(grapheme)
    end
  end)
end

parse_hand = fn hand ->
  parse_hand_joker.(hand, false)
end

traditional_score = fn lst ->
  cond do
    # :five_of_a_kind
    Enum.count(lst) == 1 -> 7
    # :four_of_a_kind
    Enum.count(lst) == 2 && Enum.max(lst) == 4 -> 6
    # :full_house
    Enum.count(lst) == 2 -> 5
    # :three_of_kind
    Enum.count(lst) == 3 && Enum.max(lst) == 3 -> 4
    # :two_pair
    Enum.count(lst) == 3 -> 3
    # :one_pair
    Enum.count(lst) == 4 -> 2
    # :high_card
    Enum.count(lst) == 5 -> 1
  end
end

score_from_map = fn map ->
  map
  |> Map.to_list()
  |> Enum.map(fn {_value, char_list} -> Enum.count(char_list) end)
  |> traditional_score.()
end

get_score = fn graphemes ->
  graphemes
  |> Enum.group_by(& &1)
  |> score_from_map.()
end

get_joker_score = fn hand ->
  map = Enum.group_by(hand, & &1)

  has_jokers = Map.get(map, 1)

  if has_jokers do
    rest_map = Map.drop(map, [1])
    rest_values = Map.values(rest_map)
    rest_count = Enum.count(rest_values)
    jokers_count = Enum.count(has_jokers)

    cond do
      # :five_of_a_kind
      jokers_count == 5 || jokers_count == 4 ->
        7

      # :five_of_a_kind
      jokers_count == 3 && rest_count == 1 ->
        7

      # :four_of
      jokers_count == 3 ->
        6

      # :five_of_a_kind
      jokers_count == 2 && rest_count == 1 ->
        7

      # :four_of
      jokers_count == 2 && rest_count == 2 ->
        6

      # :three_of_kinds
      jokers_count == 2 ->
        4

      # :five_of
      jokers_count == 1 && rest_count == 1 ->
        7

      # 1,3; 2,2; 3,1;
      jokers_count == 1 && rest_count == 2 &&
          Enum.map(rest_values, &Enum.count/1) |> Enum.max() == 3 ->
        6

      # :full_house 1,3; 2,2; 3,1;
      jokers_count == 1 && rest_count == 2 ->
        5

      # :three_of_kind one has two -> XJJ, Y, Z
      jokers_count == 1 && rest_count == 3 ->
        4

      # :one_pair
      true ->
        2
    end
  else
    score_from_map.(map)
  end
end

parse_for_part_one = fn input ->
  input
  |> Enum.map(&String.split(&1, " "))
  |> Enum.map(fn [h, b] -> [parse_hand.(h), String.to_integer(b), get_score.(parse_hand.(h))] end)
end

parse_for_part_two = fn input ->
  input
  |> Enum.map(&String.split(&1, " "))
  |> Enum.map(fn [h, b] ->
    hand = parse_hand_joker.(h, true)
    [hand, String.to_integer(b), get_joker_score.(hand)]
  end)
end

parse_for_part_two.(test_input)

Part One

sorter = fn [ah, _ab, a_score], [bh, _bb, b_score] ->
  if a_score == b_score, do: ah < bh, else: a_score < b_score
end

map_to_rank = fn lst ->
  lst
  |> Enum.with_index(1)
  |> Enum.map(fn {[_h, bid, _s], i} -> i * bid end)
  |> Enum.sum()
end

part_one = fn input ->
  input
  |> parse_for_part_one.()
  |> Enum.sort(&amp;sorter.(&amp;1, &amp;2))
  |> map_to_rank.()
end

IO.inspect(part_one.(test_input) == 6440, label: "test part one")

part_one.(input)

Part Two

part_two = fn input ->
  input
  |> parse_for_part_two.()
  |> Enum.sort(&amp;sorter.(&amp;1, &amp;2))
  |> map_to_rank.()
end

part_two.(test_input) == 5905

part_two.(input)