solve day 8

This commit is contained in:
Rokas Puzonas 2023-12-18 23:30:28 +02:00
parent 8eb9656ed3
commit 769fb00274
3 changed files with 312 additions and 7 deletions

View File

@ -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);
}

306
src/day8.zig Normal file
View File

@ -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, &current, &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);
}

View File

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