2024-12-27 18:05:58 -08:00

263 lines
9.9 KiB
Zig

const std = @import("std");
const print = std.debug.print;
const util = @import("util.zig");
const Heading = enum {
north,
east,
south,
west,
fn rotate(self: Heading) Heading {
return switch (self) {
Heading.north => Heading.east,
Heading.east => Heading.south,
Heading.south => Heading.west,
Heading.west => Heading.north,
};
}
};
const Location = struct { x: usize, y: usize };
pub fn main() !void {
const response = try part_one(false);
print("{}\n", .{response});
}
pub fn part_one(is_test_case: bool) !u32 {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const input_file = try util.getInputFile("06", is_test_case);
const data = try util.readAllInputWithAllocator(input_file, allocator);
// https://stackoverflow.com/a/79199470/1040915
var it = std.mem.splitScalar(u8, data, '\n');
var lines_list = std.ArrayList([]u8).init(allocator);
defer lines_list.deinit();
while (it.next()) |line| {
if (line.len > 1) {
var mut_copy = try allocator.alloc(u8, line.len);
// The line above is leaking memory all over the place, but what's a boy to do?
// The only solution I know (`defer allocator.free(mut_copy);`) leads to segmentation faults
// Related to (though not the same as) my question here: https://stackoverflow.com/questions/79305128/how-to-idiomatically-return-an-accumulated-slice-array-from-a-zig-function
var idx: usize = 0;
while (idx < line.len) : (idx += 1) {
mut_copy[idx] = line[idx];
}
try lines_list.append(mut_copy);
}
}
var lines = lines_list.items;
// Find starting point
var x: usize = 0;
var location: Location = undefined;
while (x < lines[0].len) : (x += 1) {
var y: usize = 0;
while (y < lines.len) : (y += 1) {
if (lines[y][x] == '^') {
location = Location{ .x = x, .y = y };
lines[y][x] = '.';
}
}
}
print("DEBUG - starting location is {}\n", .{location});
var heading = Heading.north;
var total_distinct_locations: u32 = 0;
while (true) {
const next_square = getNextSquare(location, heading, lines[0].len, lines.len) catch break;
if (lines[next_square.y][next_square.x] == '#') {
heading = heading.rotate();
continue;
} else {
if (lines[location.y][location.x] != 'X') {
lines[location.y][location.x] = 'X';
total_distinct_locations += 1;
}
location = next_square;
}
}
return total_distinct_locations + 1;
}
const NavigationError = error{ OutOfBounds, UnexpectedTriplicateVisit };
fn getNextSquare(current_square: Location, heading: Heading, max_x: usize, max_y: usize) !Location {
return switch (heading) {
Heading.north => {
if (current_square.y == 0) {
return NavigationError.OutOfBounds;
} else {
return Location{ .x = current_square.x, .y = current_square.y - 1 };
}
},
Heading.east => {
if (current_square.x == max_x - 1) {
return NavigationError.OutOfBounds;
} else {
return Location{ .x = current_square.x + 1, .y = current_square.y };
}
},
Heading.south => {
if (current_square.y == max_y - 1) {
return NavigationError.OutOfBounds;
} else {
return Location{ .x = current_square.x, .y = current_square.y + 1 };
}
},
Heading.west => {
if (current_square.x == 0) {
return NavigationError.OutOfBounds;
} else {
return Location{ .x = current_square.x - 1, .y = current_square.y };
}
},
};
}
// Not a working implementation - I'm moving on to other problems rather than continuing to struggle on something that's
// a) not interesting me, and b) not teaching me new techniques.
pub fn part_two(is_test_case: bool) !u32 {
// Mostly the same logic as part_one to start with, but:
// * when leaving a square, note the heading it was left with
// * when visiting a square that has previously been visited, check if it is relatively-right (i.e. if moving north,
// check if the square was previously-departed to the east). If so - this is an opportunity to create a loop
// * make sure to note all these opportunities and dedupe them rather than counting them naively!
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const input_file = try util.getInputFile("06", is_test_case);
const data = try util.readAllInputWithAllocator(input_file, allocator);
// https://stackoverflow.com/a/79199470/1040915
var it = std.mem.splitScalar(u8, data, '\n');
var lines_list = std.ArrayList([]u8).init(allocator);
defer lines_list.deinit();
while (it.next()) |line| {
if (line.len > 1) {
var mut_copy = try allocator.alloc(u8, line.len);
// The line above is leaking memory all over the place, but what's a boy to do?
// The only solution I know (`defer allocator.free(mut_copy);`) leads to segmentation faults
// Related to (though not the same as) my question here: https://stackoverflow.com/questions/79305128/how-to-idiomatically-return-an-accumulated-slice-array-from-a-zig-function
var idx: usize = 0;
while (idx < line.len) : (idx += 1) {
mut_copy[idx] = line[idx];
}
try lines_list.append(mut_copy);
}
}
var lines = lines_list.items;
// Find starting point
var x: usize = 0;
var location: Location = undefined;
outer: while (x < lines[0].len) : (x += 1) {
var y: usize = 0;
while (y < lines.len) : (y += 1) {
if (lines[y][x] == '^') {
location = Location{ .x = x, .y = y };
lines[y][x] = '.';
break :outer;
}
}
}
print("DEBUG - starting location is {}\n", .{location});
var heading = Heading.north;
var potential_block_locations = std.ArrayList(Location).init(allocator);
while (true) {
const next_square = getNextSquare(location, heading, lines[0].len, lines.len) catch break;
if (lines[next_square.y][next_square.x] == '#') {
heading = heading.rotate();
continue;
} else {
// Next square is not an obstacle, so update departed square then move into the target
// Having just moved into a square - if the just-moved-into-square has already been visited, and was departed
// relatively-right, then the _next_ square (if it's not an obstacle) is a potential block-location
if (lines[location.y][location.x] != '.') {
try potential_block_locations.append(next_square);
print("DEBUG - adding a potential_block_location - {}\n", .{next_square});
}
try updateDepartedSquare(&lines, location.y, location.x, heading);
location = next_square;
}
}
// TODO - dedupe potential_block_locations and return size
return @intCast(potential_block_locations.items.len);
}
fn updateDepartedSquare(lines: *[][]u8, y: usize, x: usize, heading: Heading) !void {
// I'm going to naively implement this on the assumption that there are only two opportunities:
// * The square has never been visited before (so, update content to `URDL` for headings)
// * The square has been visited exactly once (so, update content to just `X`)
// (I.e. I am assuming it's not possible for a square to be visited three times, and equivalently that we don't
// need to record the specific cases of a 2-visit)
// I am pretty sure that's true (otherwise we'd be in a loop already), but I guess we'll see from the test-cases :P
const current_value = lines.*[y][x];
var new_value: u8 = undefined;
if (current_value == '.') {
if (heading == Heading.north) {
new_value = 'U';
}
if (heading == Heading.east) {
new_value = 'R';
}
if (heading == Heading.south) {
new_value = 'D';
}
if (heading == Heading.west) {
new_value = 'L';
}
} else {
if (current_value == 'U' or current_value == 'R' or current_value == 'D' or current_value == 'L') {
new_value = 'X';
} else {
return NavigationError.UnexpectedTriplicateVisit;
}
}
lines.*[y][x] = new_value;
// Below is a more elegant implementation, but something's off in the type-system
// const current_value = lines.*[y][x];
// const new_value: u8 = switch (current_value) {
// '.' => {
// switch (heading) {
// Heading.north => 'U',
// Heading.east => 'R',
// Heading.south => 'D',
// Heading.west => 'L',
// }
// },
// 'U', 'R', 'D', 'L' => 'X',
// else => {
// return NavigationError.UnexpectedTriplicateVisit;
// },
// };
// lines[y][x] = new_value;
}
const expect = std.testing.expect;
test "part_one" {
const part_one_response = try part_one(true);
print("DEBUG - part_one_response is {}\n", .{part_one_response});
try expect(part_one_response == 41);
}
test "part_two" {
// const part_two_response = part_two(true) catch |err| {
// print("DEBUG - error from part_two {}\n", .{err});
// return;
// };
const part_two_response = try part_two(true);
print("DEBUG - part_two_response is {}\n", .{part_two_response});
try expect(part_two_response == 6);
}