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

Day 7

2023/07.livemd

Day 7

Mix.install([
  {:kino, "~> 0.11.3"}
])

Input

example = """
32T3K 765
T55J5 684
KK677 28
KTJJT 220
QQQJA 483
"""

input = Kino.Input.textarea("Input", default: example)

Part 1

expected = [765 * 1, 220 * 2, 28 * 3, 684 * 4, 483 * 5]
[765, 440, 84, 2736, 2415]
defmodule Part1 do
  def parse(input, joker \\ 11) do
    for line <- String.split(input, "\n", trim: true) do
      [hand, bid] = String.split(line, " ")

      labels =
        for c <- String.to_charlist(hand) do
          case c do
            ?T -> 10
            ?J -> joker
            ?Q -> 12
            ?K -> 13
            ?A -> 14
            _ -> c - ?0
          end
        end

      {labels, String.to_integer(bid)}
    end
  end

  def type(hand) do
    freq =
      Enum.frequencies(hand)
      |> Map.values()
      |> Enum.sort(:desc)

    case freq do
      [5] -> 7
      [4, 1] -> 6
      [3, 2] -> 5
      [3, 1, 1] -> 4
      [2, 2, 1] -> 3
      [2, 1, 1, 1] -> 2
      [1, 1, 1, 1, 1] -> 1
    end
  end

  def tiebreak(list) do
    Enum.sort_by(list, &amp;elem(&amp;1, 0))
    |> Enum.map(&amp;elem(&amp;1, 1))
  end

  def run(input) do
    parse(input)
    |> Enum.group_by(fn {hand, _} -> type(hand) end)
    |> Enum.sort_by(&amp;elem(&amp;1, 0))
    |> Enum.flat_map(fn {_type, list} -> tiebreak(list) end)
    |> Enum.with_index(1)
    |> Enum.map(fn {bid, rank} -> bid * rank end)
  end
end

Part1.run(example) == expected
true
Kino.Input.read(input)
|> Part1.run()
|> Enum.sum()
|> then(&amp;Kino.Markdown.new("Part 1: #{&amp;1}"))

Part 2

expected = 5905
5905
defmodule Part2 do
  def replace_zero(hand, replacement) do
    Enum.map(hand, fn
      0 -> replacement
      n -> n
    end)
  end

  def type(hand) do
    jokers = Enum.count(hand, &amp;(&amp;1 == 0))

    if jokers in [0, 5] do
      Part1.type(hand)
    else
      hand
      |> Enum.uniq()
      |> Enum.map(&amp;replace_zero(hand, &amp;1))
      |> Enum.map(&amp;Part1.type/1)
      |> Enum.max()
    end
  end

  def run(input) do
    Part1.parse(input, 0)
    |> Enum.group_by(fn {hand, _} -> type(hand) end)
    |> Enum.sort_by(&amp;elem(&amp;1, 0))
    |> Enum.flat_map(fn {_type, list} -> Part1.tiebreak(list) end)
    |> Enum.with_index(1)
    |> Enum.map(fn {bid, rank} -> bid * rank end)
    |> Enum.sum()
  end
end

Part2.run(example) == expected
true
Kino.Input.read(input)
|> Part2.run()
|> then(&amp;Kino.Markdown.new("Part 2: #{&amp;1}"))