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

Day 3: Gear Ratios

day-3.livemd

Day 3: Gear Ratios

Part one

sample_input =
  """
  467..114..
  ...*......
  ..35..633.
  ......#...
  617*......
  .....+.58.
  ..592.....
  ......755.
  ...$.*....
  .664.598..
  """
defmodule GearRatios do
  def sum_part_numbers(schematic) do
    symbol_positions = extract_symbol_positions(schematic)

    for {number, _from, _to} = number_area <- extract_number_areas(schematic),
        touches_one_of?(number_area, symbol_positions),
        reduce: 0 do
      acc -> acc + number
    end
  end

  def sum_gear_ratios(schematic) do
    gear_positions = extract_gear_positions(schematic)
    number_areas = extract_number_areas(schematic)

    for gear_position <- gear_positions,
        gear_ratio = get_gear_ratio(gear_position, number_areas),
        reduce: 0 do
      acc -> acc + gear_ratio
    end
  end

  @digits 0..9 |> Enum.map(&amp;"#{&amp;1}")

  defp extract_number_areas(schematic) do
    schematic
    |> String.split("\n", trim: true)
    |> Enum.with_index()
    |> Enum.flat_map(fn {line, row} ->
      line
      |> String.split("", trim: true)
      |> Enum.with_index()
      |> Enum.chunk_by(&amp;(elem(&amp;1, 0) in @digits))
      |> Enum.filter(fn [{first_char, _} | _] -> first_char in @digits end)
      |> Enum.map(fn [{_, first_col} | _] = chunk ->
        {_, last_col} = List.last(chunk)
        number = chunk |> Enum.map(&amp;elem(&amp;1, 0)) |> Enum.join() |> String.to_integer()
        {number, {row, first_col}, {row, last_col}}
      end)
    end)
  end

  defp extract_gear_positions(schematic),
    do: extract_positions(schematic, &amp;(&amp;1 == "*"))

  defp extract_symbol_positions(schematic),
    do: extract_positions(schematic, &amp;(&amp;1 not in ["." | @digits]))

  defp extract_positions(schematic, allowed_char_fn?) do
    schematic
    |> String.split("\n", trim: true)
    |> Enum.with_index()
    |> Enum.flat_map(fn {line, row} ->
      line
      |> String.split("", trim: true)
      |> Enum.with_index()
      |> Enum.reduce([], fn {c, idx}, acc ->
        if allowed_char_fn?.(c) do
          [{c, {row, idx}} | acc]
        else
          acc
        end
      end)
    end)
  end

  defp touches_one_of?(area, positions), do: Enum.any?(positions, &amp;touch?(&amp;1, area))

  defp touch?(
         {_, {symbol_row, symbol_col}} = _position,
         {_, {from_row, from_col}, {to_row, to_col}} = _area
       ) do
    symbol_row in (from_row - 1)..(to_row + 1) and symbol_col in (from_col - 1)..(to_col + 1)
  end

  defp get_gear_ratio(gear_position, number_areas) do
    number_areas
    |> filter_adjacent_to(gear_position)
    |> case do
      [{number_1, _, _}, {number_2, _, _}] -> number_1 * number_2
      _ -> 0
    end
  end

  defp filter_adjacent_to(areas, position), do: Enum.filter(areas, &amp;touch?(position, &amp;1))
end
GearRatios.sum_part_numbers(sample_input)
# expect 4361
Path.join(__DIR__, "day-3.input")
|> File.read!()
|> GearRatios.sum_part_numbers()

# 525119

Part two

GearRatios.sum_gear_ratios(sample_input)
# expect 467835
Path.join(__DIR__, "day-3.input")
|> File.read!()
|> GearRatios.sum_gear_ratios()

# 76504829