aboutsummaryrefslogtreecommitdiff
path: root/day8.zig
diff options
context:
space:
mode:
Diffstat (limited to 'day8.zig')
-rw-r--r--day8.zig208
1 files changed, 208 insertions, 0 deletions
diff --git a/day8.zig b/day8.zig
new file mode 100644
index 0000000..831f751
--- /dev/null
+++ b/day8.zig
@@ -0,0 +1,208 @@
+const std = @import("std");
+pub const log_level: std.log.Level = .info;
+
+pub fn main() !void {
+ try std.fmt.format(std.io.getStdOut().writer(), "Day 8 pt1: {}\n", .{try solve_pt1(std.heap.page_allocator, puzzle_input)});
+ try std.fmt.format(std.io.getStdOut().writer(), "Day 8 pt2: {}\n", .{try solve_pt2(std.heap.page_allocator, puzzle_input)});
+}
+const NodeValue = struct {
+ left: []const u8,
+ right: []const u8,
+};
+
+fn solve_pt1(a: std.mem.Allocator, input: []const u8) !u64 {
+ var spl = std.mem.split(u8, input, "\n\n");
+ const instructions = spl.first();
+ const nodes_str = spl.next() orelse return error.NoNodes;
+ var spl2 = std.mem.split(u8, nodes_str, "\n");
+ var nodes = std.StringHashMap(NodeValue).init(a);
+ defer nodes.deinit();
+ while (spl2.next()) |node_line| {
+ var toks = std.mem.tokenize(u8, node_line, " (,)=");
+ const key = toks.next() orelse return error.NoKey;
+ const left = toks.next() orelse return error.NoLeft;
+ const right = toks.next() orelse return error.NoRight;
+ try nodes.putNoClobber(key, .{
+ .left = left,
+ .right = right,
+ });
+ }
+
+ var steps: u64 = 0;
+ var pos: []const u8 = "AAA";
+ while (!std.mem.eql(u8, pos, "ZZZ")) {
+ for (instructions) |inst| {
+ const node_value = nodes.get(pos).?;
+ pos = switch (inst) {
+ 'R' => node_value.right,
+ 'L' => node_value.left,
+ else => return error.InvalidDirection,
+ };
+ steps += 1;
+ }
+ }
+ return steps;
+}
+test "pt1" {
+ try std.testing.expectEqual(@as(u64, 6), try solve_pt1(std.testing.allocator, test_input));
+}
+const HistEntry = struct {
+ node: []const u8,
+ step: u64,
+};
+const Path = struct {
+ // current position
+ pos: []const u8,
+
+ // list of the _endpoints_ we have reached.
+ endpoints: std.ArrayList(HistEntry),
+
+ // at n*cycle + offset this path is finished where n is integer > 0
+ offset: ?u64 = null,
+ cycle: ?u64 = null,
+
+ fn init(a: std.mem.Allocator, start: []const u8) !Path {
+ return .{
+ .pos = start,
+ .endpoints = std.ArrayList(HistEntry).init(a),
+ };
+ }
+
+ fn visit(self: *Path, node: []const u8) void {
+ self.pos = node;
+ }
+
+ fn check(self: *Path, step: u64) !void {
+ if (self.cycle != null) return; // we already found the cycle for this one
+
+ if (self.pos[2] == 'Z') { // we hit an endpoint
+ for (self.endpoints.items) |hi| { // did we aleady hit this endpoint?
+ if (std.mem.eql(u8, hi.node, self.pos)) {
+ self.offset = hi.step;
+ self.cycle = step - hi.step;
+ return;
+ }
+ }
+ try self.endpoints.append(.{ .node = self.pos, .step = step });
+ }
+ }
+};
+fn solve_pt2(a: std.mem.Allocator, input: []const u8) !u64 {
+ var spl = std.mem.split(u8, input, "\n\n");
+ const instructions = spl.first();
+ const nodes_str = spl.next() orelse return error.NoNodes;
+ var spl2 = std.mem.split(u8, nodes_str, "\n");
+ var nodes = std.StringHashMap(NodeValue).init(a);
+ defer nodes.deinit();
+ while (spl2.next()) |node_line| {
+ var toks = std.mem.tokenize(u8, node_line, " (,)=");
+ const key = toks.next() orelse return error.NoKey;
+ const left = toks.next() orelse return error.NoLeft;
+ const right = toks.next() orelse return error.NoRight;
+ try nodes.putNoClobber(key, .{
+ .left = left,
+ .right = right,
+ });
+ }
+
+ var pos: []Path = blk: {
+ var pl = std.ArrayList(Path).init(a);
+ defer pl.deinit();
+ var ki = nodes.keyIterator();
+ while (ki.next()) |k| {
+ if (k.*[2] == 'A') { // ends with an A
+ std.log.info("start pos {s}", .{k.*});
+ try pl.append(try Path.init(a, k.*));
+ }
+ }
+ break :blk try pl.toOwnedSlice();
+ };
+ defer a.free(pos);
+
+ var log = std.time.timestamp() + 10;
+
+ var steps: u64 = 0;
+ while (!ends(pos)) {
+ // > repeat the whole sequence of instructions as necessary
+ // always repeat the whole set
+ for (instructions) |inst| {
+ for (pos) |*p| {
+ const node_value = nodes.get(p.pos).?;
+ p.visit(switch (inst) {
+ 'R' => node_value.right,
+ 'L' => node_value.left,
+ else => return error.InvalidDirection,
+ });
+ }
+ }
+ steps += instructions.len;
+
+ // logging...
+ const now = std.time.timestamp();
+ if (now > log) {
+ for (pos) |p| {
+ std.log.info("pos {s}", .{p.pos});
+ }
+ log = now + 10;
+ }
+
+ // now check for cycles
+ for (pos) |*p| {
+ try p.check(steps);
+ }
+ // now... check if we have detected a cycle on _all_ tracks
+ for (pos) |p| {
+ if (p.cycle == null) break;
+ } else {
+ std.log.info("all routes have cycled, using LCM", .{});
+ // great, so we haven't reached the end but we can probably calculate it.
+ var l = lcm(pos[0].cycle.?, pos[1].cycle.?);
+ for (pos[2..]) |p| {
+ l = lcm(l, p.cycle.?);
+ }
+ // so, this is our answer?
+ return l;
+ }
+ }
+ return steps;
+}
+
+fn ends(pos: []Path) bool {
+ for (pos) |p| {
+ if (p.pos[2] != 'Z') {
+ return false;
+ }
+ } else {
+ std.log.err("ends true", .{});
+ for (pos) |p| {
+ std.log.err("{s}", .{p.pos});
+ }
+ return true;
+ }
+}
+fn lcm(a: u64, b: u64) u64 {
+ return (a * b) / std.math.gcd(a, b);
+}
+test "pt2" {
+ try std.testing.expectEqual(@as(u64, 6), try solve_pt2(std.testing.allocator, test_input2));
+}
+const test_input =
+ \\LLR
+ \\
+ \\AAA = (BBB, BBB)
+ \\BBB = (AAA, ZZZ)
+ \\ZZZ = (ZZZ, ZZZ)
+;
+const test_input2 =
+ \\LR
+ \\
+ \\11A = (11B, XXX)
+ \\11B = (XXX, 11Z)
+ \\11Z = (11B, XXX)
+ \\22A = (22B, XXX)
+ \\22B = (22C, 22C)
+ \\22C = (22Z, 22Z)
+ \\22Z = (22B, 22B)
+ \\XXX = (XXX, XXX)
+;
+const puzzle_input = @embedFile("day8.in");