Definitely had to think about this one more. Not sure if what I ended up with was particularly efficient.
Part 1
const std = @import("std");
const input = @embedFile("input.txt");
pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
// parse rules
var rules = std.AutoHashMap(u8, std.ArrayListUnmanaged(u8)).init(arena.allocator());
var update_section_start: usize = 0;
var rule_iter = std.mem.splitScalar(u8, input, '\n');
while (rule_iter.next()) |rule| {
if (rule.len == 0) {
// a blank line separates the rules and updates in the input.
update_section_start = rule_iter.index.?;
break;
}
const sep = std.mem.indexOfScalar(u8, rule, '|').?;
const before_num = try std.fmt.parseInt(u8, rule[0..sep], 10);
const after_num = try std.fmt.parseInt(u8, rule[sep + 1 ..], 10);
const entry = try rules.getOrPutValue(after_num, .empty);
try entry.value_ptr.append(arena.allocator(), before_num);
}
var result: u32 = 0;
var update_iter = std.mem.tokenizeScalar(u8, input[update_section_start..], '\n');
var update_nums = std.ArrayList(u8).init(arena.allocator());
var banned_nums = std.AutoHashMap(u8, void).init(arena.allocator());
next_update: while (update_iter.next()) |update| {
update_nums.clearRetainingCapacity();
banned_nums.clearRetainingCapacity();
var num_iter = std.mem.tokenizeScalar(u8, update, ',');
while (num_iter.next()) |num_string| {
const num = try std.fmt.parseInt(u8, num_string, 10);
if (banned_nums.contains(num)) continue :next_update;
const rule = rules.get(num) orelse continue;
for (rule.items) |rule_num| {
try banned_nums.put(rule_num, {});
}
try update_nums.append(num);
}
result += update_nums.items[update_nums.items.len / 2];
}
std.debug.print("{d}\n", .{result});
}
Part 2
const std = @import("std");
const input = @embedFile("input.txt");
pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
// parse rules
var rules = std.AutoHashMap(u8, std.ArrayListUnmanaged(u8)).init(arena.allocator());
var update_section_start: usize = 0;
var rule_line_iter = std.mem.splitScalar(u8, input, '\n');
while (rule_line_iter.next()) |rule| {
if (rule.len == 0) {
// a blank line separates the rules and updates in the input.
update_section_start = rule_line_iter.index.?;
break;
}
const sep = std.mem.indexOfScalar(u8, rule, '|').?;
const before_num = try std.fmt.parseInt(u8, rule[0..sep], 10);
const after_num = try std.fmt.parseInt(u8, rule[sep + 1 ..], 10);
const entry = try rules.getOrPutValue(after_num, .empty);
try entry.value_ptr.append(arena.allocator(), before_num);
}
var result: u32 = 0;
var update_iter = std.mem.tokenizeScalar(u8, input[update_section_start..], '\n');
var update_nums = std.ArrayList(u8).init(arena.allocator());
var banned_nums = std.AutoHashMap(u8, void).init(arena.allocator());
next_update: while (update_iter.next()) |update| {
update_nums.clearRetainingCapacity();
banned_nums.clearRetainingCapacity();
var num_iter = std.mem.tokenizeScalar(u8, update, ',');
while (num_iter.next()) |num_string| {
const num = try std.fmt.parseInt(u8, num_string, 10);
try update_nums.append(num);
}
for (update_nums.items) |num| {
if (banned_nums.contains(num)) {
std.mem.sort(u8, update_nums.items, rules, lessThan);
result += update_nums.items[update_nums.items.len / 2];
continue :next_update;
}
const rule = rules.get(num) orelse continue;
for (rule.items) |rule_num| {
try banned_nums.put(rule_num, {});
}
}
}
std.debug.print("{d}\n", .{result});
}
fn lessThan(rules: std.AutoHashMap(u8, std.ArrayListUnmanaged(u8)), left: u8, right: u8) bool {
const rule_nums = rules.get(right) orelse return false;
for (rule_nums.items) |num| {
if (num == left) return true;
}
return false;
}