require 'debug' # Generic FileHandler class FileHandler def initialize(file_path) @file_path = file_path end # Usage: # FileHandler.new('/path_to_file').read_lines do |line| # # process line # end def read_lines File.foreach(@file_path, chomp: true).map do |line| yield(line) end end # Usage: # file_content = FileHandler.new('/path_to_file').read_file( def read_file File.read(@file_path) end end class PuzzleSolver def initialize(file_handler, debug: false) @handler = file_handler @debug = debug end def debug(message) puts message if @debug end def print_debug(message) print message if @debug end def solve raise NotImplementedError, 'Please implement this method in subclasses.' end end # Puzzle-specific class Grid attr_reader :cells, :num_rows, :num_columns # @param [<>] an array of arrays of of 1's and 0's e.g., [[1, 0], [0, 1]] def initialize(grid) raise "Invalid grid: 0 rows in #{grid}" if grid.length == 0 raise "Invalid grid: 0 columns in #{grid}" if grid[0].length == 0 raise "Invalid grid: inconsistent number of columns in #{grid}" unless grid.all? {|column| column.length == grid[0].length} @num_rows = grid.length @num_columns = grid[0].length @cells = grid end # @return [Grid] def self.from_file_handler(handler) grid = [] handler.read_lines do |line| next if line.nil? || line.empty? grid << line.split('').map {|cell| cell == '@' ? 1 : 0} end Grid.new(grid) end # @return [<[Integer, Integer]>] list of coordinates of adjacent cells of [x,y]. def adjacent_coordinates(x, y) adjacent_cells = [-1, 0, 1].flat_map do |x_offset| [-1, 0, 1].map do |y_offset| [x + x_offset, y + y_offset] end end adjacent_cells.delete([x, y]) # only return cells on the grid adjacent_cells.select do |cell| 0 <= cell[0] && cell[0] < @num_rows && 0 <= cell[1] && cell[1] < @num_columns end end def adjacent_cell_values(x, y) adjacent_coordinates = adjacent_coordinates(x, y) adjacent_cell_values = adjacent_coordinates.map {|x, y| @cells[x][y]} adjacent_cell_values end # @return [Integer] the number of rolls in the adjacent positions of cell (x, y) def num_adjacent_rolls(x, y) adjacent_cell_values = adjacent_cell_values(x, y) adjacent_cell_values.sum end def removable_roll_coords cells = [] (0...@num_rows).each do |x| (0...@num_columns).each do |y| cells << [x, y] if num_adjacent_rolls(x, y) < 4 and @cells[x][y] == 1 end end cells end def remove_rolls(coords) coords.each do |x, y| puts "Warning: No roll in cell (#{x},#{y})" if @cells[x][y] == 0 @cells[x][y] = 0 end end end class GridSolver < PuzzleSolver def solve grid = Grid.from_file_handler(@handler) total_rolls_removed = 0 removable_roll_coords = grid.removable_roll_coords while removable_roll_coords.length != 0 total_rolls_removed += removable_roll_coords.length grid.remove_rolls(removable_roll_coords) removable_roll_coords = grid.removable_roll_coords end total_rolls_removed end end if ARGV[0].nil? || ARGV[0].empty? puts "Usage: ruby #{__FILE__} [debug]" exit 1 end file_path = ARGV[0] debug = (ARGV[1] == "debug") file_handler = FileHandler.new(file_path) ## Puzzle-specific cls = GridSolver ## puts "The answer is: #{cls.new(file_handler, debug:).solve}" # The answer is: 9206 # real 0m8,510s # user 0m8,450s # sys 0m0,059s