aoc2023

Advent of Code 2023
Log | Files | Refs | README

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");