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

Day 3

day3/sol.livemd

Day 3

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

Part1

input = Kino.Input.textarea("Enter input here:")
defmodule Day3Part1 do
  def solve(input) do
    grid =
      String.split(input, "\n", trim: true)
      |> Enum.map(&(String.to_charlist(&1) |> List.to_tuple()))
      |> List.to_tuple()

    num_rows = tuple_size(grid)
    num_cols = tuple_size(elem(grid, 0))

    parse_grid(0, 0, num_rows, num_cols, grid)
    |> Enum.filter(fn {_, ok?} -> ok? end)
    |> Enum.map(fn {x, _} -> x end)
    |> Enum.sum()
  end

  defp parse_grid(r, c, num_rows, num_cols, grid) when c == num_cols do
    parse_grid(r + 1, 0, num_cols, num_rows, grid)
  end

  defp parse_grid(r, _, num_rows, _, _) when r == num_rows do
    # this is the base case, just return back an empty list
    []
  end

  defp parse_grid(r, c, num_rows, num_cols, grid) do
    chr = elem(elem(grid, r), c)

    cond do
      chr in 48..57 -> parse_number(r, c, num_rows, num_cols, grid, 0, false)
      true -> parse_grid(r, c + 1, num_rows, num_cols, grid)
    end
  end

  defp parse_number(r, c, num_rows, num_cols, grid, num, ok?) when c < num_cols and c >= 0 do
    chr = elem(elem(grid, r), c)

    if chr in 48..57 do
      feasible? =
        case ok? do
          true ->
            true

          false ->
            Enum.any?([
              check_feasibility(r + 1, c, num_rows, num_cols, grid),
              check_feasibility(r - 1, c, num_rows, num_cols, grid),
              check_feasibility(r, c + 1, num_rows, num_cols, grid),
              check_feasibility(r, c - 1, num_rows, num_cols, grid),
              check_feasibility(r + 1, c + 1, num_rows, num_cols, grid),
              check_feasibility(r + 1, c - 1, num_rows, num_cols, grid),
              check_feasibility(r - 1, c + 1, num_rows, num_cols, grid),
              check_feasibility(r - 1, c - 1, num_rows, num_cols, grid)
            ])
        end

      parse_number(r, c + 1, num_rows, num_cols, grid, num * 10 + (chr - 48), feasible?)
    else
      [{num, ok?} | parse_grid(r, c, num_rows, num_cols, grid)]
    end
  end

  defp parse_number(r, c, num_rows, num_cols, grid, num, ok?),
    do: [{num, ok?} | parse_grid(r, c, num_rows, num_cols, grid)]

  defp check_feasibility(r, c, num_rows, num_cols, grid)
       when r < num_rows and c < num_cols and r >= 0 and c >= 0 do
    chr = elem(elem(grid, r), c)

    cond do
      chr in 48..57 -> false
      # dot
      chr == 46 -> false
      true -> true
    end
  end

  defp check_feasibility(_, _, _, _, _), do: false
