Solution to 18-1
This commit is contained in:
parent
0949fc75fd
commit
6914093f48
1
NOTES.md
1
NOTES.md
@ -12,6 +12,7 @@ Notes, thoughts, or questions that arose as I implemented the solutions. Hopeful
|
|||||||
* Better error-handling than GoLang's (though that bar is set _real_ low). I have only just scratched the surface, though, it looks interestingly powerful - might well be even better than I've realized at this point!
|
* Better error-handling than GoLang's (though that bar is set _real_ low). I have only just scratched the surface, though, it looks interestingly powerful - might well be even better than I've realized at this point!
|
||||||
* Creation of "bare" structs - i.e. you can do `myFunction(.{thing})` rather than `myFunction(StructName{thing})` (looking at you, GoLang)
|
* Creation of "bare" structs - i.e. you can do `myFunction(.{thing})` rather than `myFunction(StructName{thing})` (looking at you, GoLang)
|
||||||
* [Continue expressions](https://ziglang.org/documentation/master/#while) - don't need to remember to put the index-incrementing code at the end of every branch!
|
* [Continue expressions](https://ziglang.org/documentation/master/#while) - don't need to remember to put the index-incrementing code at the end of every branch!
|
||||||
|
* And returning from `while` loops with `break <value>` and `else <value>`!
|
||||||
* Great powerful `switch` syntax (though not as powerful as Rust's)
|
* Great powerful `switch` syntax (though not as powerful as Rust's)
|
||||||
* Labelled loops - _usually_ should be avoided, but helpful on occasion!
|
* Labelled loops - _usually_ should be avoided, but helpful on occasion!
|
||||||
* `defer` - though, unlike in GoLang where it's a nice-to-have that allows one to do cleanup, here it is _absolutely essential_ for all the manual `deinit`s and `free`s
|
* `defer` - though, unlike in GoLang where it's a nice-to-have that allows one to do cleanup, here it is _absolutely essential_ for all the manual `deinit`s and `free`s
|
||||||
|
3450
inputs/18/real.txt
Normal file
3450
inputs/18/real.txt
Normal file
File diff suppressed because it is too large
Load Diff
25
inputs/18/test.txt
Normal file
25
inputs/18/test.txt
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
5,4
|
||||||
|
4,2
|
||||||
|
4,5
|
||||||
|
3,0
|
||||||
|
2,1
|
||||||
|
6,3
|
||||||
|
2,4
|
||||||
|
1,5
|
||||||
|
0,6
|
||||||
|
3,3
|
||||||
|
2,6
|
||||||
|
5,1
|
||||||
|
1,2
|
||||||
|
5,5
|
||||||
|
2,5
|
||||||
|
6,5
|
||||||
|
1,4
|
||||||
|
0,4
|
||||||
|
6,4
|
||||||
|
1,1
|
||||||
|
6,1
|
||||||
|
1,0
|
||||||
|
0,5
|
||||||
|
1,6
|
||||||
|
2,0
|
166
solutions/18.zig
Normal file
166
solutions/18.zig
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const print = std.debug.print;
|
||||||
|
const util = @import("util.zig");
|
||||||
|
const log = util.log;
|
||||||
|
const Point = util.Point;
|
||||||
|
const expect = std.testing.expect;
|
||||||
|
|
||||||
|
// As of this day I started initializing the Allocator in `main` instead of `part_<whatever>` - the higher-level it is,
|
||||||
|
// the less repetition and (more importantly) the more likely I'll be able to deallocate anything returned from
|
||||||
|
// lower-level functions.
|
||||||
|
//
|
||||||
|
// Also while researching the previous day I realized how useful `catch unreachable` is for non-production-grade code
|
||||||
|
// like this - silently-swallowing irrelevant errors like `OutOfMemory`.
|
||||||
|
pub fn main() !void {
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
defer _ = gpa.deinit();
|
||||||
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
|
const response = try partOne(false, false, allocator);
|
||||||
|
print("{}\n", .{response});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn partOne(is_test_case: bool, debug: bool, allocator: std.mem.Allocator) !u32 {
|
||||||
|
if (debug) {
|
||||||
|
print("DEBUG LOGGING IS ENABLED\n", .{});
|
||||||
|
}
|
||||||
|
const dimension: usize = if (is_test_case) 7 else 71;
|
||||||
|
const data_volume: usize = if (is_test_case) 12 else 1024;
|
||||||
|
|
||||||
|
const map = buildMap(dimension, allocator);
|
||||||
|
defer allocator.free(map);
|
||||||
|
defer {
|
||||||
|
for (map) |line| {
|
||||||
|
allocator.free(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const input_file = try util.getInputFile("18", is_test_case);
|
||||||
|
const data = try util.readAllInputWithAllocator(input_file, allocator);
|
||||||
|
defer allocator.free(data);
|
||||||
|
|
||||||
|
var data_lines_it = std.mem.splitScalar(u8, data, '\n');
|
||||||
|
var count: usize = 0;
|
||||||
|
while (data_lines_it.next()) |line| : (count += 1) {
|
||||||
|
const comma_index = std.mem.indexOf(u8, line, ",").?;
|
||||||
|
const x = std.fmt.parseInt(u8, line[0..comma_index], 10) catch unreachable;
|
||||||
|
const y = std.fmt.parseInt(u8, line[comma_index + 1 ..], 10) catch unreachable;
|
||||||
|
map[y][x] = '#';
|
||||||
|
if (count >= data_volume - 1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For debugging purposes
|
||||||
|
if (debug) {
|
||||||
|
print("Printing the map for your own visualization:\n", .{});
|
||||||
|
for (map) |line| {
|
||||||
|
for (line) |c| {
|
||||||
|
print("{c}", .{c});
|
||||||
|
}
|
||||||
|
print("\n", .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hello Dijkstra my old friend...
|
||||||
|
var visited = std.AutoHashMap(Point, void).init(allocator);
|
||||||
|
defer visited.deinit();
|
||||||
|
|
||||||
|
var distances = std.AutoHashMap(Point, u32).init(allocator);
|
||||||
|
defer distances.deinit();
|
||||||
|
distances.put(Point{ .x = 0, .y = 0 }, 0) catch unreachable;
|
||||||
|
|
||||||
|
var candidates = std.AutoHashMap(Point, void).init(allocator);
|
||||||
|
candidates.put(Point{ .x = 0, .y = 0 }, {}) catch unreachable;
|
||||||
|
defer candidates.deinit();
|
||||||
|
|
||||||
|
return while (true) {
|
||||||
|
var cand_it = candidates.keyIterator();
|
||||||
|
var current_point: Point = undefined;
|
||||||
|
var lowest_distance_found: u32 = std.math.maxInt(u32);
|
||||||
|
while (cand_it.next()) |cand| {
|
||||||
|
const actual_point = cand.*;
|
||||||
|
log("Considering {s} as the next current_point ", .{actual_point}, debug);
|
||||||
|
if (visited.contains(actual_point)) {
|
||||||
|
log("but rejecting it because it's been visited already\n", .{}, debug);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const distance_of_candidate = distances.get(actual_point) orelse std.math.maxInt(u32);
|
||||||
|
if (distance_of_candidate < lowest_distance_found) {
|
||||||
|
log("and it is a possibility!\n", .{}, debug);
|
||||||
|
current_point = actual_point;
|
||||||
|
lowest_distance_found = distance_of_candidate;
|
||||||
|
} else {
|
||||||
|
log("but rejecting it because it already has a shorter minimum-distance ({} vs. {})\n", .{ distance_of_candidate, lowest_distance_found }, debug);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log("Found point {s} as current_point - it has distance {}\n", .{ current_point, lowest_distance_found }, debug);
|
||||||
|
if (lowest_distance_found == std.math.maxInt(u32)) {
|
||||||
|
print("ERROR - iterated over all candidates, but found none with non-infinite distance\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for termination condition
|
||||||
|
if (current_point.x == dimension - 1 and current_point.y == dimension - 1) {
|
||||||
|
break lowest_distance_found;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Haven't terminated => we're still on a non-target node. Check neighbours, and update their min-distance.
|
||||||
|
|
||||||
|
const distance_of_neighbour_from_current_point = lowest_distance_found + 1;
|
||||||
|
const neighbours = current_point.neighbours(dimension, dimension, allocator);
|
||||||
|
for (neighbours) |neighbour| {
|
||||||
|
if (visited.contains(neighbour)) {
|
||||||
|
log("Not processing {s} because it's already been visited\n", .{neighbour}, debug);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (map[neighbour.y][neighbour.x] == '.') {
|
||||||
|
const distance_response = distances.getOrPut(neighbour) catch unreachable;
|
||||||
|
if (!distance_response.found_existing) {
|
||||||
|
log("Adding a new (first) distance to {s} (via {s}) - {}\n", .{ neighbour, current_point, distance_of_neighbour_from_current_point }, debug);
|
||||||
|
distance_response.value_ptr.* = distance_of_neighbour_from_current_point;
|
||||||
|
} else {
|
||||||
|
if (distance_response.value_ptr.* > distance_of_neighbour_from_current_point) {
|
||||||
|
log("Overriding distance for neighbour {s} because distance of path from {s} ({}) is less than current value ({})\n", .{ neighbour, current_point, distance_of_neighbour_from_current_point, distance_response.value_ptr.* }, debug);
|
||||||
|
distance_response.value_ptr.* = distance_of_neighbour_from_current_point;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log("Adding {s} to candidates\n", .{neighbour}, debug);
|
||||||
|
candidates.put(neighbour, {}) catch unreachable;
|
||||||
|
} else {
|
||||||
|
log("Not processing neighbour {s} because it is blocked off\n", .{neighbour}, debug);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
allocator.free(neighbours);
|
||||||
|
|
||||||
|
// Update sets for next iteration
|
||||||
|
visited.put(current_point, {}) catch unreachable;
|
||||||
|
// Not strictly necessary, as we already check for visited whenever iterating over candidates - but this will
|
||||||
|
// prevent some unnecessary double-processing.
|
||||||
|
_ = candidates.remove(current_point);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn buildMap(dimension: usize, allocator: std.mem.Allocator) [][]u8 {
|
||||||
|
var map_list = std.ArrayList([]u8).init(allocator);
|
||||||
|
for (0..dimension) |_| {
|
||||||
|
var line = std.ArrayList(u8).init(allocator);
|
||||||
|
for (0..dimension) |_| {
|
||||||
|
line.append('.') catch unreachable;
|
||||||
|
}
|
||||||
|
map_list.append(line.toOwnedSlice() catch unreachable) catch unreachable;
|
||||||
|
}
|
||||||
|
return map_list.toOwnedSlice() catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
test "partOne" {
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
defer _ = gpa.deinit();
|
||||||
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
|
const response = try partOne(true, true, allocator);
|
||||||
|
print("Response is {}\n", .{response});
|
||||||
|
try expect(response == 22);
|
||||||
|
}
|
@ -93,6 +93,45 @@ pub fn magnitude(num: i32) u32 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// These are used in so many of these types of puzzles - I should have just implemented them at the start, rather than
|
||||||
|
// so late in the challenge (Day 18 :P )
|
||||||
|
// (Though, in my defence, I didn't know what datatype would be appropriate, and I was quite a long way from being able
|
||||||
|
// to confidently use generics in Zig at that point)
|
||||||
|
pub const Point = struct {
|
||||||
|
x: usize,
|
||||||
|
y: usize,
|
||||||
|
pub fn neighbours(self: *Point, width: usize, height: usize, allocator: std.mem.Allocator) []Point {
|
||||||
|
var response = std.ArrayList(Point).init(allocator);
|
||||||
|
if (self.x > 0) {
|
||||||
|
response.append(Point{ .x = self.x - 1, .y = self.y }) catch unreachable;
|
||||||
|
}
|
||||||
|
if (self.y > 0) {
|
||||||
|
response.append(Point{ .x = self.x, .y = self.y - 1 }) catch unreachable;
|
||||||
|
}
|
||||||
|
if (self.x < width - 1) {
|
||||||
|
response.append(Point{ .x = self.x + 1, .y = self.y }) catch unreachable;
|
||||||
|
}
|
||||||
|
if (self.y < height - 1) {
|
||||||
|
response.append(Point{ .x = self.x, .y = self.y + 1 }) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.toOwnedSlice() catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format(self: Point, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
|
||||||
|
_ = fmt;
|
||||||
|
_ = options;
|
||||||
|
|
||||||
|
try writer.print("[{},{}]", .{ self.x, self.y });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn log(comptime message: []const u8, args: anytype, debug: bool) void {
|
||||||
|
if (debug) {
|
||||||
|
std.debug.print(message, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const expect = @import("std").testing.expect;
|
const expect = @import("std").testing.expect;
|
||||||
|
|
||||||
test {
|
test {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user