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

Day 07

2023/day-07.livemd

Day 07

Mix.install([
  {:flow, "~> 1.2"},
  {:kino, "~> 0.11"},
  {:nimble_parsec, "~> 1.4"}
])

example_input =
  Kino.Input.textarea("example input:")
  |> Kino.render()

real_input = Kino.Input.textarea("real input:")

Common

defmodule Parser do
  import NimbleParsec

  hand = ascii_string([], 5) |> post_traverse(:to_hand)
  bid = integer(min: 1)

  hand_and_bid =
    hand
    |> ignore(string(" "))
    |> concat(bid)
    |> wrap()
    |> ignore(optional(ascii_char([?\n])))

  list_of_hands =
    hand_and_bid
    |> times(min: 1)

  defparsec(:parse, list_of_hands |> eos())

  defp to_hand(rest, [args], context, _line, _offset) do
    {rest, [context.to_hand.(args)], context}
  end
end

Part 1

defmodule Hand do
  defstruct [:card_strength, :hand_strength]

  @card_strengths %{
    ?2 => 1,
    ?3 => 2,
    ?4 => 3,
    ?5 => 4,
    ?6 => 5,
    ?7 => 6,
    ?8 => 7,
    ?9 => 8,
    ?T => 9,
    ?J => 10,
    ?Q => 11,
    ?K => 12,
    ?A => 13
  }

  def compare(a, b) when a.hand_strength > b.hand_strength, do: :gt
  def compare(a, b) when a.hand_strength < b.hand_strength, do: :lt
  def compare(a, b) when a.card_strength == b.card_strength, do: :eq
  def compare(a, b) when a.card_strength > b.card_strength, do: :gt
  def compare(a, b) when a.card_strength < b.card_strength, do: :ld

  def new(string) do
    chars = to_charlist(string)
    sorted = Enum.sort_by(chars, &amp;@card_strengths[&amp;1])

    %__MODULE__{
      card_strength: card_strength(chars),
      hand_strength: hand_strength(sorted)
    }
  end

  defp card_strength(chars), do: Enum.map(chars, &amp;@card_strengths[&amp;1])

  defp hand_strength([a, a, a, a, a]), do: 6
  defp hand_strength([a, a, a, a, _]), do: 5
  defp hand_strength([_, a, a, a, a]), do: 5
  defp hand_strength([a, a, a, b, b]), do: 4
  defp hand_strength([b, b, a, a, a]), do: 4
  defp hand_strength([a, a, a, _, _]), do: 3
  defp hand_strength([_, a, a, a, _]), do: 3
  defp hand_strength([_, _, a, a, a]), do: 3
  defp hand_strength([a, a, b, b, _]), do: 2
  defp hand_strength([a, a, _, b, b]), do: 2
  defp hand_strength([_, a, a, b, b]), do: 2
  defp hand_strength([a, a, _, _, _]), do: 1
  defp hand_strength([_, a, a, _, _]), do: 1
  defp hand_strength([_, _, a, a, _]), do: 1
  defp hand_strength([_, _, _, a, a]), do: 1
  defp hand_strength([_, _, _, _, _]), do: 0
end

ExUnit.start(autorun: false)

defmodule HandTest do
  use ExUnit.Case, async: true

  describe "compare/2" do
    test "returns :gt when first hand is stronger" do
      assert :gt = Hand.compare(Hand.new("QQQJA"), Hand.new("T55J5"))
    end

    test "returns :lt when first hand is weaker" do
      assert :lt = Hand.compare(Hand.new("KK677"), Hand.new("T55J5"))
    end

    test "returns :eq when hands are equal" do
      assert :eq = Hand.compare(Hand.new("KK677"), Hand.new("KK677"))
    end
  end
end

ExUnit.run()

hands =
  real_input
  |> Kino.Input.read()
  |> Parser.parse(context: %{to_hand: &amp;Hand.new/1})
  |> case do
    {:ok, acc, "", _, _, _} -> acc
  end
  |> Enum.sort_by(&amp;hd/1, Hand)
  |> Enum.with_index(1)
  |> Enum.reduce(0, fn {[_, bid], idx}, acc -> acc + bid * idx end)

Part 2

