Solution to 17-1

This commit is contained in:
Jack Jackson 2025-01-19 17:19:12 -08:00
parent 4202f34395
commit 263c51c364
5 changed files with 281 additions and 2 deletions

View File

@ -3,6 +3,7 @@ Notes, thoughts, or questions that arose as I implemented the solutions. Hopeful
# Useful references
* [Zig Notes](https://github.com/david-vanderson/zig-notes) - particularly on Arrays vs. Slices, and Strings.
* I only discovered [this](https://kristoff.it/blog/advent-of-code-zig/) on Day 17 (which, because of my delayed-attempts, was actually Jan 19th).
# Things I like
@ -278,3 +279,16 @@ If I try making `from_line` return a pointer ([fiddle](https://zigfiddle.dev/?o9
* In ZigFiddle, I get a printed `12`, which is equal to neither `2+1` nor `10+1`. The `expect`s do _not_ fail.
* It _is_ equal to `10 + 1 + 1`, so _maybe_ the second `Value` was the one that got incremented both times? I can't see how that could be possible, though. And, when I tried adding a [third value](https://zigfiddle.dev/?0QGh6GrDLpQ), the result was `8`, so that theory doesn't hold water.
* On my own machine, I get the value `32761` printed, which is so far from either of those values that it makes me suspect I've somehow printed a bare pointer by mistake, but I can't see where I've done so if that's the case.
## Can't you change a slice's length?
The following code gives an error:
```zig
pub fn main() void {
var mutable_slice = &[_]u32{ 0, 1, 2 };
mutable_slice = &[_]u32{ 4, 5, 6, 7 };
}
```
Why?

5
inputs/17/real.txt Normal file
View File

@ -0,0 +1,5 @@
Register A: 18427963
Register B: 0
Register C: 0
Program: 2,4,1,1,7,5,0,3,4,3,1,6,5,5,3,0

5
inputs/17/test.txt Normal file
View File

@ -0,0 +1,5 @@
Register A: 729
Register B: 0
Register C: 0
Program: 0,1,5,4,3,0

255
solutions/17.zig Normal file
View File

@ -0,0 +1,255 @@
const std = @import("std");
const print = std.debug.print;
const util = @import("util.zig");
const expect = std.testing.expect;
// Some memory leaks :shrug:
pub fn main() !void {
const response = try part_one(false);
print("{any}\n", .{response});
}
pub fn part_one(is_test_case: bool) ![]u64 {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const input_file = try util.getInputFile("17", is_test_case);
const data = try util.readAllInputWithAllocator(input_file, allocator);
defer allocator.free(data);
return Computer.run(data, allocator);
}
const ExecutionError = error{InfiniteLoop};
const Computer = struct {
regA: u64,
regB: u64,
regC: u64,
program: []const u4 = &.{},
instruction_pointer: u32,
outputs: std.ArrayList(u64),
pub fn init(a: u64, b: u64, c: u64, allocator: std.mem.Allocator) Computer {
return Computer{ .regA = a, .regB = b, .regC = c, .instruction_pointer = 0, .outputs = std.ArrayList(u64).init(allocator) };
}
pub fn parse_program(input_line: []const u8, allocator: std.mem.Allocator) ![]u4 {
const values = input_line[9..];
var split = std.mem.splitScalar(u8, values, ',');
var response = std.ArrayList(u4).init(allocator);
while (split.next()) |v| {
try response.append(try std.fmt.parseInt(u4, v, 10));
}
return try response.toOwnedSlice();
}
pub fn deinit(self: *Computer, allocator: std.mem.Allocator) void {
self.outputs.deinit();
allocator.free(self.program);
}
pub fn run(data: []const u8, allocator: std.mem.Allocator) ![]u64 {
var lines = std.mem.splitScalar(u8, data, '\n');
const first_line = lines.next().?;
const regA = try std.fmt.parseInt(u32, first_line[12..], 10);
const second_line = lines.next().?;
const regB = try std.fmt.parseInt(u32, second_line[12..], 10);
const third_line = lines.next().?;
const regC = try std.fmt.parseInt(u32, third_line[12..], 10);
_ = lines.next();
const program_line = lines.next().?;
var c = Computer.init(regA, regB, regC, allocator);
defer c.deinit(allocator);
const program = try Computer.parse_program(program_line, allocator);
return c.execute_program(program);
}
pub fn execute_program(self: *Computer, program: []const u4) ![]u64 {
self.program = program;
var op_count: u32 = 0;
while (self.instruction_pointer < program.len - 1) : (op_count += 1) {
// print("DEBUG - a/b/c are {}/{}/{}, pointer {}, values {}-{}\n", .{ self.regA, self.regB, self.regC, self.instruction_pointer, self.program[self.instruction_pointer], self.program[self.instruction_pointer + 1] });
const operand = program[self.instruction_pointer + 1];
switch (program[self.instruction_pointer]) {
0 => self.op_adv(operand),
1 => self.op_bxl(operand),
2 => self.op_bst(operand),
3 => self.op_jnz(operand),
4 => self.op_bxc(operand),
5 => self.op_out(operand),
6 => self.op_bdv(operand),
7 => self.op_cdv(operand),
else => unreachable,
}
if (op_count > 1000) {
print("Infinite loop detected for program {any}\n", .{program});
return ExecutionError.InfiniteLoop;
}
}
return try self.outputs.toOwnedSlice();
}
// These need not necessarily be `pub`, but that's helpful for testing.
pub fn op_adv(self: *Computer, operand: u4) void {
// print("DEBUG - op_adv with operand {}, ", .{operand});
self.regA = @divFloor(self.regA, std.math.pow(u64, 2, self.get_combo_value(operand)));
// print("new value is {}\n", .{self.regA});
self.instruction_pointer += 2;
}
// I wonder if there's a reflection-based way to implement these three functions as parameterizations of a single
// function?
pub fn op_bdv(self: *Computer, operand: u4) void {
// print("DEBUG - op_bdv with operand {}, ", .{operand});
self.regB = @divFloor(self.regA, std.math.pow(u64, 2, self.get_combo_value(operand)));
// print("new value is {}\n", .{self.regB});
self.instruction_pointer += 2;
}
pub fn op_cdv(self: *Computer, operand: u4) void {
// print("DEBUG - op_cdv with operand {}, ", .{operand});
self.regC = @divFloor(self.regA, std.math.pow(u64, 2, self.get_combo_value(operand)));
// print("new value is {}\n", .{self.regC});
self.instruction_pointer += 2;
}
pub fn op_bxl(self: *Computer, operand: u4) void {
// print("DEBUG - op_bxl with operand {}, ", .{operand});
self.regB = self.regB ^ operand;
// print("new value is {}\n", .{self.regB});
self.instruction_pointer += 2;
}
pub fn op_bst(self: *Computer, operand: u4) void {
// print("DEBUG - op_bst with operand {}, ", .{operand});
self.regB = self.get_combo_value(operand) % 8;
// print("new value is {}\n", .{self.regB});
self.instruction_pointer += 2;
}
pub fn op_jnz(self: *Computer, operand: u4) void {
// print("DEBUG - jnz-ing ", .{});
if (self.regA == 0) {
// print("but not moving\n", .{});
self.instruction_pointer += 2;
} else {
// print("jumping to {}\n", .{operand});
self.instruction_pointer = operand;
}
}
pub fn op_bxc(self: *Computer, _: u4) void {
// print("DEBUG - bxc (no operand), ", .{});
self.regB = self.regB ^ self.regC;
// print("new value {}\n", .{self.regB});
self.instruction_pointer += 2;
}
pub fn op_out(self: *Computer, operand: u4) void {
const new_output = self.get_combo_value(operand) % 8;
// print("DEBUG - op_out with {}\n", .{new_output});
self.outputs.append(new_output) catch unreachable;
self.instruction_pointer += 2;
}
fn get_combo_value(self: *Computer, combo_operand: u4) u64 {
return switch (combo_operand) {
0...3 => combo_operand, // Do we need to cast this?
4 => self.regA,
5 => self.regB,
6 => self.regC,
else => {
print("Unexpected combo_operand {}\n", .{combo_operand});
unreachable;
},
};
}
};
test "basic operations" {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var c = Computer.init(128, 40, 98, allocator);
c.op_adv(3);
try expect(c.regA == 16);
c.op_bdv(2);
try expect(c.regB == 4);
c.op_cdv(1);
try expect(c.regC == 8);
c = Computer.init(128, 40, 98, allocator);
c.op_bxl(10);
try expect(c.regB == 34);
c.op_bst(4);
try expect(c.regB == 0); // 128 % 8 = 0
c.op_bst(6);
try expect(c.regB == 2); // 98 % 8 = 2
c.op_bst(1);
try expect(c.regB == 1);
c.op_bst(2);
try expect(c.regB == 2);
c.op_bst(3);
try expect(c.regB == 3);
c = Computer.init(128, 40, 98, allocator);
try expect(c.instruction_pointer == 0);
c.op_jnz(2);
try expect(c.instruction_pointer == 2);
c.regA = 0;
c.op_jnz(5);
try expect(c.instruction_pointer == 4); // 2 on from the original 2.
c.regA = 1;
c.op_jnz(5);
try expect(c.instruction_pointer == 5);
}
test "basic_programs" {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var c = Computer.init(10, 0, 9, allocator);
const program1: []const u4 = &.{ 2, 6 };
const response1 = try c.execute_program(program1);
allocator.free(response1);
try expect(c.regB == 1);
c = Computer.init(10, 0, 9, allocator);
const program2 = [_]u4{ 5, 0, 5, 1, 5, 4 }; // Apparently, even if a slice is `var`, you can't change its length.
const response2 = try c.execute_program(&program2);
try expect(std.mem.eql(u64, response2, &.{ 0, 1, 2 }));
allocator.free(response2);
c = Computer.init(2024, 0, 9, allocator);
const program3 = [_]u4{ 0, 1, 5, 4, 3, 0 };
const response3 = try c.execute_program(&program3);
try expect(std.mem.eql(u64, response3, &.{ 4, 2, 5, 6, 7, 7, 7, 7, 3, 1, 0 }));
try expect(c.regA == 0);
allocator.free(response3);
c = Computer.init(2024, 29, 9, allocator);
const program4 = [_]u4{ 1, 7 };
const response4 = try c.execute_program(&program4);
try expect(c.regB == 26);
allocator.free(response4);
c = Computer.init(2024, 2024, 43690, allocator);
const program5 = [_]u4{ 4, 0 };
const response5 = try c.execute_program(&program5);
try expect(c.regB == 44354);
allocator.free(response5);
}
test "part_one" {
const response = try part_one(true);
print("DEBUG - part_one response is {any}\n", .{response});
try expect(std.mem.eql(u64, response, &.{ 4, 6, 3, 5, 6, 3, 5, 2, 1, 0 }));
}

View File

@ -10,7 +10,7 @@ pub fn getInputFile(problemNumber: []const u8, isTestCase: bool) ![]u8 {
// Technically this could be implemented as just repeated calls to `concatString`, but I _guess_ this is more efficient?
// (I. Hate. Manual memory allocation)
fn concatStrings(strings: []const []const u8) ![]u8 {
pub fn concatStrings(strings: []const []const u8) ![]u8 {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
@ -27,7 +27,7 @@ fn concatStrings(strings: []const []const u8) ![]u8 {
return combined;
}
fn concatString(a: []const u8, b: []const u8) ![]u8 {
pub fn concatString(a: []const u8, b: []const u8) ![]u8 {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
// I don't recall where the below came from, but it causes a `[gpa] (err): memory address 0x10383f000 leaked:` when