const std = @import("std");
const Part = struct {
x: u32,
m: u32,
a: u32,
s: u32,
fn parse(p: []const u8) !Part {
var split = std.mem.splitScalar(u8, p, ',');
var result = Part{
.x = try std.fmt.parseInt(u32, split.next().?[2..], 10),
.m = try std.fmt.parseInt(u32, split.next().?[2..], 10),
.a = try std.fmt.parseInt(u32, split.next().?[2..], 10),
.s = try std.fmt.parseInt(u32, split.next().?[2..], 10),
};
return result;
}
fn len_inclusive(self: Part) u64 {
return @as(u64, self.x - self.x_min + 1) * @as(u64, self.m - self.m_min + 1) * @as(u64, self.a - self.a_min + 1) * @as(u64, self.s - self.s_min + 1);
}
};
const Range = struct {
start: u32,
stop: u32,
fn new(start: u32, stop: u32) Range {
return .{ .start = start, .stop = stop };
}
fn len_inclusive(self: Range) u64 {
if (self.stop < self.start) return 0 else return self.stop - self.start + 1;
}
};
const PartRange = struct {
x: Range,
m: Range,
a: Range,
s: Range,
fn len(self: PartRange) u64 {
return self.x.len_inclusive() * self.m.len_inclusive() * self.a.len_inclusive() * self.s.len_inclusive();
}
};
const QueuePart = struct {
part: PartRange,
workflow: []const u8,
};
const empty = "";
const Rule = struct {
part: u8,
comparator: u8,
amount: u32,
workflow: []const u8,
fn parse(p: []const u8) !Rule {
var number_split = std.mem.splitScalar(u8, p, ':');
var part = number_split.next().?;
var workflow = number_split.next();
var rule = Rule{
.part = part[0],
.comparator = 0,
.amount = 0,
.workflow = empty,
};
if (workflow) |w| {
rule.comparator = part[1];
var amount = try std.fmt.parseInt(u32, part[2..], 10);
rule.amount = amount;
rule.workflow = w;
} else {
rule.part = 'd';
rule.workflow = part;
}
return rule;
}
};
pub fn solve(allocator: std.mem.Allocator, file_name: []const u8) !void {
var inputs = try std.fs.cwd().openFile(file_name, .{});
var contents = try inputs.readToEndAlloc(allocator, 100 * 1024);
var buffer = try allocator.alloc(u8, 100 * 1024);
_ = buffer;
var sections = std.mem.splitSequence(u8, contents[0 .. contents.len - 1], "\n\n");
var debug_it = false;
var workflows = std.StringHashMap(std.ArrayList(Rule)).init(allocator);
var workflow_order = std.ArrayList([]const u8).init(allocator);
var line_buffer: [1024]u8 = undefined;
_ = line_buffer;
var workflow_section = std.mem.splitScalar(u8, sections.next().?, '\n');
while (workflow_section.next()) |workflow| {
var a = std.mem.splitScalar(u8, workflow, '{');
var workflow_name = a.next().?;
var rules_string = a.next().?;
var rules = std.mem.splitScalar(u8, rules_string[0 .. rules_string.len - 1], ',');
var workflow_rules = std.ArrayList(Rule).init(allocator);
while (rules.next()) |rule_part| {
try workflow_rules.append(try Rule.parse(rule_part));
}
try workflows.put(workflow_name, workflow_rules);
try workflow_order.append(workflow_name);
}
var run_section = std.mem.splitScalar(u8, sections.next().?, '\n');
var part1: u64 = 0;
while (run_section.next()) |section| {
var part = try Part.parse(section[1 .. section.len - 1]);
var is_rejected = false;
var is_accepted = false;
var workflow = std.mem.zeroes([4]u8);
_ = workflow;
var current = workflows.get("in").?;
while (!is_rejected and !is_accepted) {
for (current.items) |item| {
var a: u32 = 0;
switch (item.part) {
'd' => {
if (item.workflow[0] == 'A') {
is_accepted = true;
break;
} else if (item.workflow[0] == 'R') {
is_rejected = true;
break;
} else {
current = workflows.get(item.workflow).?;
}
},
'x' => a = part.x,
'm' => a = part.m,
'a' => a = part.a,
's' => a = part.s,
else => {
std.log.info("Bad part: {c}", .{item.part});
std.os.exit(1);
},
}
if (item.comparator == '>') {
if (a > item.amount) {
if (item.workflow[0] == 'A') {
is_accepted = true;
break;
} else if (item.workflow[0] == 'R') {
is_rejected = true;
break;
}
current = workflows.get(item.workflow).?;
break;
}
} else if (item.comparator == '<') {
if (a < item.amount) {
if (item.workflow[0] == 'A') {
is_accepted = true;
break;
} else if (item.workflow[0] == 'R') {
is_rejected = true;
break;
}
current = workflows.get(item.workflow).?;
break;
}
}
}
}
if (is_accepted) {
part1 += part.x + part.m + part.a + part.s;
}
}
std.log.info("Part1: {d}", .{part1});
var queue = std.ArrayList(QueuePart).init(allocator);
try queue.append(.{
.part = .{ .x = Range.new(1, 4000), .m = Range.new(1, 4000), .a = Range.new(1, 4000), .s = Range.new(1, 4000) },
.workflow = "in",
});
var part2: u64 = 0;
while (queue.popOrNull()) |*ppart| {
var part = @constCast(ppart);
if (part.workflow[0] == 'A') {
part2 += part.part.len();
continue;
} else if (part.workflow[0] == 'R') {
continue;
}
var workflow = workflows.get(part.workflow).?;
for (workflow.items[0 .. workflow.items.len - 1]) |rule| {
var part_b = part.*;
part_b.workflow = rule.workflow;
var compare_amount: Range = undefined;
var xmas_a: Range = undefined;
var xmas_b: Range = undefined;
switch (rule.part) {
'x' => compare_amount = part.part.x,
'm' => compare_amount = part.part.m,
'a' => compare_amount = part.part.a,
's' => compare_amount = part.part.s,
else => {
std.log.err("Bad part in rule: {c}", .{rule.part});
std.os.exit(1);
},
}
if (rule.comparator == '>') {
if (compare_amount.stop <= rule.amount) continue;
if (compare_amount.start > rule.amount) {
part.*.workflow = rule.workflow;
try queue.append(part.*);
break;
}
xmas_a = .{ .start = compare_amount.start, .stop = rule.amount };
xmas_b = .{ .start = rule.amount + 1, .stop = compare_amount.stop };
} else if (rule.comparator == '<') {
if (compare_amount.start >= rule.amount) continue;
if (compare_amount.stop < rule.amount) {
part.workflow = rule.workflow;
try queue.append(part.*);
break;
}
xmas_a = .{ .start = rule.amount, .stop = compare_amount.stop };
xmas_b = .{ .start = compare_amount.start, .stop = rule.amount - 1 };
} else {
std.log.info("Invalid comparator: {c}", .{rule.comparator});
std.os.exit(1);
}
switch (rule.part) {
'x' => {
part.part.x = xmas_a;
part_b.part.x = xmas_b;
},
'm' => {
part.part.m = xmas_a;
part_b.part.m = xmas_b;
},
'a' => {
part.part.a = xmas_a;
part_b.part.a = xmas_b;
},
's' => {
part.part.s = xmas_a;
part_b.part.s = xmas_b;
},
else => std.os.exit(1),
}
if (debug_it) {
std.log.info("range({d}, {d})", .{ xmas_a.start, xmas_a.stop });
std.log.info("range({d}, {d})", .{ xmas_b.start, xmas_b.stop });
}
try queue.append(part_b);
}
part.workflow = workflow.items[workflow.items.len - 1].workflow;
try queue.append(part.*);
}
std.log.info("Part 2: {d}", .{part2});
}