changeset 26:eb6c3a7d2f72

Constrained and more optimised route finding * Track routes so we can see if we have gone straight for too long * Track multiple routes so we can use a non-optimal route to X if it makes another route to Y through X possible (e.g. optimal route takes three consecutive steps to X, but then has to turn, whereas a longer straight earlier and two consecutive steps to X gives a much better next hop to Y) * We have a start point, so only include the nodes from the search front in "unvisited" to avoid looking at lots of irrelevant nodes
author IBBoard <dev@ibboard.co.uk>
date Sun, 17 Dec 2023 20:13:03 +0000
parents 79dc2ba41df2
children 6b58ddfaed38
files day17.rb
diffstat 1 files changed, 82 insertions(+), 23 deletions(-) [+]
line wrap: on
line diff
--- a/day17.rb	Sun Dec 17 10:32:05 2023 +0000
+++ b/day17.rb	Sun Dec 17 20:13:03 2023 +0000
@@ -1,5 +1,7 @@
 #! /usr/bin/env ruby
 
+require 'set'
+
 if ARGV.length != 1
         abort("Incorrect arguments - needs input file")
 elsif not File.exist? (ARGV[0])
@@ -9,42 +11,99 @@
 file = ARGV[0]
 
 Coord = Struct.new(:x, :y)
+Route = Struct.new(:steps, :distance)
+Routes = Struct.new(:routes, :min_distance)
 
 city_map = File.open(file, "r").each_line(chomp: true).map{ |line| line.each_char.map(&:to_i)}
 
-distances = Array.new(city_map.length) {Array.new(city_map[0].length) {Float::INFINITY}}
-distances[0][0] = 0
+start_space = Coord.new(0,0)
+end_space = Coord.new(city_map[0].length - 1, city_map.length - 1)
+
+visited = {}
+routes = { start_space => Routes.new([Route.new([start_space], 0)], 0)}
 
-visited = Array.new(city_map.length) {Array.new(city_map[0].length)}
+unvisited = Set.new
+unvisited << start_space
+#(0...city_map.length).each {|y| (0...city_map[0].length).each {|x| unvisited << Coord.new(x,y)}}
 
-unvisited = []
-(0...city_map.length).each {|y| (0...city_map[0].length).each {|x| unvisited << Coord.new(x,y)}}
+$up = Coord.new(0, -1)
+$down = Coord.new(0, 1)
+$left = Coord.new(-1, 0)
+$right = Coord.new(1, 0)
 
 def get_neighbours(city_map, x, y)
-	[Coord.new(-1,0), Coord.new(1,0), Coord.new(0,-1), Coord.new(0,1)].map {|coord| Coord.new(coord.x + x, coord.y + y)}.filter {|coord| coord.x >= 0 and coord.y >= 0 and coord.x < city_map[0].length and coord.y < city_map.length}
+	[$up, $down, $left, $right].map {|coord| Coord.new(coord.x + x, coord.y + y)}.filter {|coord| coord.x >= 0 and coord.y >= 0 and coord.x < city_map[0].length and coord.y < city_map.length}
+end
+
+def allowed_routes(city_map, routes, cur_node, next_node)
+	next_step_distance = city_map[next_node.y][next_node.x]
+	candidate_routes = routes[cur_node].routes.map {|route| Route.new(route.steps + [next_node], route.distance + next_step_distance)}
+	valid_routes = candidate_routes.filter do |route|
+		looped = route.steps.count(next_node) > 1
+		if route.steps.length < 5
+			! looped
+		else
+			step_range = route.steps[-5..-2]
+			!(step_range.all?{|step| step.x == next_node.x} or step_range.all?{|step| step.y == next_node.y}) and !looped
+		end
+	end
+	valid_routes = valid_routes + routes[next_node].routes if !routes[next_node].nil?
+	Routes.new(valid_routes, valid_routes.map(&:distance).min)
+end
+
+def get_distance(routes, node)
+	if routes.key?(node)
+		routes[node].min_distance
+	else
+		Float::INFINITY
+	end
 end
 
 until unvisited.empty?
+#	puts unvisited
 	min_node = nil
+#	min_node_direct_distance = Float::INFINITY
 	unvisited.each do |node|
-		if min_node.nil? || distances[node.y][node.x] < distances[min_node.y][min_node.x]
-			min_node = node unless visited[node.y][node.x]
+#		x_dist = (end_space.x - node.x).abs
+#		y_dist = (end_space.y - node.y).abs
+#		direct_distance = x_dist * x_dist + y_dist * y_dist
+		if min_node.nil? || get_distance(routes, node) < get_distance(routes, min_node)# || direct_distance < min_node_direct_distance
+#			puts "#{get_distance(routes, node)} < #{get_distance(routes, min_node)} || #{direct_distance} < #{min_node_direct_distance}"
+			min_node = node
+#			min_node_direct_distance = direct_distance
+		end
+	end
+#	puts unvisited.length
+	puts "Min node: #{min_node}"
+
+	break if get_distance(routes, min_node) == Float::INFINITY or min_node == end_space
+
+	get_neighbours(city_map, min_node.x, min_node.y).each do |neighbour|
+		new_routes = allowed_routes(city_map, routes, min_node, neighbour)
+		if new_routes.routes.length > 0
+			routes[neighbour] = new_routes
+			unvisited << neighbour if !visited[neighbour]
 		end
 	end
 
-	break if distances[min_node.y][min_node.x] == Float::INFINITY
-
-	cur_distance = distances[min_node.y][min_node.x]
+	visited[min_node] = true
+	unvisited = unvisited.delete(min_node)
+#	puts "Delete #{min_node}"
+#	a = $stdin.gets("\n")
+end
+=begin
+city_map.length.times do |y|
+	city_map[0].length.times do |x|
+		print routes[Coord.new(x,y)].routes.map(&:distance).min.to_s.rjust(3) + " " + city_map[y][x].to_s + " |"
+	end
+	puts
+end
+city_map.length.times do |y|
+	city_map[0].length.times do |x|
+		puts "#{x},#{y} ⇒ #{routes[Coord.new(x,y)].routes.length} - #{routes[Coord.new(x,y)].routes.map(&:distance).minmax}"
+	end
+end
+=end
 
-	get_neighbours(city_map, min_node.x, min_node.y).each do |neighbour|
-		value = city_map[neighbour.y][neighbour.x]
-		alt = cur_distance + value
-		distances[neighbour.y][neighbour.x] = alt if alt < distances[neighbour.y][neighbour.x]
-	end
-
-	visited[min_node.y][min_node.x] = true
-	unvisited.delete(min_node)
-	distances
-end
-
-puts distances[-1][-1]
+puts routes[end_space].routes.sort {|a, b| a.distance <=> b.distance}[0]
+#puts "#{routes[end_space]}"
\ No newline at end of file