Mercurial > repos > other > adventofcode2023
view day22.rb @ 33:676461cc3a70
Day 23 - track segments, not each space
This allows us to explore the branches once then do quicker calculations
for valid route lengths. But still requires exploring 1,262,816 routes
to find the longest!
author | IBBoard <dev@ibboard.co.uk> |
---|---|
date | Thu, 04 Jan 2024 14:52:24 +0000 |
parents | 47dc75915e91 |
children |
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] Block = Struct.new(:id, :x, :y, :z) SupportedBy = Struct.new(:blocks, :z) supports = Hash.new supported_by = Hash.new blocks = File.open(file, "r").each_line(chomp: true).with_index.map do |line, id| block_start, block_end = line.split("~") start_x, start_y, start_z = block_start.split(",") end_x, end_y, end_z = block_end.split(",") supports[id] = [] Block.new(id, start_x.to_i..end_x.to_i, start_y.to_i..end_y.to_i, start_z.to_i..end_z.to_i) end def height_sort(a,b) if a.z.first == b.z.first # Make it a stable sort a.y.first == b.y.first ? a.x.first <=> b.x.first : a.y.first <=> b.y.first else a.z.first <=> b.z.first end end blocks.sort! {|a,b| height_sort(a, b)} def overlap?(range_1, range_2) range_1.include?(range_2.first) || range_1.include?(range_2.last) || range_2.include?(range_1.first) || range_2.include?(range_1.last) end blocks.reduce([]) do |lower_blocks, block| support_blocks = lower_blocks.reduce(SupportedBy.new([], 0)) do |candidate_supports, lower_block| if overlap?(block.x, lower_block.x) && overlap?(block.y, lower_block.y) if lower_block.z.last > candidate_supports.z SupportedBy.new([lower_block], lower_block.z.last) elsif lower_block.z.last == candidate_supports.z candidate_supports.blocks << lower_block candidate_supports else candidate_supports end else candidate_supports end end drop = block.z.first - support_blocks.z - 1 block.z = (support_blocks.z+1)..(support_blocks.z+block.z.size) support_blocks.blocks.each {|support| supports[support.id] << block} supported_by[block.id] = support_blocks.blocks lower_blocks << block lower_blocks end disintegratable = supports.filter {|block_id, supports_list| supports_list.all? {|supported| supported_by[supported.id].length > 1} } puts "#{disintegratable.length} disintegratable blocks" def find_droppers(id, supports, supported_by, dropping, cache) new_drops = supports[id].filter do |block| (Set.new(supported_by[block.id].map(&:id)) - dropping).length == 0 end dropping = dropping + new_drops.map(&:id) new_drops.each do |supported_block| cache_key = [supported_block.id, Set.new(supported_by[supported_block.id].map(&:id)) & dropping] if cache.include?(cache_key) dropping += cache[cache_key] #puts "Got #{cache[cache_key]} for #{cache_key}" else supported_droppers = find_droppers(supported_block.id, supports, supported_by, dropping, cache) cache[cache_key] = supported_droppers #puts "Caching #{supported_droppers} for #{cache_key} while dropping #{dropping}" dropping += supported_droppers end end dropping - [id] end cascade = Hash.new cache = Hash.new blocks.reverse.each do |block| id = block.id cascade[id] = find_droppers(id, supports, supported_by, Set.new([id]), cache) end puts "Dropping a total of #{cascade.values.map{|val| val.length}.sum} blocks"