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

AoC 2023 Day 7

2023/day7.livemd

AoC 2023 Day 7

Mix.install([
  {:kino_aoc, "~> 0.1.5"}
])

Input

{:ok, puzzle_input} =
  KinoAOC.download_puzzle("2023", "7", System.fetch_env!("LB_AOC_COOKIE_SECRET"))

Parsing

defmodule Hand do
  defstruct [:cards, :bid]

  def parse(<> <> " " <> bid) do
    %Hand{
      cards: map_cards(cards),
      bid: String.to_integer(bid)
    }
  end

  def map_cards(cards) do
    cards
    |> String.codepoints()
    |> Enum.map(&amp;map_card/1)
  end

  def map_card("T"), do: 10
  def map_card("J"), do: 11
  def map_card("Q"), do: 12
  def map_card("K"), do: 13
  def map_card("A"), do: 14
  def map_card(num), do: String.to_integer(num)

  def type(%Hand{cards: cards}) do
    sets =
      cards
      |> Enum.sort()
      |> Enum.chunk_by(&amp; &amp;1)
      |> Enum.sort_by(&amp;length/1, :desc)

    case sets do
      # Five of a kind
      [l] when length(l) == 5 -> 5
      # Four of a kind
      [l | _] when length(l) == 4 -> 4
      # Full house
      [l, m | _] when length(l) == 3 and length(m) == 2 -> 3
      # Three of a kind 
      [l | _] when length(l) == 3 -> 2
      # Two pair
      [l, m | _] when length(l) == 2 and length(m) == 2 -> 1
      # One pair
      [l | _] when length(l) == 2 -> 0
      # High card
      _ -> -1
    end
  end

  def compare(a, b) do
    a_type = type(a)
    b_type = type(b)

    Enum.zip([a_type | a.cards], [b_type | b.cards])
    |> compare()
  end

  def compare([{same, same} | rest]), do: compare(rest)
  def compare([{a, b} | rest]), do: a < b
end
hands =
  puzzle_input
  |> String.split("\n", trim: true)
  |> Enum.map(&amp;Hand.parse/1)

Part 1

hands
|> Enum.sort(&amp;Hand.compare/2)
|> Enum.with_index(1)
|> Enum.map(fn {%Hand{bid: bid}, i} -> bid * i end)
|> Enum.sum()

Part 2

defmodule HandWithJokers do
  def parse(<> <> " " <> bid) do
    %Hand{
      cards: map_cards(cards),
      bid: String.to_integer(bid)
    }
  end

  def map_cards(cards) do
    cards
    |> String.codepoints()
    |> Enum.map(&amp;map_card/1)
  end

  def map_card("T"), do: 10
  def map_card("J"), do: 1
  def map_card("Q"), do: 11
  def map_card("K"), do: 12
  def map_card("A"), do: 13
  def map_card(num), do: String.to_integer(num)

  def type(%Hand{cards: cards}) do
    sets =
      cards
      |> Enum.sort()
      |> Enum.chunk_by(&amp; &amp;1)
      |> Enum.sort_by(&amp;length/1, :desc)

    jokers = Enum.count(cards, &amp;(&amp;1 == 1))

    sets_type(sets, jokers)
  end

  # This is not quite right
  def sets_type(sets, jokers) do
    case sets do
      # When the largest set is jokers
      [[1 | _] | rest] -> sets_type(rest, jokers)
      # Five of a kind
      [l | _] when length(l) + jokers >= 5 -> 5
      # Four of a kind
      [l | _] when length(l) + jokers >= 4 -> 4
      # Full house
      [l, m | _] when length(l) + length(m) + jokers >= 5 -> 3
      # Three of a kind 
      [l | _] when length(l) + jokers >= 3 -> 2
      # Two pair
      [l, m | _] when length(l) + length(m) + jokers >= 2 -> 1
      # One pair
      [l | _] when length(l) + jokers >= 2 -> 0
      # High card
      _ -> -1
    end
  end

  def compare(a, b) do
    a_type = type(a)
    b_type = type(b)

    Enum.zip([a_type | a.cards], [b_type | b.cards])
    |> compare()
  end

  def compare([{same, same} | rest]), do: compare(rest)
  def compare([{a, b} | _]), do: a < b
end
hands =
  puzzle_input
  |> String.split("\n", trim: true)
  |> Enum.map(&amp;HandWithJokers.parse/1)
hands
|> Enum.sort(&amp;HandWithJokers.compare/2)
|> Enum.with_index(1)
|> Enum.map(fn {%Hand{bid: bid}, i} -> bid * i end)
|> Enum.sum()