304 lines
9.7 KiB
Zig
304 lines
9.7 KiB
Zig
const std = @import("std");
|
|
const print = std.debug.print;
|
|
const util = @import("util.zig");
|
|
|
|
pub fn main() !void {
|
|
const output = try part_two(false);
|
|
print("{}\n", .{output});
|
|
}
|
|
|
|
pub fn part_one(is_test_case: bool) !u32 {
|
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
defer _ = gpa.deinit();
|
|
const allocator = gpa.allocator();
|
|
|
|
const input_file = try util.getInputFile("05", is_test_case);
|
|
const data = try util.readAllInputWithAllocator(input_file, allocator);
|
|
|
|
// https://stackoverflow.com/a/79199470/1040915
|
|
var it = std.mem.splitScalar(u8, data, '\n');
|
|
var lines_list = std.ArrayList([]const u8).init(allocator);
|
|
defer lines_list.deinit();
|
|
|
|
var end_of_rules = false;
|
|
var rules = std.ArrayList(Rule).init(allocator);
|
|
defer rules.deinit();
|
|
var updates = std.ArrayList(Update).init(allocator);
|
|
defer updates.deinit();
|
|
|
|
while (it.next()) |line| {
|
|
if (line.len == 0) {
|
|
end_of_rules = true;
|
|
continue;
|
|
}
|
|
|
|
if (!end_of_rules) {
|
|
try rules.append(try ruleFromLine(line));
|
|
} else {
|
|
const update = try updateFromLine(line, allocator);
|
|
print("DEBUG - adding new update {any}\n", .{update.values});
|
|
try updates.append(update);
|
|
}
|
|
}
|
|
|
|
print("DEBUG - rules are:\n", .{});
|
|
for (rules.items) |rule| { // Type annotations are weird here - ZLS reports `rule` as `[]const u8`, not `Rule`.
|
|
print("{}|{}\n", .{ rule.first, rule.second });
|
|
}
|
|
print("DEBUG - length of updates is {}\n", .{updates.items.len});
|
|
print("DEBUG - first update is {any}\n", .{updates.items[0]});
|
|
|
|
var total: u32 = 0;
|
|
for (updates.items) |update| {
|
|
// I considered implementing a `fn all(comptime T: type, arr: []T, func: fn(t: T) bool) bool` to make this
|
|
// more concise, but it looks like there's no simple way to make an anonymous function (so cannot pass in
|
|
// `r.passes` as the function parameter)
|
|
|
|
if (updatePassesAllRules(update, rules.items)) {
|
|
total += getMiddleValueOfUpdate(update);
|
|
}
|
|
}
|
|
return total;
|
|
}
|
|
|
|
const Rule = struct {
|
|
first: u32,
|
|
second: u32,
|
|
|
|
fn passes(self: Rule, update: Update) bool {
|
|
var second_encountered = false;
|
|
for (update.values) |value| {
|
|
if (value == self.second) {
|
|
second_encountered = true;
|
|
}
|
|
if (value == self.first) {
|
|
print("DEBUG - does update {any} pass rule {}|{}? {}\n", .{ update, self.first, self.second, !second_encountered });
|
|
return !second_encountered;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
fn updatePassesAllRules(update: Update, rules: []Rule) bool {
|
|
for (rules) |rule| {
|
|
print("DEBUG - checking {any} against {}|{}\n", .{ update.values, rule.first, rule.second });
|
|
if (!rule.passes(update)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
fn getMiddleValueOfUpdate(update: Update) u32 {
|
|
return update.values[update.values.len / 2]; // Assumes that they're all of odd length.
|
|
}
|
|
|
|
fn ruleFromLine(line: []const u8) !Rule {
|
|
var it = std.mem.splitScalar(u8, line, '|');
|
|
const first = try std.fmt.parseInt(u32, it.next().?, 10);
|
|
const second = try std.fmt.parseInt(u32, it.next().?, 10);
|
|
return Rule{ .first = first, .second = second };
|
|
}
|
|
|
|
const Update = struct { values: []u32 };
|
|
|
|
fn updateFromLine(line: []const u8, allocator: std.mem.Allocator) !Update {
|
|
print("DEBUG - processing line as update\n", .{});
|
|
for (line) |c| {
|
|
print("{c}", .{c});
|
|
}
|
|
print("\n", .{});
|
|
|
|
var it = std.mem.splitScalar(u8, line, ',');
|
|
var item_list = std.ArrayList(u32).init(allocator);
|
|
// If this is commented-out, lots of memory-leaks are reported - but with this line in, a segmentation fault occurs
|
|
// on referencing the result of this function.
|
|
// defer item_list.deinit();
|
|
|
|
while (it.next()) |item| {
|
|
print("DEBUG - adding {any} to the item_list\n", .{item});
|
|
try item_list.append(try std.fmt.parseInt(u32, item, 10));
|
|
}
|
|
print("DEBUG - items are {any}\n", .{item_list.items});
|
|
const return_update = Update{ .values = item_list.items };
|
|
print("DEBUG - return values are {any}\n", .{return_update.values});
|
|
return return_update;
|
|
}
|
|
|
|
pub fn part_two(is_test_case: bool) !u32 {
|
|
// I'm sure there's probably a better sorting logic here, but nothing's springing to mind, soooo we're going with...
|
|
// * Identify all the incorrect updates. For each of them:
|
|
// * For every rule, if the rule fails, swap the elements that made it fail
|
|
// * Repeat until the whole set of rules pass
|
|
// (I think this depends on Rules being consistent - i.e. if `a|b`, then there is no `b|a`. Which is a reasonable
|
|
// assumption otherwise the problems are insoluble!)
|
|
|
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
defer _ = gpa.deinit();
|
|
const allocator = gpa.allocator();
|
|
|
|
const input_file = try util.getInputFile("05", is_test_case);
|
|
const data = try util.readAllInputWithAllocator(input_file, allocator);
|
|
|
|
// https://stackoverflow.com/a/79199470/1040915
|
|
var it = std.mem.splitScalar(u8, data, '\n');
|
|
var lines_list = std.ArrayList([]const u8).init(allocator);
|
|
defer lines_list.deinit();
|
|
|
|
var end_of_rules = false;
|
|
var rules = std.ArrayList(Rule).init(allocator);
|
|
defer rules.deinit();
|
|
var updates = std.ArrayList(Update).init(allocator);
|
|
defer updates.deinit();
|
|
|
|
var total: u32 = 0;
|
|
while (it.next()) |line| {
|
|
if (line.len == 0) {
|
|
end_of_rules = true;
|
|
continue;
|
|
}
|
|
|
|
if (!end_of_rules) {
|
|
try rules.append(try ruleFromLine(line));
|
|
} else {
|
|
var update = try updateFromLine(line, allocator);
|
|
|
|
if (!updatePassesAllRules(update, rules.items)) {
|
|
reorderUpdate(rules.items, &update);
|
|
total += getMiddleValueOfUpdate(update);
|
|
}
|
|
}
|
|
}
|
|
return total;
|
|
}
|
|
|
|
fn reorderUpdate(rules: []Rule, update: *Update) void {
|
|
var number_of_reorderings: usize = 0;
|
|
while (reorderUpdateOnce(rules, update)) : (number_of_reorderings += 1) {
|
|
print("DEBUG - reordered {} times\n", .{number_of_reorderings});
|
|
}
|
|
}
|
|
|
|
// Ugh, I _hate_ modifying the argument to a function (rather than returning a new value) like some kind of C-using
|
|
// barbarian. But hey, when in Rome...
|
|
//
|
|
// Returns `true` if any changes were made
|
|
fn reorderUpdateOnce(rules: []Rule, update: *Update) bool {
|
|
var changes_made = false;
|
|
for (rules) |rule| {
|
|
var have_encountered_second_value = false; // Have to use this because checking for `!= undefined` is itself UB.
|
|
var index_of_second_value: usize = undefined;
|
|
var idx: usize = 0;
|
|
while (idx < update.values.len) : (idx += 1) {
|
|
if (update.values[idx] == rule.second) {
|
|
index_of_second_value = idx;
|
|
have_encountered_second_value = true;
|
|
}
|
|
if (update.values[idx] == rule.first and have_encountered_second_value == true) {
|
|
// Rule has been breached - swap the values, then continue with the next Rule
|
|
update.values[idx] = rule.second;
|
|
update.values[index_of_second_value] = rule.first;
|
|
changes_made = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return changes_made;
|
|
}
|
|
|
|
const expect = std.testing.expect;
|
|
|
|
test "part_one" {
|
|
const part_one_value = try part_one(true);
|
|
print("DEBUG - part_one_value is {}\n", .{part_one_value});
|
|
try expect(part_one_value == 143);
|
|
}
|
|
|
|
test "reordering" {
|
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
defer _ = gpa.deinit();
|
|
const allocator = gpa.allocator();
|
|
|
|
var values1 = try allocator.alloc(u32, 5);
|
|
defer allocator.free(values1);
|
|
values1[0] = 75;
|
|
values1[1] = 97;
|
|
values1[2] = 47;
|
|
values1[3] = 61;
|
|
values1[4] = 53;
|
|
var update1 = Update{ .values = values1 };
|
|
|
|
var values2 = try allocator.alloc(u32, 3);
|
|
defer allocator.free(values2);
|
|
values2[0] = 61;
|
|
values2[1] = 13;
|
|
values2[2] = 29;
|
|
var update2 = Update{ .values = values2 };
|
|
|
|
var values3 = try allocator.alloc(u32, 5);
|
|
defer allocator.free(values3);
|
|
values3[0] = 97;
|
|
values3[1] = 13;
|
|
values3[2] = 75;
|
|
values3[3] = 29;
|
|
values3[4] = 47;
|
|
var update3 = Update{ .values = values3 };
|
|
|
|
const ruleText =
|
|
\\47|53
|
|
\\97|13
|
|
\\97|61
|
|
\\97|47
|
|
\\75|29
|
|
\\61|13
|
|
\\75|53
|
|
\\29|13
|
|
\\97|29
|
|
\\53|29
|
|
\\61|53
|
|
\\97|53
|
|
\\61|29
|
|
\\47|13
|
|
\\75|47
|
|
\\97|75
|
|
\\47|61
|
|
\\75|61
|
|
\\47|29
|
|
\\75|13
|
|
\\53|13
|
|
;
|
|
|
|
var it = std.mem.splitScalar(u8, ruleText, '\n');
|
|
var rules = std.ArrayList(Rule).init(allocator);
|
|
defer rules.deinit();
|
|
while (it.next()) |line| {
|
|
try rules.append(try ruleFromLine(line));
|
|
}
|
|
|
|
reorderUpdate(rules.items, &update1);
|
|
try expect(update1.values[0] == 97);
|
|
try expect(update1.values[1] == 75);
|
|
try expect(update1.values[2] == 47);
|
|
try expect(update1.values[3] == 61);
|
|
try expect(update1.values[4] == 53);
|
|
|
|
reorderUpdate(rules.items, &update2);
|
|
try expect(update2.values[0] == 61);
|
|
try expect(update2.values[1] == 29);
|
|
try expect(update2.values[2] == 13);
|
|
|
|
reorderUpdate(rules.items, &update3);
|
|
try expect(update3.values[0] == 97);
|
|
try expect(update3.values[1] == 75);
|
|
try expect(update3.values[2] == 47);
|
|
try expect(update3.values[3] == 29);
|
|
try expect(update3.values[4] == 13);
|
|
}
|
|
|
|
test "part_two" {
|
|
const part_two_value = try part_two(true);
|
|
print("DEBUG - part_one_value is {}\n", .{part_two_value});
|
|
try expect(part_two_value == 123);
|
|
}
|