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(&"#{&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(&(elem(&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(&elem(&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, &(&1 == "*"))
defp extract_symbol_positions(schematic),
do: extract_positions(schematic, &(&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, &touch?(&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, &touch?(position, &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