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

Day4

2023/elixir/day4.livemd

Day4

Mix.install([
  {:kino_aoc, git: "https://github.com/ljgago/kino_aoc"},
  {:benchee, "~> 1.0"}
])

Get Input

{:ok, data} = KinoAOC.download_puzzle("2023", "4", System.fetch_env!("LB_AOC_SECRET"))

Task1

defmodule Day4 do
  def parse(row) do
    [_, card] = row |> String.split(": ", trim: true)

    card
    |> String.split(" | ", trim: true)
    |> Enum.map(fn set ->
      set |> String.split() |> MapSet.new()
    end)
    |> List.to_tuple()
  end

  def task1(card) do
    n = count_win(card)
    (n > 0 && 2 ** (n - 1)) || 0
  end

  def task2(cards) do
    acc = 1..length(cards) |> Enum.map(fn n -> {n, 1} end) |> Enum.into(%{})

    cards
    |> Enum.reduce({acc, 1}, fn card, acc ->
      card |> count_win() |> update_copies(acc)
    end)
    |> elem(0)
    |> Map.values()
  end

  def update_copies(0, {acc, i}), do: {acc, i + 1}

  def update_copies(n, {acc, i}) do
    acc =
      (i + 1)..(i + n)
      |> Enum.reduce(acc, fn k, acc ->
        Map.put(acc, k, acc[k] + acc[i])
      end)

    {acc, i + 1}
  end

  def count_win({win, nums}) do
    win |> MapSet.intersection(nums) |> MapSet.size()
  end

  def out(res, t), do: IO.puts("Res #{t}: #{res}")
end

dt = """
Card 1: 41 48 83 86 17 | 83 86  6 31 17  9 48 53
Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19
Card 3:  1 21 53 59 44 | 69 82 63 72 16 21 14  1
Card 4: 41 92 73 84 69 | 59 84 76 51 58  5 54 83
Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36
Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11
"""

data
|> String.split("\n", trim: true)
|> Enum.map(&Day4.parse/1)
|> Enum.map(&Day4.task1/1)
|> Enum.sum()
|> Day4.out("task1")

data
|> String.split("\n", trim: true)
|> Enum.map(&Day4.parse/1)
|> Day4.task2()
|> Enum.sum()
|> Day4.out("task2")

Bench

Benchee.run(%{
  "day_4_part1" => fn ->
    data
    |> String.split("\n", trim: true)
    |> Enum.map(&Day4.parse/1)
    |> Enum.map(&Day4.task1/1)
    |> Enum.sum()
  end,
  "day_4_part2" => fn ->
    data
    |> String.split("\n", trim: true)
    |> Enum.map(&Day4.parse/1)
    |> Day4.task2()
    |> Enum.sum()
  end
})