Solve day5 (part2 code is very ugly... but it works :))
parent
3a3764bf4c
commit
9aacd7d8ba
|
|
@ -0,0 +1,61 @@
|
|||
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
|
||||
|
||||
# TODO: Puzzle-specific
|
||||
|
||||
|
||||
|
||||
if ARGV[0].nil? || ARGV[0].empty?
|
||||
puts "Usage: ruby #{__FILE__} <file_name> [debug]"
|
||||
exit 1
|
||||
end
|
||||
file_path = ARGV[0]
|
||||
debug = (ARGV[1] == "debug")
|
||||
file_handler = FileHandler.new(file_path)
|
||||
|
||||
## Puzzle-specific
|
||||
cls = PuzzleSolver
|
||||
##
|
||||
|
||||
puts "The answer is: #{cls.new(file_handler, debug:).solve}"
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
3-5
|
||||
10-14
|
||||
16-20
|
||||
12-18
|
||||
|
||||
1
|
||||
5
|
||||
8
|
||||
11
|
||||
17
|
||||
32
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,102 @@
|
|||
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, chomp: true)
|
||||
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
|
||||
|
||||
class FreshnessDatabase
|
||||
# @param [<Range>] list of ranges of fresh IDs
|
||||
def initialize(fresh_ranges)
|
||||
@fresh_ranges = fresh_ranges
|
||||
end
|
||||
|
||||
def fresh_id?(id)
|
||||
@fresh_ranges.any? {|range| range.include?(id)}
|
||||
end
|
||||
end
|
||||
|
||||
class Solver < PuzzleSolver
|
||||
# @param [String] e.g., "11-22"
|
||||
# @return [Range] e.g., 11..22 (inclusive)
|
||||
def to_range(string)
|
||||
boundaries = string.split('-').map(&:to_i)
|
||||
raise "Invalid boundaries for #{string}, got #{boundaries.inspect}" if boundaries.length != 2
|
||||
Range.new(boundaries[0], boundaries[1])
|
||||
end
|
||||
|
||||
# @param [String] full input e.g., "11-22\n33-604\n..."
|
||||
# @return [<Range>] e.g., [11..22, 33..604, ...]
|
||||
def parse_ranges(input)
|
||||
input.split("\n").map {|range| to_range(range)}
|
||||
end
|
||||
|
||||
# @param [String] ids_str e.g., "1\n5\n8\n11\n17\n32"
|
||||
# @return [<Integer>] e.g., [1, 5, 8, 11, 17, 32]
|
||||
def parse_ids(ids_str)
|
||||
ids_str.split("\n").map(&:to_i)
|
||||
end
|
||||
|
||||
def solve
|
||||
two_parts = @handler.read_file.split("\n\n")
|
||||
raise "Invalid file format: #{two_parts.inpsect}" if two_parts.length != 2
|
||||
|
||||
db = FreshnessDatabase.new(parse_ranges(two_parts[0]))
|
||||
ids = parse_ids(two_parts[1])
|
||||
|
||||
fresh_ids = ids.select {|id| db.fresh_id?(id)}
|
||||
fresh_ids.length
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if ARGV[0].nil? || ARGV[0].empty?
|
||||
puts "Usage: ruby #{__FILE__} <file_name> [debug]"
|
||||
exit 1
|
||||
end
|
||||
file_path = ARGV[0]
|
||||
debug = (ARGV[1] == "debug")
|
||||
file_handler = FileHandler.new(file_path)
|
||||
|
||||
## Puzzle-specific
|
||||
cls = Solver
|
||||
##
|
||||
|
||||
puts "The answer is: #{cls.new(file_handler, debug:).solve}"
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
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, chomp: true)
|
||||
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
|
||||
|
||||
class FreshnessDatabase
|
||||
# @param [<Range>] list of ranges of fresh IDs
|
||||
def initialize(fresh_ranges)
|
||||
@fresh_ranges = fresh_ranges
|
||||
end
|
||||
|
||||
def fresh_id?(id)
|
||||
@fresh_ranges.any? {|range| range.include?(id)}
|
||||
end
|
||||
|
||||
def parsed_ranges
|
||||
all_ranges = []
|
||||
current_min = Float::INFINITY
|
||||
current_max = -Float::INFINITY
|
||||
@fresh_ranges.each_with_index do |range, index|
|
||||
if range.max < current_min
|
||||
all_ranges.insert(0, range)
|
||||
current_min = range.min
|
||||
current_max = range.max if range.max > current_max
|
||||
next
|
||||
end
|
||||
|
||||
if range.min > current_max
|
||||
all_ranges << range
|
||||
current_max = range.max
|
||||
current_min = range.min if range.min < current_min
|
||||
next
|
||||
end
|
||||
|
||||
overlapping_range_indices, closest_range_index = FreshnessDatabase.find_all_overlapping_range_indices(all_ranges, range)
|
||||
|
||||
if overlapping_range_indices.empty?
|
||||
all_ranges.insert(closest_range_index + 1, range)
|
||||
elsif overlapping_range_indices.length == 1
|
||||
overlap_index = overlapping_range_indices[0]
|
||||
new_range = FreshnessDatabase.max_range([all_ranges[overlap_index], range])
|
||||
all_ranges[overlap_index] = new_range
|
||||
range = new_range
|
||||
else
|
||||
min_index = overlapping_range_indices[0]
|
||||
max_index = overlapping_range_indices[overlapping_range_indices.length - 1]
|
||||
min_range = all_ranges[min_index]
|
||||
max_range = all_ranges[max_index]
|
||||
new_range = FreshnessDatabase.max_range([min_range, range, max_range])
|
||||
|
||||
new_ranges = []
|
||||
all_ranges.each_index do |i|
|
||||
if i < min_index
|
||||
new_ranges << all_ranges[i]
|
||||
elsif i == min_index
|
||||
new_ranges << new_range
|
||||
elsif min_index < i && i <= max_index
|
||||
# do nothing
|
||||
else
|
||||
new_ranges << all_ranges[i]
|
||||
end
|
||||
end
|
||||
all_ranges = new_ranges
|
||||
range = new_range
|
||||
end
|
||||
|
||||
current_min = range.min if range.min < current_min
|
||||
current_max = range.max if range.max > current_max
|
||||
end
|
||||
|
||||
all_ranges
|
||||
end
|
||||
|
||||
def num_fresh_ids
|
||||
ranges = parsed_ranges
|
||||
puts ranges.inspect
|
||||
num_ids = ranges.map {|range| range.size}.sum
|
||||
|
||||
num_ids
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self.overlapping_ranges?(range1, range2)
|
||||
(range1.min <= range2.min && range2.min <= range1.max) ||
|
||||
(range1.min <= range2.max && range2.min <= range1.max)
|
||||
end
|
||||
|
||||
def self.max_range(ranges_array)
|
||||
(0...ranges_array.length - 1).each do |index|
|
||||
raise "Non overlapping ranges found on #{index}: #{ranges_array}" if !self.overlapping_ranges?(ranges_array[index], ranges_array[index+1])
|
||||
end
|
||||
|
||||
bounds = ranges_array.flat_map {|range| [range.min, range.max]}
|
||||
Range.new(bounds.min, bounds.max)
|
||||
end
|
||||
|
||||
def self.find_all_overlapping_range_indices(all_ranges, range)
|
||||
# NOTE: we assume `range` is between smallest and largest range, as those cases are handles separately in main loop
|
||||
raise "Error unmet assumption: #{all_ranges}" if all_ranges.length < 2
|
||||
|
||||
indices = []
|
||||
closest_range_index = 0 # used in case there is no overlap
|
||||
all_ranges.each_with_index do |possible_range, index|
|
||||
if self.overlapping_ranges?(possible_range, range)
|
||||
indices << index
|
||||
else
|
||||
closest_range_index = index if range.min > possible_range.max
|
||||
end
|
||||
end
|
||||
|
||||
[indices, closest_range_index]
|
||||
end
|
||||
end
|
||||
|
||||
class Solver < PuzzleSolver
|
||||
# @param [String] e.g., "11-22"
|
||||
# @return [Range] e.g., 11..22 (inclusive)
|
||||
def to_range(string)
|
||||
boundaries = string.split('-').map(&:to_i)
|
||||
raise "Invalid boundaries for #{string}, got #{boundaries.inspect}" if boundaries.length != 2
|
||||
Range.new(boundaries[0], boundaries[1])
|
||||
end
|
||||
|
||||
# @param [String] full input e.g., "11-22\n33-604\n..."
|
||||
# @return [<Range>] e.g., [11..22, 33..604, ...]
|
||||
def parse_ranges(input)
|
||||
input.split("\n").map {|range| to_range(range)}
|
||||
end
|
||||
|
||||
# @param [String] ids_str e.g., "1\n5\n8\n11\n17\n32"
|
||||
# @return [<Integer>] e.g., [1, 5, 8, 11, 17, 32]
|
||||
def parse_ids(ids_str)
|
||||
ids_str.split("\n").map(&:to_i)
|
||||
end
|
||||
|
||||
def solve
|
||||
two_parts = @handler.read_file.split("\n\n")
|
||||
raise "Invalid file format: #{two_parts.inpsect}" if two_parts.length != 2
|
||||
|
||||
db = FreshnessDatabase.new(parse_ranges(two_parts[0]))
|
||||
db.num_fresh_ids
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if ARGV[0].nil? || ARGV[0].empty?
|
||||
puts "Usage: ruby #{__FILE__} <file_name> [debug]"
|
||||
exit 1
|
||||
end
|
||||
file_path = ARGV[0]
|
||||
debug = (ARGV[1] == "debug")
|
||||
file_handler = FileHandler.new(file_path)
|
||||
|
||||
## Puzzle-specific
|
||||
cls = Solver
|
||||
##
|
||||
|
||||
puts "The answer is: #{cls.new(file_handler, debug:).solve}"
|
||||
Loading…
Reference in New Issue