view day5b.rb @ 13:7826431dbc4f

Finish day 5 part 2 - seeds with ranges There must be a cleaner method, but this works for splitting ranges when there's a partial overlap. An interim version dropped ranges because we were iterating over an array while also deleting items from it.
author IBBoard <dev@ibboard.co.uk>
date Sat, 09 Dec 2023 19:53:22 +0000
parents 25dda3397797
children dcc060e59c47
line wrap: on
line source

#! /usr/bin/env ruby

require 'set'

if ARGV.length != 1
        abort("Incorrect arguments - needs input file")
elsif not File.exist? (ARGV[0])
	abort("File #{ARGV[0]} did not exist")
end

file = ARGV[0]

maps = Hash.new

lines = File.open(file, "r").each_line(chomp: true)

seeds = lines.first.split(":")[1].split(" ").map(&:to_i).each_slice(2).map {|v| v[0]...(v[0]+v[1])}

RawMapping = Struct.new(:from, :to, :ranges)
MappingRange = Struct.new(:source_range, :offset)

mappings = Array.new
mappings_array = []

lines.drop(1).reduce(mappings_array) do |mappings, val|
	if m = /([a-z]+)-to-([a-z]+) map:/.match(val) then
		mappings << RawMapping.new(m[1], m[2], [])
	elsif val != "" then
		dest_start, source_start, range_length = val.split(" ").map(&:to_i)
		mappings[-1].ranges << MappingRange.new((source_start...(source_start + range_length)), dest_start - source_start)
	end
	mappings
end

mappings = mappings_array.map {|mapping| [mapping.from, mapping]}.to_h

def map_with_override(mappings, inputs)
	inputs.flat_map do |input|
		processed = []
		unprocessed = [input]
		# This "each… each" seems messy, but otherwise we can accidentally skip values
		# because we're editing as we iterate
		mappings.ranges.each do |mapping|
			unprocessed.each do |input_range|
				if mapping.source_range.end <= input_range.begin or input_range.end <= mapping.source_range.begin
					# Input is entirely outside the mapped range
				elsif mapping.source_range.cover?(input_range)
					# Input is inside the mapped range
					processed << ((input_range.begin+mapping.offset)...(input_range.end+mapping.offset))
					unprocessed.delete(input_range)
				elsif input_range.begin < mapping.source_range.begin
					# Input straddles the start
					unprocessed.delete(input_range)
					unprocessed << ((input_range.begin)...(mapping.source_range.begin))
					processed << ((mapping.source_range.begin+mapping.offset)...(input_range.end+mapping.offset))
				else
					# Must straddle the end
					unprocessed.delete(input_range)
					processed << ((input_range.begin+mapping.offset)...(mapping.source_range.end+mapping.offset))
					unprocessed << ((mapping.source_range.end)...(input_range.end))
				end
			end
		end
		processed + unprocessed
	end
end

steps = ["seed", "soil", "fertilizer", "water", "light", "temperature", "humidity"]

# FIXME: Some negative values and 0s, and the final `min()` gets a nill value
converted_ranges = steps.reduce(seeds) {|vals, map_name| map_with_override(mappings[map_name], vals)}
puts converted_ranges.map{|v| v.min}.min