From 4202f34395673e0f7a26555f61be83327769cd33 Mon Sep 17 00:00:00 2001 From: Jack Jackson Date: Fri, 17 Jan 2025 23:00:52 -0800 Subject: [PATCH] Solution to 16-2 --- solutions/16.zig | 232 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 201 insertions(+), 31 deletions(-) diff --git a/solutions/16.zig b/solutions/16.zig index 0db1179..4240313 100644 --- a/solutions/16.zig +++ b/solutions/16.zig @@ -4,7 +4,7 @@ const util = @import("util.zig"); const expect = std.testing.expect; pub fn main() !void { - const response = try part_one(false); + const response = try part_two(false); print("{}\n", .{response}); } @@ -42,36 +42,10 @@ fn part_one(is_test_case: bool) !u32 { defer _ = gpa.deinit(); const allocator = gpa.allocator(); - const input_file = try util.getInputFile("16", is_test_case); - const data = try util.readAllInputWithAllocator(input_file, allocator); - defer allocator.free(data); - - var map_list = std.ArrayList([]u8).init(allocator); - - var it = std.mem.splitScalar(u8, data, '\n'); - var start_position: Position = undefined; - var target_point: Point = undefined; - var line_counter: usize = 0; - - while (it.next()) |line| : (line_counter += 1) { - var line_list = std.ArrayList(u8).init(allocator); - for (line) |c| { - try line_list.append(c); - } - try map_list.append(try line_list.toOwnedSlice()); - - const index_of_s = std.mem.indexOf(u8, line, "S"); - if (index_of_s != null) { - start_position = Position{ .point = Point{ .x = index_of_s.?, .y = line_counter }, .heading = Heading.east }; - } - - const index_of_e = std.mem.indexOf(u8, line, "E"); - if (index_of_e != null) { - target_point = Point{ .x = index_of_e.?, .y = line_counter }; - } - } - - const map = try map_list.toOwnedSlice(); + const built = try build_map_and_start_and_end(is_test_case, allocator); + const map = built.map; + const start_position = built.start_position; + const target_point = built.target_point; defer allocator.free(map); defer { for (map) |line| { @@ -159,6 +133,196 @@ fn part_one(is_test_case: bool) !u32 { } } +// Existence of such a value in the map indicates that _all_ of the Positions listed in .predecessors can reach this +// Position with a total path-length of length +const PredecessorsAndLength = struct { + predecessors: std.ArrayList(Position), + length: u32, + + pub fn create(predecessor: Position, length: u32, allocator: std.mem.Allocator) !PredecessorsAndLength { + var list = std.ArrayList(Position).init(allocator); + try list.append(predecessor); + return PredecessorsAndLength{ .predecessors = list, .length = length }; + } + + pub fn deinit(self: *PredecessorsAndLength) void { + self.predecessors.deinit(); + } +}; + +fn part_two(is_test_case: bool) !u32 { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + const built = try build_map_and_start_and_end(is_test_case, allocator); + const map = built.map; + const start_position = built.start_position; + const target_point = built.target_point; + defer allocator.free(map); + defer { + for (map) |line| { + allocator.free(line); + } + } + + var distances = std.AutoHashMap(Position, PredecessorsAndLength).init(allocator); + defer distances.deinit(); + defer { + var preds_it = distances.valueIterator(); + while (preds_it.next()) |val| { + val.deinit(); + } + } + var visited_positions = std.AutoHashMap(Position, bool).init(allocator); + defer visited_positions.deinit(); + + try distances.put(start_position, PredecessorsAndLength{ .predecessors = std.ArrayList(Position).init(allocator), .length = 0 }); + var current_position = start_position; + var current_distance: u32 = 0; + var found_lowest_distance_to_target: ?u32 = null; + while (true) { + if (std.meta.eql(current_position.point, target_point) and found_lowest_distance_to_target == null) { + found_lowest_distance_to_target = current_distance; + } + const moves = try find_valid_moves(map, current_position, allocator); + + for (moves) |move| { + if (visited_positions.contains(move.position)) { + continue; + } + + const distance_from_here = current_distance + move.cost; + if (distances.contains(move.position)) { + const current_lowest_distance = distances.get(move.position).?.length; + if (distance_from_here < current_lowest_distance) { + // print("DEBUG - found a new lowest distance for {}/{} ({}) - moving from {} to {}\n", .{ move.position.point.x, move.position.point.y, move.position.heading, current_lowest_distance, distance_from_here }); + var stale_value = distances.get(move.position).?; + stale_value.deinit(); + try distances.put(move.position, try PredecessorsAndLength.create(current_position, distance_from_here, allocator)); + } + if (distance_from_here == current_lowest_distance) { + try distances.getPtr(move.position).?.predecessors.append(current_position); + } + } else { + // print("DEBUG - found fresh lowest distance for {}/{} ({}) - {}\n", .{ move.position.point.x, move.position.point.y, move.position.heading, distance_from_here }); + try distances.put(move.position, try PredecessorsAndLength.create(current_position, distance_from_here, allocator)); + } + } + allocator.free(moves); + + try visited_positions.put(current_position, true); + + // Find the next candidate by iterating over all unvisited nodes with non-infinite distance, and picking the one + // with lowest distance. + // There would almost-certainly be a way to optimize this with a min-queue if we cared. + var next_position: Position = undefined; + // var lowest_distance_found: u32 = std.math.inf(u32); + // above gives `error: reached unreachable code` + var lowest_distance_found: u32 = 999999999; + var dist_it = distances.iterator(); + while (dist_it.next()) |entry| { + // print("DEBUG - checking whether {}/{}({}) is valid as next current_position - ", .{ entry.key_ptr.point.x, entry.key_ptr.point.y, entry.key_ptr.heading }); + if (visited_positions.contains(entry.key_ptr.*)) { + // print("no, because it's been visited already\n", .{}); + continue; + } + if (entry.value_ptr.*.length > lowest_distance_found) { + // print("no, because its distance ({}) is higher than the lowest found so far ({})\n", .{ entry.value_ptr.*, lowest_distance_found }); + continue; + } + // print("it is!\n", .{}); + // print("{}/{}({}) is a valid next-position\n", .{ entry.key_ptr.point.x, entry.key_ptr.point.y, entry.key_ptr.heading }); + next_position = entry.key_ptr.*; + lowest_distance_found = entry.value_ptr.*.length; + } + current_position = next_position; + current_distance = lowest_distance_found; + // If we are dealing with distances larger than _a_ found-distance-to-target, then (because all edge-lengths are + // positive) no further paths to be found can be shorter - therefore we've found all possible shortest paths. + if (found_lowest_distance_to_target != null and current_distance > found_lowest_distance_to_target.?) { + break; + } + } + print("DEBUG - finished finding all paths to target_point\n", .{}); + + // Iterate back over the predecessors of paths that end at the target_point - all of those are on shortest-paths + var points_on_shortest_paths = std.AutoHashMap(Point, bool).init(allocator); + defer points_on_shortest_paths.deinit(); + var positions_to_be_processed = std.AutoHashMap(Position, bool).init(allocator); + defer positions_to_be_processed.deinit(); + + for (0..4) |i| { + const target = Position{ .point = target_point, .heading = @enumFromInt(i) }; + if (distances.contains(target)) { + try positions_to_be_processed.put(target, true); + } + } + + while (true) { + var position_it = positions_to_be_processed.keyIterator(); + if (position_it.next()) |pos| { + const actual_pos = pos.*; + // print("DEBUG - found a position to be processed - it is {}/{}\n", .{ pos.point.x, pos.point.y }); + if (distances.contains(actual_pos)) { + try points_on_shortest_paths.put(actual_pos.point, true); + // print("DEBUG - it's on a shortest path, has been added\n", .{}); + for (distances.get(actual_pos).?.predecessors.items) |pred| { + // print("DEBUG - adding {}/{} to the positions_to_be_processed\n", .{ pred.point.x, pred.point.y }); + try positions_to_be_processed.put(pred, true); + } + } + _ = positions_to_be_processed.remove(actual_pos); + } else { + // `positions_to_be_processed` is empty - stop looping + // print("DEBUG - there are no more positions_to_be_processed - stopping processing\n", .{}); + break; + } + } + print("DEBUG - points_on_shortest_paths are: ", .{}); + var count: u32 = 0; + var key_it = points_on_shortest_paths.keyIterator(); + while (key_it.next()) |point| { + count += 1; + print("{}/{}, ", .{ point.x, point.y }); + } + print("\n", .{}); + return count; +} + +fn build_map_and_start_and_end(is_test_case: bool, allocator: std.mem.Allocator) !struct { map: [][]u8, start_position: Position, target_point: Point } { + const input_file = try util.getInputFile("16", is_test_case); + const data = try util.readAllInputWithAllocator(input_file, allocator); + defer allocator.free(data); + + var map_list = std.ArrayList([]u8).init(allocator); + + var it = std.mem.splitScalar(u8, data, '\n'); + var start_position: Position = undefined; + var target_point: Point = undefined; + var line_counter: usize = 0; + + while (it.next()) |line| : (line_counter += 1) { + var line_list = std.ArrayList(u8).init(allocator); + for (line) |c| { + try line_list.append(c); + } + try map_list.append(try line_list.toOwnedSlice()); + + const index_of_s = std.mem.indexOf(u8, line, "S"); + if (index_of_s != null) { + start_position = Position{ .point = Point{ .x = index_of_s.?, .y = line_counter }, .heading = Heading.east }; + } + + const index_of_e = std.mem.indexOf(u8, line, "E"); + if (index_of_e != null) { + target_point = Point{ .x = index_of_e.?, .y = line_counter }; + } + } + + return .{ .map = try map_list.toOwnedSlice(), .start_position = start_position, .target_point = target_point }; +} + const Move = struct { position: Position, cost: u32 }; fn find_valid_moves(map: [][]u8, current_position: Position, allocator: std.mem.Allocator) ![]Move { @@ -208,3 +372,9 @@ test "part_one" { print("DEBUG - part_one_response is {}\n", .{part_one_response}); try expect(part_one_response == 7036); } + +test "part_two" { + const part_two_response = try part_two(true); + print("DEBUG - part_two_response is {}\n", .{part_two_response}); + try expect(part_two_response == 45); +}