const aoc = @import("./aoc.zig"); const std = @import("std"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; const NodePart = u6; const NodeLabel = [3]NodePart; const NodeJunction = struct { from : NodeLabel, left : NodeLabel, right: NodeLabel }; const Direction = enum { Left, Right }; const DirectionList = std.ArrayList(Direction); const NodeJunctionList = std.ArrayList(NodeJunction); const Input = struct { instructions: DirectionList, nodes: NodeJunctionList, fn deinit(self: *const Input) void { self.instructions.deinit(); self.nodes.deinit(); } }; const NodeSplit = struct { name: NodePart, left: NodeLabel, right: NodeLabel, }; const NodeMap = std.ArrayList(NodeMapBranch); const NodeMapBranch = struct { name: NodePart, nodes: std.ArrayList(NodeMapLeaf) }; const NodeMapLeaf = struct { name: NodePart, nodes: std.ArrayList(NodeSplit) }; fn parse_instructions(allocator: Allocator, line: []const u8) !DirectionList { var instructions = DirectionList.init(allocator); errdefer instructions.deinit(); for (line) |c| { const direction = switch (c) { 'R' => Direction.Right, 'L' => Direction.Left, else => unreachable }; try instructions.append(direction); } return instructions; } fn parse_char_to_node_part(c: u8) NodePart { if (std.ascii.isAlphabetic(c)) { assert(std.ascii.isUpper(c)); return @intCast(c - 'A' + 10); } else if (std.ascii.isDigit(c)) { return @intCast(c - '0'); } else { unreachable; } } fn parse_node_label(text: []const u8) NodeLabel { assert(text.len == 3); var label: NodeLabel = undefined; inline for (0..3) |i| { label[i] = parse_char_to_node_part(text[i]); } return label; } fn parse_nodes(allocator: Allocator, lines: []const []const u8) !NodeJunctionList { var nodes = try NodeJunctionList.initCapacity(allocator, lines.len); errdefer nodes.deinit(); for (lines) |line| { const from_str = line[0..3]; const left_str = line[7..10]; const right_str = line[12..15]; nodes.appendAssumeCapacity(NodeJunction{ .from = parse_node_label(from_str), .left = parse_node_label(left_str), .right = parse_node_label(right_str), }); } return nodes; } fn parse_input(allocator: Allocator, lines: []const []const u8) !Input { const instructions = try parse_instructions(allocator, lines[0]); errdefer instructions.deinit(); const nodes = try parse_nodes(allocator, lines[2..]); errdefer nodes.deinit(); return Input{ .instructions = instructions, .nodes = nodes }; } fn node_map_find_branch(node_map: *NodeMap, branch_name: NodePart) ?*NodeMapBranch { for (node_map.items) |*branch| { if (branch.name == branch_name) { return branch; } } return null; } fn node_leaf_find_split(node_leaf: *NodeMapLeaf, name: NodePart) ?*NodeSplit { for (node_leaf.nodes.items) |*split| { if (split.name == name) { return split; } } return null; } fn node_branch_find_leaf(node_branch: *NodeMapBranch, leaf_name: NodePart) ?*NodeMapLeaf { for (node_branch.nodes.items) |*leaf| { if (leaf.name == leaf_name) { return leaf; } } return null; } fn create_node_map(allocator: Allocator, nodes: NodeJunctionList) !NodeMap { var node_map = NodeMap.init(allocator); errdefer free_node_map(node_map); for (nodes.items) |node_junction| { const from = node_junction.from; var node_branch_opt = node_map_find_branch(&node_map, from[0]); if (node_branch_opt == null) { try node_map.append(NodeMapBranch{ .name = from[0], .nodes = std.ArrayList(NodeMapLeaf).init(allocator) }); node_branch_opt = &node_map.items[node_map.items.len-1]; } var node_branch = node_branch_opt.?; var node_leaf_opt = node_branch_find_leaf(node_branch, from[1]); if (node_leaf_opt == null) { try node_branch.nodes.append(NodeMapLeaf{ .name = from[1], .nodes = std.ArrayList(NodeSplit).init(allocator) }); node_leaf_opt = &node_branch.nodes.items[node_branch.nodes.items.len-1]; } var node_leaf = node_leaf_opt.?; try node_leaf.nodes.append(NodeSplit{ .name = from[2], .left = node_junction.left, .right = node_junction.right, }); } return node_map; } fn free_node_map(node_map: NodeMap) void { for (node_map.items) |branch| { for (branch.nodes.items) |leaf| { leaf.nodes.deinit(); } branch.nodes.deinit(); } node_map.deinit(); } fn node_map_lookup(node_map: *NodeMap, from: NodeLabel, direction: Direction) ?NodeLabel { const branch_opt = node_map_find_branch(node_map, from[0]); if (branch_opt == null) return null; const branch = branch_opt.?; const leaf_opt = node_branch_find_leaf(branch, from[1]); if (leaf_opt == null) return null; const leaf = leaf_opt.?; const split_opt = node_leaf_find_split(leaf, from[2]); if (split_opt == null) return null; const split = split_opt.?; return switch (direction) { .Left => split.left, .Right => split.right, }; } pub fn part1(input: *aoc.Input) !aoc.Result { const allocator = input.allocator; const parsed = try parse_input(allocator, input.lines); defer parsed.deinit(); var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); var node_map = try create_node_map(arena.allocator(), parsed.nodes); defer free_node_map(node_map); var steps: u32 = 0; var goal = comptime parse_node_label("ZZZ"); var current = comptime parse_node_label("AAA"); while (!std.mem.eql(NodePart, ¤t, &goal)) { const instruction = parsed.instructions.items[steps % parsed.instructions.items.len]; current = node_map_lookup(&node_map, current, instruction) orelse @panic("dead end"); steps += 1; } return .{ .uint = steps }; } fn find_steps_needed(node_map: *NodeMap, instructions: []const Direction, start: NodeLabel) u32 { var steps: u32 = 0; var current = start; while (current[2] != comptime parse_char_to_node_part('Z')) { const instruction = instructions[steps % instructions.len]; current = node_map_lookup(node_map, current, instruction) orelse @panic("dead end"); steps += 1; } return steps; } fn lcm(a: u64, b: u64) u64 { return a * b / std.math.gcd(a, b); } pub fn part2(input: *aoc.Input) !aoc.Result { const allocator = input.allocator; const parsed = try parse_input(allocator, input.lines); defer parsed.deinit(); var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); var node_map = try create_node_map(arena.allocator(), parsed.nodes); defer free_node_map(node_map); var all_steps = std.ArrayList(u32).init(allocator); defer all_steps.deinit(); for (parsed.nodes.items) |node_junction| { if (node_junction.from[2] == comptime parse_char_to_node_part('A')) { const steps = find_steps_needed(&node_map, parsed.instructions.items, node_junction.from); try all_steps.append(steps); } } assert(all_steps.items.len > 0); var answer: u64 = all_steps.items[0]; for (1..all_steps.items.len) |i| { answer = lcm(answer, @intCast(all_steps.items[i])); } return .{ .uint = answer }; } test "part 1 example 1" { const example_input = [_][]const u8{ "RL", "", "AAA = (BBB, CCC)", "BBB = (DDD, EEE)", "CCC = (ZZZ, GGG)", "DDD = (DDD, DDD)", "EEE = (EEE, EEE)", "GGG = (GGG, GGG)", "ZZZ = (ZZZ, ZZZ)", }; try aoc.expectAnswerUInt(part1, 2, &example_input); } test "part 1 example 2" { const example_input = [_][]const u8{ "LLR", "", "AAA = (BBB, BBB)", "BBB = (AAA, ZZZ)", "ZZZ = (ZZZ, ZZZ)", }; try aoc.expectAnswerUInt(part1, 6, &example_input); } test "part 2 example" { const example_input = [_][]const u8{ "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)", }; try aoc.expectAnswerUInt(part2, 6, &example_input); }