changeset 17:92144824cbb7

Add "inside the loop" counting for day 10 part 2 Use the "how many lines do we cross?" approach. We also need extra state for "am I actually inside a line, or did it just revert back on itself". Slightly slow, but not terrible.
author IBBoard <dev@ibboard.co.uk>
date Sun, 10 Dec 2023 20:26:12 +0000
parents 9b1d04091335
children ddb69833346c
files day10.txt day10b.rb
diffstat 2 files changed, 219 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/day10.txt	Sun Dec 10 19:17:56 2023 +0000
+++ b/day10.txt	Sun Dec 10 20:26:12 2023 +0000
@@ -102,3 +102,103 @@
 23...
 
 Find the single giant loop starting at S. How many steps along the loop does it take to get from the starting position to the point farthest from the starting position?
+
+--- Part Two ---
+
+You quickly reach the farthest point of the loop, but the animal never emerges. Maybe its nest is within the area enclosed by the loop?
+
+To determine whether it's even worth taking the time to search for such a nest, you should calculate how many tiles are contained within the loop. For example:
+
+...........
+.S-------7.
+.|F-----7|.
+.||.....||.
+.||.....||.
+.|L-7.F-J|.
+.|..|.|..|.
+.L--J.L--J.
+...........
+
+The above loop encloses merely four tiles - the two pairs of . in the southwest and southeast (marked I below). The middle . tiles (marked O below) are not in the loop. Here is the same loop again with those regions marked:
+
+...........
+.S-------7.
+.|F-----7|.
+.||OOOOO||.
+.||OOOOO||.
+.|L-7OF-J|.
+.|II|O|II|.
+.L--JOL--J.
+.....O.....
+
+In fact, there doesn't even need to be a full tile path to the outside for tiles to count as outside the loop - squeezing between pipes is also allowed! Here, I is still within the loop and O is still outside the loop:
+
+..........
+.S------7.
+.|F----7|.
+.||OOOO||.
+.||OOOO||.
+.|L-7F-J|.
+.|II||II|.
+.L--JL--J.
+..........
+
+In both of the above examples, 4 tiles are enclosed by the loop.
+
+Here's a larger example:
+
+.F----7F7F7F7F-7....
+.|F--7||||||||FJ....
+.||.FJ||||||||L7....
+FJL7L7LJLJ||LJ.L-7..
+L--J.L7...LJS7F-7L7.
+....F-J..F7FJ|L7L7L7
+....L7.F7||L7|.L7L7|
+.....|FJLJ|FJ|F7|.LJ
+....FJL-7.||.||||...
+....L---J.LJ.LJLJ...
+
+The above sketch has many random bits of ground, some of which are in the loop (I) and some of which are outside it (O):
+
+OF----7F7F7F7F-7OOOO
+O|F--7||||||||FJOOOO
+O||OFJ||||||||L7OOOO
+FJL7L7LJLJ||LJIL-7OO
+L--JOL7IIILJS7F-7L7O
+OOOOF-JIIF7FJ|L7L7L7
+OOOOL7IF7||L7|IL7L7|
+OOOOO|FJLJ|FJ|F7|OLJ
+OOOOFJL-7O||O||||OOO
+OOOOL---JOLJOLJLJOOO
+
+In this larger example, 8 tiles are enclosed by the loop.
+
+Any tile that isn't part of the main loop can count as being enclosed by the loop. Here's another example with many bits of junk pipe lying around that aren't connected to the main loop at all:
+
+FF7FSF7F7F7F7F7F---7
+L|LJ||||||||||||F--J
+FL-7LJLJ||||||LJL-77
+F--JF--7||LJLJ7F7FJ-
+L---JF-JLJ.||-FJLJJ7
+|F|F-JF---7F7-L7L|7|
+|FFJF7L7F-JF7|JL---7
+7-L-JL7||F7|L7F-7F7|
+L.L7LFJ|||||FJL7||LJ
+L7JLJL-JLJLJL--JLJ.L
+
+Here are just the tiles that are enclosed by the loop marked with I:
+
+FF7FSF7F7F7F7F7F---7
+L|LJ||||||||||||F--J
+FL-7LJLJ||||||LJL-77
+F--JF--7||LJLJIF7FJ-
+L---JF-JLJIIIIFJLJJ7
+|F|F-JF---7IIIL7L|7|
+|FFJF7L7F-JF7IIL---7
+7-L-JL7||F7|L7F-7F7|
+L.L7LFJ|||||FJL7||LJ
+L7JLJL-JLJLJL--JLJ.L
+
+In this last example, 10 tiles are enclosed by the loop.
+
+Figure out whether you have time to search for the nest by calculating the area within the loop. How many tiles are enclosed by the loop?
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/day10b.rb	Sun Dec 10 20:26:12 2023 +0000
@@ -0,0 +1,119 @@
+#! /usr/bin/env ruby
+
+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]
+
+charmap = File.open(file, "r").each_line(chomp: true).map {|line| line.each_char.to_a}.to_a
+$map_height = charmap.length
+$map_width = charmap[0].length
+
+# Ugly way to find the start, because we know there's one (for now)
+start = charmap.each_with_index.flat_map { |columns, i| j = columns.find_index("S"); if j then [j, i] else nil end }.compact
+start_x, start_y = start
+
+$direction_inversion = {north: :south, south: :north, east: :west, west: :east}
+
+$pipemap = charmap.map do |row|
+	row.map do |cell|
+		case cell
+		when "|"
+			[:north, :south]
+		when "-"
+			[:east, :west]
+		when "L"
+			[:north, :east]
+		when "J"
+			[:north, :west]
+		when "7"
+			[:south, :west]
+		when "F"
+			[:south, :east]
+		when "S"
+			# Assume all directions are valid
+			[:north, :south, :east, :west]
+		else
+			[]
+		end
+	end
+end
+
+def valid_position? (x, y)
+	x >= 0 and x < $map_width and y >= 0 and y < $map_height
+end
+
+def get_target_for_step(step)
+	case step.direction
+		when :north then [step.x, step.y-1]
+		when :south then [step.x, step.y+1]
+		when :east then [step.x+1, step.y]
+		when :west then [step.x-1, step.y]
+	end
+end
+
+def get_next_step(step)
+	new_x, new_y = get_target_for_step(step)
+	target_inputs = valid_position?(new_x, new_y) ? $pipemap[new_y][new_x] : [] 
+	required_direction = $direction_inversion[step.direction]
+	target_inputs.include?(required_direction) ? Step.new(new_x, new_y, (target_inputs - [required_direction])[0]) : nil
+end
+
+Step = Struct.new(:x, :y, :direction)
+
+first_steps = [:north, :south, :east, :west].map {|dir| Step.new(start_x, start_y, dir)}
+routes = first_steps.map{|v|[v]}
+
+while ! routes.any? {|route| last_step = route[-1]; route.length > 1 and last_step.x == start_x and last_step.y == start_y}
+	routes = routes.map do |route|
+		next_step = get_next_step(route[-1])
+		if next_step
+			route << next_step
+		else
+			nil
+		end
+	end.compact
+end
+
+# We get two routes - the two opposite directions around the same path
+# so take the first one
+route = routes[0].map {|v| [v.x, v.y]}
+
+in_out_transition = [:north, :south]
+
+State = Struct.new(:crossed_lines, :inside_count, :last_switch)
+
+# TODO: Replace Source with actual piece based on route
+actual_start_directions = first_steps.map do |v|
+	step = get_next_step(v)
+	!step.nil? and [route[1], route[-2]].include?([step.x, step.y]) ? v.direction : nil
+end.filter {|v| v}
+$pipemap[start_y][start_x] = actual_start_directions
+
+cells_inside = $pipemap.each_with_index.map do |row, y|
+	row_state = State.new(0, 0, nil)
+	row.each_with_index do |cell, x|
+		if route.include?([x,y])
+			if cell == in_out_transition
+				row_state.crossed_lines += 1
+			elsif cell != [:east, :west]
+				this_switch = (cell & in_out_transition)
+				if row_state.last_switch.nil?
+					row_state.last_switch = this_switch
+				else
+					if this_switch != row_state.last_switch
+						row_state.crossed_lines += 1
+					end
+					row_state.last_switch = nil
+				end
+			end
+		elsif row_state.crossed_lines % 2.0 != 0
+			row_state.inside_count += 1
+		end
+	end
+	row_state
+end.reduce(0) {|accum, row_state| accum + row_state.inside_count}
+puts cells_inside
\ No newline at end of file