Powered by AppSignal & Oban Pro

Day 4

day_4.livemd

Day 4

Part 1

input =
  IO.gets("input: ")
  |> String.trim()
  |> String.split(",")
  |> Enum.map(&Integer.parse(&1))
  |> Enum.map(fn {number, _rest} -> number end)
boards =
  IO.getn("boards: ", 1_000_000)
  |> String.split("\n\n")
  |> Enum.map(&String.split(&1, "\n", trim: true))
  |> Enum.map(fn board ->
    Enum.map(board, fn line ->
      line
      |> String.split(" ", trim: true)
      |> Enum.map(&Integer.parse(&1))
      |> Enum.map(fn {number, _rest} -> {number, false} end)
    end)
  end)
defmodule AdventOfCode.DayFour do
  def solve_part_one(input, boards) do
    solve(input, boards)
  end

  def solve_part_two(input, boards) do
    solve_v2(input, boards)
  end

  defp solve([], boards), do: boards

  defp solve([current | rest], boards) do
    case check_boards(boards, current) do
      {:ok, solution} -> solution
      {:cont, marked_boards} -> solve(rest, marked_boards)
    end
  end

  defp check_boards(boards, current) do
    boards =
      boards
      |> Enum.map(&mark_board(&1, current))

    Enum.reduce(boards, {:cont, boards}, fn board, acc ->
      if check_board(board) do
        {:ok, get_solution(board, current)}
      else
        acc
      end
    end)
  end

  defp solve_v2([], boards), do: boards

  defp solve_v2([current | rest], boards) do
    case check_boards_v2(boards, current) do
      {:ok, solution} -> solution
      {:cont, marked_boards} -> solve_v2(rest, marked_boards)
    end
  end

  defp check_boards_v2([board], current) do
    board = mark_board(board, current)

    if check_board(board) do
      {:ok, get_solution(board, current)}
    else
      {:cont, [board]}
    end
  end

  defp check_boards_v2(boards, current) do
    boards =
      boards
      |> Enum.map(&mark_board(&1, current))
      |> Enum.filter(fn board -> !check_board(board) end)

    {:cont, boards}
  end

  defp mark_board(board, current) do
    Enum.map(
      board,
      fn line ->
        Enum.map(
          line,
          fn {line_number, status} ->
            case status do
              false -> {line_number, line_number == current}
              true -> {line_number, true}
            end
          end
        )
      end
    )
  end

  defp check_board(board) do
    has_winning_col?(board) || has_winning_row?(board)
  end

  defp has_winning_col?(board) do
    Enum.any?(board, fn line -> check_col(line) end)
  end

  defp has_winning_row?(board) do
    board
    |> Enum.zip()
    |> Enum.map(&Tuple.to_list(&1))
    |> has_winning_col?()
  end

  defp check_col(line) do
    line_count = Enum.count(line)

    count =
      line
      |> Enum.filter(&is_marked?(&1))
      |> Enum.count()

    count == line_count
  end

  defp get_solution(board, current) do
    unmarked_sum =
      Enum.reduce(board, 0, fn line, acc ->
        line_sum =
          line
          |> Enum.filter(fn el -> !is_marked?(el) end)
          |> Enum.map(fn {number, _status} -> number end)
          |> Enum.sum()

        line_sum + acc
      end)

    unmarked_sum * current
  end

  defp is_marked?({_number, true}), do: true
  defp is_marked?({_number, false}), do: false
end

IO.puts(AdventOfCode.DayFour.solve_part_one(input, boards))
IO.puts(AdventOfCode.DayFour.solve_part_two(input, boards))
ExUnit.start(autorun: false)

defmodule DayFourTest do
  use ExUnit.Case, async: true

  @example_input [
    7,
    4,
    9,
    5,
    11,
    17,
    23,
    2,
    0,
    14,
    21,
    24,
    10,
    16,
    13,
    6,
    15,
    25,
    12,
    22,
    18,
    20,
    8,
    19,
    3,
    26,
    1
  ]
  @example_boards [
    [
      [{22, false}, {13, false}, {17, false}, {11, false}, {0, false}],
      [{8, false}, {2, false}, {23, false}, {4, false}, {24, false}],
      [{21, false}, {9, false}, {14, false}, {16, false}, {7, false}],
      [{6, false}, {10, false}, {3, false}, {18, false}, {5, false}],
      [{1, false}, {12, false}, {20, false}, {15, false}, {19, false}]
    ],
    [
      [{3, false}, {15, false}, {0, false}, {2, false}, {22, false}],
      [{9, false}, {18, false}, {13, false}, {17, false}, {5, false}],
      [{19, false}, {8, false}, {7, false}, {25, false}, {23, false}],
      [{20, false}, {11, false}, {10, false}, {24, false}, {4, false}],
      [{14, false}, {21, false}, {16, false}, {12, false}, {6, false}]
    ],
    [
      [{14, false}, {21, false}, {17, false}, {24, false}, {4, false}],
      [{10, false}, {16, false}, {15, false}, {9, false}, {19, false}],
      [{18, false}, {8, false}, {23, false}, {26, false}, {20, false}],
      [{22, false}, {11, false}, {13, false}, {6, false}, {5, false}],
      [{2, false}, {0, false}, {12, false}, {3, false}, {7, false}]
    ]
  ]

  test "part 1" do
    assert 4512 = AdventOfCode.DayFour.solve_part_one(@example_input, @example_boards)
  end

  test "part 2" do
    assert 1924 = AdventOfCode.DayFour.solve_part_two(@example_input, @example_boards)
  end
end

ExUnit.run()