end
{:module, Day3Part1, <<70, 79, 82, 49, 0, 0, 19, ...>>, {:check_feasibility, 5}}
input
|> Kino.Input.read()
|> Day3Part1.solve()
550064
defmodule Day3Part2 do
  def solve(input) do
    grid =
      String.split(input, "\n", trim: true)
      |> Enum.map(&amp;(String.to_charlist(&amp;1) |> List.to_tuple()))
      |> List.to_tuple()

    num_rows = tuple_size(grid)
    num_cols = tuple_size(elem(grid, 0))

    parse_grid(0, 0, num_rows, num_cols, grid)
    |> Enum.filter(&amp;(length(&amp;1) == 2))
    |> Enum.map(&amp;Enum.product(&amp;1))
    |> Enum.sum()
  end

  defp parse_grid(r, c, num_rows, num_cols, grid) when c == num_cols do
    parse_grid(r + 1, 0, num_cols, num_rows, grid)
  end

  defp parse_grid(r, _, num_rows, _, _) when r == num_rows do
    # this is the base case, just return back an empty list
    []
  end

  defp parse_grid(r, c, num_rows, num_cols, grid) do
    chr = elem(elem(grid, r), c)

    if chr == 42 do
      # asterisk/gear
      [
        parse_gear(r, c, num_rows, num_cols, grid)
        | parse_grid(r, c + 1, num_rows, num_cols, grid)
      ]
    else
      parse_grid(r, c + 1, num_rows, num_cols, grid)
    end
  end

  defp parse_gear(r, c, num_rows, num_cols, grid) do
    {tl, tc, tr} = {
      get_neighbor(r - 1, c - 1, num_rows, num_cols, grid),
      get_neighbor(r - 1, c, num_rows, num_cols, grid),
      get_neighbor(r - 1, c + 1, num_rows, num_cols, grid)
    }

    {bl, bc, br} = {
      get_neighbor(r + 1, c - 1, num_rows, num_cols, grid),
      get_neighbor(r + 1, c, num_rows, num_cols, grid),
      get_neighbor(r + 1, c + 1, num_rows, num_cols, grid)
    }

    top_row =
      cond do
        is_digit?(tl) and is_digit?(tc) and is_digit?(tr) ->
          [parse_number(r - 1, c - 1, num_cols, grid)]

        is_digit?(tl) and is_digit?(tc) and not is_digit?(tr) ->
          [parse_number(r - 1, c - 1, num_cols, grid)]

        is_digit?(tl) and not is_digit?(tc) and is_digit?(tr) ->
          [parse_number(r - 1, c - 1, num_cols, grid), parse_number(r - 1, c + 1, num_cols, grid)]

        is_digit?(tl) and not is_digit?(tc) and not is_digit?(tr) ->
          [parse_number(r - 1, c - 1, num_cols, grid)]

        not is_digit?(tl) and is_digit?(tc) and is_digit?(tr) ->
          [parse_number(r - 1, c, num_cols, grid)]

        not is_digit?(tl) and is_digit?(tc) and not is_digit?(tr) ->
          [parse_number(r - 1, c, num_cols, grid)]

        not is_digit?(tl) and not is_digit?(tc) and is_digit?(tr) ->
          [parse_number(r - 1, c + 1, num_cols, grid)]

        true ->
          []
      end

    bot_row =
      cond do
        is_digit?(bl) and is_digit?(bc) and is_digit?(br) ->
          [parse_number(r + 1, c - 1, num_cols, grid)]

        is_digit?(bl) and is_digit?(bc) and not is_digit?(br) ->
          [parse_number(r + 1, c - 1, num_cols, grid)]

        is_digit?(bl) and not is_digit?(bc) and is_digit?(br) ->
          [parse_number(r + 1, c - 1, num_cols, grid), parse_number(r + 1, c + 1, num_cols, grid)]

        is_digit?(bl) and not is_digit?(bc) and not is_digit?(br) ->
          [parse_number(r + 1, c - 1, num_cols, grid)]

        not is_digit?(bl) and is_digit?(bc) and is_digit?(br) ->
          [parse_number(r + 1, c, num_cols, grid)]

        not is_digit?(bl) and is_digit?(bc) and not is_digit?(br) ->
          [parse_number(r + 1, c, num_cols, grid)]

        not is_digit?(bl) and not is_digit?(bc) and is_digit?(br) ->
          [parse_number(r + 1, c + 1, num_cols, grid)]

        true ->
          []
      end

    left =
      case get_neighbor(r, c - 1, num_rows, num_cols, grid) |> is_digit?() do
        true -> [parse_number(r, c - 1, num_cols, grid)]
        false -> []
      end

    right =
      case get_neighbor(r, c + 1, num_rows, num_cols, grid) |> is_digit?() do
        true -> [parse_number(r, c + 1, num_cols, grid)]
        false -> []
      end

    top_row ++ bot_row ++ left ++ right
  end

  defp parse_number(r, c, num_cols, grid) do
    {fr, fc} = get_first_digit(r, c, num_cols, grid)
    parse_num_from_start(fr, fc, num_cols, grid, 0)
  end

  defp parse_num_from_start(r, c, num_cols, grid, num) when c < num_cols do
    chr = elem(elem(grid, r), c)

    if chr in 48..57 do
      parse_num_from_start(r, c + 1, num_cols, grid, num * 10 + (chr - 48))
    else
      num
    end
  end

  defp parse_num_from_start(_, _, _, _, num), do: num

  defp get_first_digit(r, c, num_cols, grid) when c < num_cols and c >= 0 do
    chr = elem(elem(grid, r), c)

    if chr in 48..57 do
      get_first_digit(r, c - 1, num_cols, grid)
    else
      {r, c + 1}
    end
  end

  defp get_first_digit(r, c, _, _), do: {r, c + 1}

  defp is_digit?(d), do: d in 48..58

  defp get_neighbor(r, c, num_rows, num_cols, grid)
       when r < num_rows and r >= 0 and c < num_cols and c >= 0 do
    elem(elem(grid, r), c)
  end

  # ascii code of a dot
  defp get_neighbor(_, _, _, _, _), do: 46
end
{:module, Day3Part2, <<70, 79, 82, 49, 0, 0, 35, ...>>, {:get_neighbor, 5}}
input
|> Kino.Input.read()
|> Day3Part2.solve()
85010461