back home

Day - 3

Part 1 - Code

      
defmodule DayThree do
  @year 2023
  @day 3
  @filepath "data.txt"

  def line_to_tokens({line, idx}) do
    pattern = ~r/\d+/
    sym_pattern = ~r/[^a-zA-Z0-9.]/

    tuple_to_struct = fn {f, l} ->
      %{index: f, binary: binary_part(line, f, l), size: l, row: idx}
    end

    part_numbers =
      Regex.scan(pattern, line, return: :index)
      |> Enum.flat_map(& &1)
      |> Enum.map(tuple_to_struct)

    specials =
      Regex.scan(sym_pattern, line, return: :index)
      |> Enum.flat_map(& &1)
      |> Enum.map(tuple_to_struct)

    %{
      nums: part_numbers,
      specials: specials
    }
  end

  def tokens_to_coors(data) do
    index = Map.get(data, :index)
    row = Map.get(data, :row)

    {
      {row, index},
      data
    }
  end

  def get_neighborhood(num, row_bounds, col_bounds) do
    {row, index, size} = {Map.get(num, :row), Map.get(num, :index), Map.get(num, :size)}

    top = for i <- (index - 1)..(index + size), do: {row - 1, i}
    bottom = for i <- (index - 1)..(index + size), do: {row + 1, i}
    sides = [{row, index - 1}, {row, index + size}]
    all = top ++ bottom ++ sides

    all |> Enum.filter(fn {r, c} -> 0 <= r && r < row_bounds && 0 <= c && c < col_bounds end)
  end

  def check_if_near_symbol(num, symbol_map, row_bounds, col_bounds) do
    get_neighborhood(num, row_bounds, col_bounds)
    |> Enum.map(&Map.has_key?(symbol_map, &1))
    |> Enum.any?(&(&1 == true))
  end

  def get_gear_power(rc, list, row_bounds, col_bounds) do
    row = rc.row

    compute = fn gear, num ->
      s_r = gear.row
      s_c = gear.index

      nei =
        [
          {s_r - 1, s_c - 1},
          {s_r - 1, s_c},
          {s_r - 1, s_c + 1},
          {s_r, s_c - 1},
          {s_r, s_c + 1},
          {s_r + 1, s_c - 1},
          {s_r + 1, s_c},
          {s_r + 1, s_c + 1}
        ]
        |> Enum.filter(fn {r, c} -> 0 <= r && r < row_bounds && 0 <= c && c < col_bounds end)
        |> Enum.into(MapSet.new())

      n_r = num.row
      n_c = num.index
      num_span = for n <- n_c..(n_c + num.size - 1), do: {n_r, n}
      num_span = num_span |> Enum.into(MapSet.new())

      MapSet.intersection(nei, num_span)
      |> MapSet.size() > 0
    end

    out =
      [Enum.at(list, row - 1), Enum.at(list, row), Enum.at(list, row + 1)]
      |> Enum.filter(&(&1 != nil))
      |> Enum.flat_map(&Map.get(&1, :nums))
      |> Enum.filter(&compute.(rc, &1))

    case out |> length() do
      2 ->
        out
        |> Enum.map(&Map.get(&1, :binary))
        |> Enum.map(&String.to_integer/1)
        |> Enum.reduce(1, fn a, b -> a * b end)

      _ ->
        0
    end
  end

  def row_to_gear_power(row, list, row_bounds, col_bounds) do
    filter_fn = fn m ->
      case m.binary do
        "*" -> DayThree.get_gear_power(m, list, row_bounds, col_bounds)
        _ -> 0
      end
    end

    row
    |> Map.get(:specials)
    |> Enum.map(filter_fn)
    |> Enum.sum()
  end

  def solve_part_1(matrix, row_bounds, col_bounds) do
    all_elements = matrix |> Enum.reduce(%{}, &Map.merge(&1, &2, fn _, v1, v2 -> v1 ++ v2 end))

    symbol_map =
      all_elements
      |> Map.get(:specials)
      |> Enum.map(&DayThree.tokens_to_coors/1)
      |> Enum.into(%{})

    part_numbers_adjacent_to_symbols =
      all_elements
      |> Map.get(:nums)
      |> Enum.filter(&DayThree.check_if_near_symbol(&1, symbol_map, row_bounds, col_bounds))

    sum_of_part_nums =
      part_numbers_adjacent_to_symbols
      |> Enum.map(&Map.get(&1, :binary))
      |> Enum.map(&String.to_integer/1)
      |> Enum.sum()

    IO.inspect(sum_of_part_nums, label: "Solution 1")
    :exit
  end

  def solve_part_2(matrix, row_bounds, col_bounds) do
    sum_of_gear_power =
      matrix
      |> Enum.with_index(0)
      |> Enum.map(&DayThree.row_to_gear_power(elem(&1, 0), matrix, row_bounds, col_bounds))
      |> Enum.sum()

    IO.inspect(sum_of_gear_power, label: "Solution 2")
    :exit
  end

  def solve(data) do
    out =
      data
      |> String.split("\n", trim: true)

    col_bounds = Enum.at(out, 0) |> String.length()
    row_bounds = out |> length()

    matrix =
      out
      |> Enum.with_index(0)
      |> Enum.map(&DayThree.line_to_tokens/1)

    solve_part_1(matrix, row_bounds, col_bounds)
    solve_part_2(matrix, row_bounds, col_bounds)

    :exit
  end

  def run do
    IO.puts("Starting...")

    case System.get_env("ADVENT_HOME") do
      value when is_binary(value) ->
        filepath = "#{value}/#{@year}/data/day#{@day}/#{@filepath}"

        case File.read(filepath) do
          {:ok, content} ->
            IO.puts("Put code here")
            solve(content)

          {:error, reason} ->
            IO.puts("Could not read file because #{reason}")
        end

      nil ->
        IO.puts("env var not found")
    end
  end
end

DayThree.run()