diff --git a/src/aoc.zig b/src/aoc.zig index 00e1f7f..a0e295e 100644 --- a/src/aoc.zig +++ b/src/aoc.zig @@ -8,7 +8,7 @@ pub const Input = struct { // For now only .uint is needed pub const Result = union(enum) { - uint: u32, + uint: u64, }; pub const Solver = *const fn(input: *Input) anyerror!Result; @@ -17,9 +17,9 @@ pub const Day = struct { part2: ?Solver }; -pub fn expectAnswerUInt(solver: Solver, answer: u32, lines: []const []const u8) !void { +pub fn expectAnswerUInt(solver: Solver, answer: u64, lines: []const []const u8) !void { var input = Input{ .lines = lines, .allocator = std.testing.allocator }; var actual = try solver(&input); - var expected: u32 = answer; + var expected: u64 = answer; try std.testing.expectEqual(expected, actual.uint); } diff --git a/src/day8.zig b/src/day8.zig new file mode 100644 index 0000000..5fba317 --- /dev/null +++ b/src/day8.zig @@ -0,0 +1,306 @@ +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')) { + var 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); +} diff --git a/src/main.zig b/src/main.zig index 2ab5c6f..7b288a1 100644 --- a/src/main.zig +++ b/src/main.zig @@ -28,6 +28,7 @@ const Days = [_]aoc.Day{ create_day(@import("day5.zig")), create_day(@import("day6.zig")), create_day(@import("day7.zig")), + create_day(@import("day8.zig")), }; fn kilobytes(count: u32) u32 { @@ -86,10 +87,7 @@ fn run(allocator: Allocator, args: *cli) !u8 { const end_time = std.time.microTimestamp(); switch (result) { - .uint => std.debug.print("{}\n", .{result.uint}), - .float => std.debug.print("{}\n", .{result.float}), - .int => std.debug.print("{}\n", .{result.int}), - .text => std.debug.print("{s}\n", .{result.text}), + .uint => std.debug.print("{}\n", .{result.uint}), } const duration = end_time - start_time; @@ -181,4 +179,5 @@ test { _ = @import("day5.zig"); _ = @import("day6.zig"); _ = @import("day7.zig"); + _ = @import("day8.zig"); }