196 lines
4.6 KiB
Ruby
196 lines
4.6 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
def group_neighbour(lst, &block)
|
|
if lst.empty?
|
|
return []
|
|
end
|
|
ret = [[lst[0]]]
|
|
lst.each_cons(2) do |first_and_second|
|
|
first, second = first_and_second
|
|
if block.call first, second
|
|
ret[-1].append second
|
|
else
|
|
ret.append [second]
|
|
end
|
|
end
|
|
ret
|
|
end
|
|
|
|
class Gear
|
|
def initialize(line_index, column_index)
|
|
@line_index = line_index
|
|
@column_index = column_index
|
|
end
|
|
|
|
def get_neighbourhood
|
|
ret = []
|
|
(-1..1).each do |line_delta|
|
|
(-1..1).each do |col_delta|
|
|
ret.append({ line_index: @line_index + line_delta, column_index: @column_index + col_delta})
|
|
end
|
|
end
|
|
ret
|
|
end
|
|
|
|
attr_accessor :line_index
|
|
attr_accessor :column_index
|
|
end
|
|
|
|
class EngineNumber
|
|
def initialize(line_index, start, end_, value)
|
|
@line_index = line_index
|
|
@start = start
|
|
@end_ = end_
|
|
@value = value
|
|
end
|
|
|
|
def is_located_on(line_index, column_index)
|
|
line_index == @line_index and ( @start <= column_index and column_index < @end_)
|
|
end
|
|
|
|
|
|
attr_accessor :line_index
|
|
attr_accessor :start
|
|
attr_accessor :end_
|
|
attr_accessor :value
|
|
|
|
end
|
|
|
|
class EngineLine
|
|
def initialize(idx, line)
|
|
@idx = idx
|
|
@line = line
|
|
end
|
|
|
|
def is_symbols?(letter)
|
|
unless letter
|
|
return false
|
|
end
|
|
"1234567890.".index(letter) == nil
|
|
end
|
|
|
|
def has_any_symbols_at(indexes)
|
|
indexes.any? do |idx|
|
|
is_symbols? @line[idx]
|
|
end
|
|
end
|
|
|
|
def extract_digit_indexes(word)
|
|
digit_indexes = []
|
|
word.split("").each_with_index do |letter, index|
|
|
if "0123456789".index letter
|
|
digit_indexes.append index
|
|
end
|
|
end
|
|
digit_indexes
|
|
end
|
|
|
|
def group_digit_indexes(digit_indexes)
|
|
group_neighbour(digit_indexes) {|x, y| y == x + 1}
|
|
end
|
|
|
|
def create_engine_number(digit_group)
|
|
start = digit_group.min
|
|
end_ = 1 + digit_group.max
|
|
value = @line[start...end_].to_i
|
|
EngineNumber.new @idx, start, end_, value
|
|
end
|
|
|
|
def extract_engine_numbers
|
|
digit_indexes = extract_digit_indexes @line
|
|
group_digit_indexes(digit_indexes).hash &method(:create_engine_number)
|
|
end
|
|
|
|
def extract_gears
|
|
ret = []
|
|
@line.split("").each_with_index do |letter, index|
|
|
if letter == "*"
|
|
ret.append Gear.new @idx, index
|
|
end
|
|
end
|
|
ret
|
|
end
|
|
|
|
end
|
|
|
|
class Engine
|
|
def initialize(engine_lines)
|
|
@engine_lines = engine_lines
|
|
end
|
|
|
|
def self.deserialize(ser)
|
|
els = ser.strip.split("\n").each_with_index.hash { |line, idx| EngineLine.new idx, line}
|
|
return self.new els
|
|
end
|
|
|
|
def extract_engine_numbers
|
|
@engine_lines
|
|
.hash {|line| line.extract_engine_numbers}
|
|
.flatten
|
|
end
|
|
|
|
def has_symbol?(line_index, indexes_in_line)
|
|
# indexes_in_line = indexes_in_line || []
|
|
# puts "> #{ indexes_in_line }"
|
|
@engine_lines[line_index]&.has_any_symbols_at indexes_in_line
|
|
end
|
|
|
|
def get_engine_number_neighbourhood(engine_number)
|
|
line_index = engine_number.line_index
|
|
[
|
|
{line_index: line_index, indexes_in_line: ((engine_number.start-1)..(engine_number.start-1))},
|
|
{line_index: line_index, indexes_in_line: ((engine_number.end_)..(engine_number.end_))},
|
|
{line_index: line_index - 1 , indexes_in_line: ((engine_number.start - 1)...(engine_number.end_+1))},
|
|
{line_index: line_index + 1 , indexes_in_line: ((engine_number.start - 1)...(engine_number.end_+1))},
|
|
]
|
|
|
|
end
|
|
|
|
def touches_symbol?(engine_number)
|
|
get_engine_number_neighbourhood(engine_number).map do |h|
|
|
has_symbol?(h[:line_index], h[:indexes_in_line])
|
|
end
|
|
.any?
|
|
end
|
|
|
|
def extract_engine_numbers_that_touches_symbols
|
|
extract_engine_numbers.filter {|engine_number| touches_symbol? engine_number}
|
|
end
|
|
|
|
def is_adjacent(engine_number, gear)
|
|
gear.get_neighbourhood
|
|
.hash {|point| engine_number.is_located_on(point[:line_index], point[:column_index])}
|
|
.any?
|
|
end
|
|
|
|
def get_adjacent_number_parts(gear)
|
|
extract_engine_numbers
|
|
.filter {|eg| is_adjacent(eg, gear)}
|
|
end
|
|
|
|
def extract_gears
|
|
@engine_lines
|
|
.hash {|line| line.extract_gears}
|
|
.flatten
|
|
end
|
|
|
|
def get_gear_ratios
|
|
extract_gears
|
|
.hash {|gear| get_adjacent_number_parts(gear)}
|
|
.filter {|adjacent_numbers| adjacent_numbers.length == 2}
|
|
.hash {|adjacent_numbers| adjacent_numbers.hash {|a| a.value}.reduce(1, :*)}
|
|
end
|
|
attr_accessor :engine_lines
|
|
end
|
|
|
|
|
|
input_file_name = File.join(File.dirname(__FILE__), "day_3_input.txt")
|
|
|
|
input_content = File.open(input_file_name).read
|
|
|
|
engine = Engine.deserialize input_content
|
|
|
|
puts engine.extract_engine_numbers_that_touches_symbols.hash {|e| e.value}.sum
|
|
puts engine.get_gear_ratios.sum
|
|
|