day8.zig (6332B)
1 const std = @import("std"); 2 pub const log_level: std.log.Level = .info; 3 4 pub fn main() !void { 5 try std.fmt.format(std.io.getStdOut().writer(), "Day 8 pt1: {}\n", .{try solve_pt1(std.heap.page_allocator, puzzle_input)}); 6 try std.fmt.format(std.io.getStdOut().writer(), "Day 8 pt2: {}\n", .{try solve_pt2(std.heap.page_allocator, puzzle_input)}); 7 } 8 const NodeValue = struct { 9 left: []const u8, 10 right: []const u8, 11 }; 12 13 fn solve_pt1(a: std.mem.Allocator, input: []const u8) !u64 { 14 var spl = std.mem.split(u8, input, "\n\n"); 15 const instructions = spl.first(); 16 const nodes_str = spl.next() orelse return error.NoNodes; 17 var spl2 = std.mem.split(u8, nodes_str, "\n"); 18 var nodes = std.StringHashMap(NodeValue).init(a); 19 defer nodes.deinit(); 20 while (spl2.next()) |node_line| { 21 var toks = std.mem.tokenize(u8, node_line, " (,)="); 22 const key = toks.next() orelse return error.NoKey; 23 const left = toks.next() orelse return error.NoLeft; 24 const right = toks.next() orelse return error.NoRight; 25 try nodes.putNoClobber(key, .{ 26 .left = left, 27 .right = right, 28 }); 29 } 30 31 var steps: u64 = 0; 32 var pos: []const u8 = "AAA"; 33 while (!std.mem.eql(u8, pos, "ZZZ")) { 34 for (instructions) |inst| { 35 const node_value = nodes.get(pos).?; 36 pos = switch (inst) { 37 'R' => node_value.right, 38 'L' => node_value.left, 39 else => return error.InvalidDirection, 40 }; 41 steps += 1; 42 } 43 } 44 return steps; 45 } 46 test "pt1" { 47 try std.testing.expectEqual(@as(u64, 6), try solve_pt1(std.testing.allocator, test_input)); 48 } 49 const HistEntry = struct { 50 node: []const u8, 51 step: u64, 52 }; 53 const Path = struct { 54 // current position 55 pos: []const u8, 56 57 // list of the _endpoints_ we have reached. 58 endpoints: std.ArrayList(HistEntry), 59 60 // at n*cycle + offset this path is finished where n is integer > 0 61 offset: ?u64 = null, 62 cycle: ?u64 = null, 63 64 fn init(a: std.mem.Allocator, start: []const u8) !Path { 65 return .{ 66 .pos = start, 67 .endpoints = std.ArrayList(HistEntry).init(a), 68 }; 69 } 70 71 fn visit(self: *Path, node: []const u8) void { 72 self.pos = node; 73 } 74 75 fn check(self: *Path, step: u64) !void { 76 if (self.cycle != null) return; // we already found the cycle for this one 77 78 if (self.pos[2] == 'Z') { // we hit an endpoint 79 for (self.endpoints.items) |hi| { // did we aleady hit this endpoint? 80 if (std.mem.eql(u8, hi.node, self.pos)) { 81 self.offset = hi.step; 82 self.cycle = step - hi.step; 83 return; 84 } 85 } 86 try self.endpoints.append(.{ .node = self.pos, .step = step }); 87 } 88 } 89 }; 90 fn solve_pt2(a: std.mem.Allocator, input: []const u8) !u64 { 91 var spl = std.mem.split(u8, input, "\n\n"); 92 const instructions = spl.first(); 93 const nodes_str = spl.next() orelse return error.NoNodes; 94 var spl2 = std.mem.split(u8, nodes_str, "\n"); 95 var nodes = std.StringHashMap(NodeValue).init(a); 96 defer nodes.deinit(); 97 while (spl2.next()) |node_line| { 98 var toks = std.mem.tokenize(u8, node_line, " (,)="); 99 const key = toks.next() orelse return error.NoKey; 100 const left = toks.next() orelse return error.NoLeft; 101 const right = toks.next() orelse return error.NoRight; 102 try nodes.putNoClobber(key, .{ 103 .left = left, 104 .right = right, 105 }); 106 } 107 108 var pos: []Path = blk: { 109 var pl = std.ArrayList(Path).init(a); 110 defer pl.deinit(); 111 var ki = nodes.keyIterator(); 112 while (ki.next()) |k| { 113 if (k.*[2] == 'A') { // ends with an A 114 std.log.info("start pos {s}", .{k.*}); 115 try pl.append(try Path.init(a, k.*)); 116 } 117 } 118 break :blk try pl.toOwnedSlice(); 119 }; 120 defer a.free(pos); 121 122 var log = std.time.timestamp() + 10; 123 124 var steps: u64 = 0; 125 while (!ends(pos)) { 126 // > repeat the whole sequence of instructions as necessary 127 // always repeat the whole set 128 for (instructions) |inst| { 129 for (pos) |*p| { 130 const node_value = nodes.get(p.pos).?; 131 p.visit(switch (inst) { 132 'R' => node_value.right, 133 'L' => node_value.left, 134 else => return error.InvalidDirection, 135 }); 136 } 137 } 138 steps += instructions.len; 139 140 // logging... 141 const now = std.time.timestamp(); 142 if (now > log) { 143 for (pos) |p| { 144 std.log.info("pos {s}", .{p.pos}); 145 } 146 log = now + 10; 147 } 148 149 // now check for cycles 150 for (pos) |*p| { 151 try p.check(steps); 152 } 153 // now... check if we have detected a cycle on _all_ tracks 154 for (pos) |p| { 155 if (p.cycle == null) break; 156 } else { 157 std.log.info("all routes have cycled, using LCM", .{}); 158 // great, so we haven't reached the end but we can probably calculate it. 159 var l = lcm(pos[0].cycle.?, pos[1].cycle.?); 160 for (pos[2..]) |p| { 161 l = lcm(l, p.cycle.?); 162 } 163 // so, this is our answer? 164 return l; 165 } 166 } 167 return steps; 168 } 169 170 fn ends(pos: []Path) bool { 171 for (pos) |p| { 172 if (p.pos[2] != 'Z') { 173 return false; 174 } 175 } else { 176 std.log.err("ends true", .{}); 177 for (pos) |p| { 178 std.log.err("{s}", .{p.pos}); 179 } 180 return true; 181 } 182 } 183 fn lcm(a: u64, b: u64) u64 { 184 return (a * b) / std.math.gcd(a, b); 185 } 186 test "pt2" { 187 try std.testing.expectEqual(@as(u64, 6), try solve_pt2(std.testing.allocator, test_input2)); 188 } 189 const test_input = 190 \\LLR 191 \\ 192 \\AAA = (BBB, BBB) 193 \\BBB = (AAA, ZZZ) 194 \\ZZZ = (ZZZ, ZZZ) 195 ; 196 const test_input2 = 197 \\LR 198 \\ 199 \\11A = (11B, XXX) 200 \\11B = (XXX, 11Z) 201 \\11Z = (11B, XXX) 202 \\22A = (22B, XXX) 203 \\22B = (22C, 22C) 204 \\22C = (22Z, 22Z) 205 \\22Z = (22B, 22B) 206 \\XXX = (XXX, XXX) 207 ; 208 const puzzle_input = @embedFile("day8.in");