165 lines
6.3 KiB
Zig
165 lines
6.3 KiB
Zig
// It seems like the goal of the program is just to multiply some numbers. It does that with instructions like mul(X,Y), where X and Y are each 1-3 digit numbers. For instance, mul(44,46) multiplies 44 by 46 to get a result of 2024. Similarly, mul(123,4) would multiply 123 by 4.
|
|
|
|
// However, because the program's memory has been corrupted, there are also many invalid characters that should be ignored, even if they look like part of a mul instruction. Sequences like mul(4*, mul(6,9!, ?(12,34), or mul ( 2 , 4 ) do nothing.
|
|
|
|
// For example, consider the following section of corrupted memory:
|
|
|
|
// xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5))
|
|
|
|
// Only the four highlighted sections are real mul instructions. Adding up the result of each instruction produces 161 (2*4 + 5*5 + 11*8 + 8*5).
|
|
|
|
// Scan the corrupted memory for uncorrupted mul instructions. What do you get if you add up all of the results of the multiplications?
|
|
|
|
const std = @import("std");
|
|
const print = std.debug.print;
|
|
const util = @import("util.zig");
|
|
|
|
pub fn main() !void {
|
|
const output = try part_two();
|
|
print("{}\n", .{output});
|
|
}
|
|
|
|
pub fn part_one() !u32 {
|
|
// Sketch logic:
|
|
// * Iterate over lines of the file
|
|
// * For each character, drop into a sub-function, which
|
|
// * Progresses and checks if this is a valid multiplication command
|
|
// * If it is, multiply it and return the value (to be summed to accumulator)
|
|
// * Else, return 0
|
|
// (A micro-optimization would be to also return the number of characters consumed so that the pointer could advance
|
|
// by that much and not recheck each of the characters within the `mul` statement for being the start of a statement)
|
|
|
|
const isTestCase: bool = true;
|
|
const data = try util.readAllInput(try util.getInputFile("03", isTestCase));
|
|
var total: u32 = 0;
|
|
var idx: u32 = 0;
|
|
while (idx < data.len) {
|
|
total += check_if_is_valid_multiplication(data, idx);
|
|
idx += 1;
|
|
}
|
|
return total;
|
|
|
|
}
|
|
|
|
pub fn part_two() !u32 {
|
|
const isTestCase: bool = true;
|
|
var should_mul = true;
|
|
const data = try util.readAllInput(try util.getInputFile("03", isTestCase));
|
|
var total: u32 = 0;
|
|
var idx: u32 = 0;
|
|
while (idx < data.len) {
|
|
// As elsewhere - there are probably plenty of optimizations to be done here, such as only checking for `d`
|
|
// first to avoid checking the whole 4 bytes. Lol :P
|
|
if (idx+4 < data.len and std.mem.eql(u8, data[idx..idx+4], "do()")) {
|
|
should_mul = true;
|
|
idx += 1;
|
|
continue;
|
|
}
|
|
if (idx+7 < data.len and std.mem.eql(u8, data[idx..idx+7], "don't()")) {
|
|
should_mul = false;
|
|
idx += 1;
|
|
continue;
|
|
}
|
|
if (should_mul) {
|
|
total += check_if_is_valid_multiplication(data, idx);
|
|
}
|
|
idx += 1;
|
|
}
|
|
return total;
|
|
}
|
|
|
|
fn check_if_is_valid_multiplication(line: []const u8, start_index: u32) u32 {
|
|
// Check starts with `mul(`
|
|
// Check for matching close paren
|
|
// Check that the contents of the parens are three numbers
|
|
//
|
|
// If any of these false, return 0
|
|
// Else, do multiplication and return appropriate value
|
|
if (line[start_index] != 'm') {
|
|
// print("DEBUG - does not start with m\n", .{});
|
|
return 0;
|
|
}
|
|
if ((start_index + 6) > line.len) {
|
|
// print("DEBUG - too close to the end\n", .{});
|
|
return 0;
|
|
}
|
|
|
|
if (!std.mem.eql(u8, line[start_index..start_index+4], "mul(")) {
|
|
return 0;
|
|
}
|
|
if (line[start_index+4] == ')') {
|
|
// I.e. if `mul()`
|
|
// print("DEBUG - content is mul()", .{});
|
|
return 0;
|
|
}
|
|
// Progress from index after the open-paren, to find the close-paren.
|
|
var i: u32 = 1;
|
|
var length_of_parameters: u32 = 0;
|
|
var index_of_comma: u32 = 0;
|
|
while (i < 12) {
|
|
if (line[start_index+i] == ')') {
|
|
length_of_parameters = i;
|
|
break;
|
|
}
|
|
if (line[start_index+i] == ',') {
|
|
index_of_comma = i;
|
|
}
|
|
i += 1;
|
|
}
|
|
if (length_of_parameters == 0) {
|
|
// Did not find a close-paren
|
|
// print("DEBUG - did not find a close-paren\n", .{});
|
|
return 0;
|
|
}
|
|
if (index_of_comma == 0) {
|
|
// Did not find comma
|
|
// print("DEBUG - did not find a comma\n", .{});
|
|
return 0;
|
|
}
|
|
// This is _almost_ guaranteed to be a legal multiplication command - we know it has the form
|
|
// `mul(...,...)`. Try parsing the `...`s as ints - and if they don't parse, return 0
|
|
const num_1 = std.fmt.parseInt(u32, line[start_index+4..start_index+index_of_comma], 10) catch {
|
|
print("DEBUG - failed to parse first value into an integer: ", .{});
|
|
for (line[start_index..start_index+index_of_comma]) |char| {
|
|
print("{c}", .{char});
|
|
}
|
|
print("\n", .{});
|
|
return 0;
|
|
};
|
|
const num_2 = std.fmt.parseInt(u32, line[start_index+index_of_comma+1..start_index+length_of_parameters], 10) catch {
|
|
print("DEBUG - failed to parse second value into an integer: ", .{});
|
|
for (line[start_index+index_of_comma+1..start_index+length_of_parameters]) |char| {
|
|
print("{c}", .{char});
|
|
}
|
|
print("\n", .{});
|
|
return 0;
|
|
};
|
|
print("DEBUG - found a real pair, {} and {}\n", .{num_1, num_2});
|
|
return num_1 * num_2;
|
|
}
|
|
|
|
const expect = @import("std").testing.expect;
|
|
|
|
test "Testing multiplication" {
|
|
const line = "mul() mul(2,3) is mu mul(2*,2), mul(2343,213) mul(231,123";
|
|
try expect(check_if_is_valid_multiplication(try fromStringToU8Array(line), 0) == 0);
|
|
try expect(check_if_is_valid_multiplication(try fromStringToU8Array(line), 6) == 6);
|
|
}
|
|
|
|
// This is absolutely fucking ridiculous - but, simply doing `<variable>.*` doesn't work.
|
|
fn fromStringToU8Array(string: *const[57:0]u8) ![]u8 {
|
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
defer _ = gpa.deinit();
|
|
const allocator = gpa.allocator();
|
|
var output = std.ArrayList(u8).init(allocator);
|
|
for (string) |char| {
|
|
try output.append(char);
|
|
}
|
|
// This "leaks memory", but I don't know how _not_ to - the alternative would be to create an array into which to
|
|
// copy `output.items`, but you _can't_ do that because `output.items.len` isn't known at comptime.
|
|
return output.items;
|
|
}
|
|
|
|
test "Part One" {
|
|
try expect(try part_one() == 161);
|
|
} |