defmodule Hand2 do
  defstruct [:card_strength, :hand_strength]

  @card_strengths %{
    ?J => 0,
    ?2 => 1,
    ?3 => 2,
    ?4 => 3,
    ?5 => 4,
    ?6 => 5,
    ?7 => 6,
    ?8 => 7,
    ?9 => 8,
    ?T => 9,
    ?Q => 11,
    ?K => 12,
    ?A => 13
  }

  def compare(a, b) when a.hand_strength > b.hand_strength, do: :gt
  def compare(a, b) when a.hand_strength < b.hand_strength, do: :lt
  def compare(a, b) when a.card_strength == b.card_strength, do: :eq
  def compare(a, b) when a.card_strength > b.card_strength, do: :gt
  def compare(a, b) when a.card_strength < b.card_strength, do: :lt

  def new(string) do
    chars = to_charlist(string)
    sorted = Enum.sort_by(chars, &amp;@card_strengths[&amp;1], :desc) |> replace_jokers()

    %__MODULE__{
      card_strength: card_strength(chars),
      hand_strength: hand_strength(sorted)
    }
  end

  defp card_strength(chars), do: Enum.map(chars, &amp;@card_strengths[&amp;1])

  defp hand_strength([a, a, a, a, a]), do: 6
  defp hand_strength([a, a, a, a, _]), do: 5
  defp hand_strength([_, a, a, a, a]), do: 5
  defp hand_strength([a, a, a, b, b]), do: 4
  defp hand_strength([b, b, a, a, a]), do: 4
  defp hand_strength([a, a, a, _, _]), do: 3
  defp hand_strength([_, a, a, a, _]), do: 3
  defp hand_strength([_, _, a, a, a]), do: 3
  defp hand_strength([a, a, b, b, _]), do: 2
  defp hand_strength([a, a, _, b, b]), do: 2
  defp hand_strength([_, a, a, b, b]), do: 2
  defp hand_strength([a, a, _, _, _]), do: 1
  defp hand_strength([_, a, a, _, _]), do: 1
  defp hand_strength([_, _, a, a, _]), do: 1
  defp hand_strength([_, _, _, a, a]), do: 1
  defp hand_strength([_, _, _, _, _]), do: 0

  defp replace_jokers([a, a, a, a, ?J]), do: [a, a, a, a, a]
  defp replace_jokers([a, a, a, ?J, ?J]), do: [a, a, a, a, a]
  defp replace_jokers([a, a, ?J, ?J, ?J]), do: [a, a, a, a, a]
  defp replace_jokers([a, ?J, ?J, ?J, ?J]), do: [a, a, a, a, a]

  defp replace_jokers([a, a, a, b, ?J]), do: [a, a, a, a, b]
  defp replace_jokers([a, a, b, ?J, ?J]), do: [a, a, a, a, b]
  defp replace_jokers([a, b, ?J, ?J, ?J]), do: [a, a, a, a, b]
  defp replace_jokers([a, b, b, b, ?J]), do: [a, b, b, b, b]
  defp replace_jokers([a, b, b, ?J, ?J]), do: [a, b, b, b, b]

  defp replace_jokers([a, a, b, b, ?J]), do: [a, a, a, b, b]

  defp replace_jokers([a, a, b, c, ?J]), do: [a, a, a, b, c]
  defp replace_jokers([a, b, c, ?J, ?J]), do: [a, a, a, b, c]
  defp replace_jokers([a, b, b, c, ?J]), do: [a, b, b, b, c]
  defp replace_jokers([a, b, c, c, ?J]), do: [a, b, c, c, c]

  defp replace_jokers([a, b, c, d, ?J]), do: [a, a, b, c, d]

  defp replace_jokers(h), do: h
end

ExUnit.start(autorun: false)

defmodule Hand2Test do
  use ExUnit.Case, async: true

  describe "compare/2" do
    test "returns :gt when first hand is stronger because of jokers" do
      assert :gt = Hand2.compare(Hand2.new("KTJJT"), Hand2.new("QQQJA"))
    end

    test "returns :gt when first hand is stronger" do
      assert :gt = Hand2.compare(Hand2.new("QQQJA"), Hand2.new("T55J5"))
    end

    test "returns :lt when first hand is weaker" do
      assert :lt = Hand2.compare(Hand2.new("KK677"), Hand2.new("T55J5"))
    end

    test "returns :eq when hands are equal" do
      assert :eq = Hand2.compare(Hand2.new("KK677"), Hand2.new("KK677"))
    end
  end
end

ExUnit.run()

hands =
  real_input
  |> Kino.Input.read()
  |> Parser.parse(context: %{to_hand: &amp;Hand2.new/1})
  |> case do
    {:ok, acc, "", _, _, _} -> acc
  end
  |> Enum.sort_by(&amp;hd/1, Hand2)
  |> Enum.with_index(1)
  |> Enum.reduce(0, fn {[_, bid], idx}, acc -> acc + bid * idx end)