feat(day-19): hell yeah

This commit is contained in:
2024-09-21 23:38:11 +01:00
parent 241ea7653b
commit 932729eb97
2 changed files with 368 additions and 173 deletions

View File

@ -1,16 +1,53 @@
const std = @import("std"); const std = @import("std");
const print = std.debug.print; const print = std.debug.print;
// rfg{s<537:gd,x>2440:R,A} const Attributes = enum(u8) {
x = 'x',
const Condition = struct { m = 'm',
category: u8, //xmas a = 'a',
value: usize, s = 's',
operation: u8, // <, >
trueCondition: []const u8,
}; };
const Rule = struct { conditions: std.ArrayList(*Condition), endAccept: bool, endReject: bool, endRule: []const u8 }; const RuleType = enum { non_terminal, terminal_expression, terminal, non_terminal_instant };
const Expression = struct {
attribute: Attributes,
value: usize,
less_than: bool,
};
const NonTerminal = struct {
expression: Expression,
next_rule: []const u8,
};
const TerminalExpression = struct {
expression: Expression,
accept: bool,
};
const NonTerminalInstant = struct {
next_rule: []const u8,
};
const Terminal = struct {
accept: bool,
};
const Rule = union(RuleType) {
non_terminal: NonTerminal,
terminal_expression: TerminalExpression,
terminal: Terminal,
non_terminal_instant: NonTerminalInstant,
};
const Workflow = struct {
name: []const u8,
rules: []Rule,
};
const Part = struct { const Part = struct {
x: usize, x: usize,
@ -19,195 +56,353 @@ const Part = struct {
s: usize, s: usize,
}; };
pub fn solve(input: [][]const u8) !void { const State = enum { terminal, non_terminal };
var allocator = std.heap.page_allocator;
var foundEmptyLine = false;
var ruleMap = std.StringHashMap(*Rule).init(allocator); const StateUnion = union(State) {
var parts = std.ArrayList(*Part).init(allocator); terminal: bool,
non_terminal: []const u8,
};
for (input) |line| { const Range = struct {
if (line[0] == '{') { low: usize,
foundEmptyLine = true; high: usize,
pub fn is_empty(self: Range) bool {
return self.low >= self.high;
}
pub fn size(self: Range) usize {
if (self.is_empty()) {
return 0;
} }
if (!foundEmptyLine) { return self.high - self.low + 1;
// parse rule }
var l = line[0 .. line.len - 1];
var index: usize = 0;
for (l) |c| {
if (c == '{') {
break;
}
index += 1;
}
var ruleName = l[0..index]; pub fn print_range(self: Range) void {
l = l[index + 1 ..]; print("{}<=x<={}\n", .{ self.low, self.high });
}
};
var ruleList = std.ArrayList(*Condition).init(allocator); const XmasRange = struct {
x: Range,
m: Range,
a: Range,
s: Range,
var commasTokens = std.mem.tokenizeSequence(u8, l, ","); pub fn sum(self: XmasRange) usize {
var currentRule: []const u8 = "hello"; return self.x.size() * self.m.size() * self.a.size() * self.s.size();
}
};
while (commasTokens.next()) |rule| { fn char_to_attribute(c: u8) Attributes {
currentRule = rule; return switch (c) {
if (commasTokens.peek()) |_| { 'x' => Attributes.x,
var dotsTokens = std.mem.tokenizeSequence(u8, rule, ":"); 'm' => Attributes.m,
var condition = dotsTokens.next().?; 'a' => Attributes.a,
var name = dotsTokens.next().?; 's' => Attributes.s,
else => unreachable,
};
}
var category = condition[0]; fn build_rule(rule: []const u8) Rule {
var operation = condition[1]; if (rule.len == 1) {
return Rule{ .terminal = Terminal{ .accept = rule[0] == 'A' } };
}
var c = try allocator.create(Condition); const colon_index_optional: ?usize = for (rule, 0..) |c, index| {
if (c != ':') {
c.*.category = category;
c.*.value = try std.fmt.parseInt(usize, condition[2..], 10);
c.*.operation = operation;
c.*.trueCondition = name;
try ruleList.append(c);
}
}
var rule = try allocator.create(Rule);
rule.*.endAccept = false;
rule.*.endReject = false;
if (currentRule[0] == 'A') {
rule.*.endAccept = true;
} else if (currentRule[0] == 'R') {
rule.*.endReject = true;
}
rule.*.conditions = ruleList;
rule.*.endRule = currentRule;
try ruleMap.put(ruleName, rule);
continue; continue;
} }
var l = line[1 .. line.len - 1]; break index;
var commas = std.mem.tokenizeSequence(u8, l, ","); } else null;
var x = commas.next().?; if (colon_index_optional == null) {
var m = commas.next().?; return Rule{ .non_terminal_instant = NonTerminalInstant{ .next_rule = rule } };
var a = commas.next().?;
var s = commas.next().?;
var part = try allocator.create(Part);
part.*.x = try std.fmt.parseInt(usize, x[2..], 10);
part.*.m = try std.fmt.parseInt(usize, m[2..], 10);
part.*.a = try std.fmt.parseInt(usize, a[2..], 10);
part.*.s = try std.fmt.parseInt(usize, s[2..], 10);
try parts.append(part);
} }
var acceptedParts = std.ArrayList(Part).init(allocator); const colon_index = colon_index_optional.?;
var accepted: usize = 0; const expression = rule[0..colon_index];
var rejected: usize = 0; const destination = rule[colon_index + 1 ..];
itemChecking: for (parts.items) |part| { const attribute = expression[0];
var p: Part = part.*; const condition = expression[1];
var currentRule: Rule = ruleMap.get("in").?.*; const string_value = expression[2..];
checking: while (true) { const value = std.fmt.parseInt(usize, string_value, 10) catch unreachable;
for (currentRule.conditions.items) |condition| {
var c = condition.*;
// print("End Rule {s}\n", .{currentRule.endRule});
switch (c.category) { if (destination.len == 1) {
'x' => { return Rule{ .terminal_expression = TerminalExpression{ .expression = Expression{ .attribute = char_to_attribute(attribute), .value = value, .less_than = condition == '<' }, .accept = destination[0] == 'A' } };
if (c.operation == '<' and p.x < c.value or c.operation == '>' and p.x > c.value) { }
// print("X accept\n", .{});
if (condition.trueCondition[0] == 'A') { return Rule{ .non_terminal = NonTerminal{ .expression = Expression{ .attribute = char_to_attribute(attribute), .value = value, .less_than = condition == '<' }, .next_rule = destination } };
accepted += 1; }
try acceptedParts.append(p);
continue :itemChecking; fn build_workflow(allocator: std.mem.Allocator, workflow: []const u8) !Workflow {
} else if (condition.trueCondition[0] == 'R') { const first_curly_index = for (workflow, 0..) |c, index| {
rejected += 1; if (c != '{') {
continue :itemChecking; continue;
} }
currentRule = ruleMap.get(condition.trueCondition).?.*;
continue :checking; break index;
} } else unreachable;
},
'm' => { const rule_name = workflow[0..first_curly_index];
if (c.operation == '<' and p.m < c.value or c.operation == '>' and p.m > c.value) { const rule_workflow = workflow[first_curly_index + 1 .. workflow.len - 1];
// print("M accept\n", .{});
if (condition.trueCondition[0] == 'A') { const rule_number = std.mem.count(u8, rule_workflow, ",") + 1;
accepted += 1;
try acceptedParts.append(p); var rules_tokenizer = std.mem.tokenizeSequence(u8, rule_workflow, ",");
continue :itemChecking; var rules = try allocator.alloc(Rule, rule_number);
} else if (condition.trueCondition[0] == 'R') { var index: usize = 0;
rejected += 1;
continue :itemChecking; while (rules_tokenizer.next()) |rule| {
} rules[index] = build_rule(rule);
currentRule = ruleMap.get(condition.trueCondition).?.*; index += 1;
continue :checking; }
}
}, return Workflow{ .name = rule_name, .rules = rules };
'a' => { }
if (c.operation == '<' and p.a < c.value or c.operation == '>' and p.a > c.value) {
// print("A accept\n", .{}); fn build_part(part: []const u8) Part {
if (condition.trueCondition[0] == 'A') { const trimmed_part = part[1 .. part.len - 1];
accepted += 1;
try acceptedParts.append(p); var comma_tokenizer = std.mem.tokenizeSequence(u8, trimmed_part, ",");
continue :itemChecking;
} else if (condition.trueCondition[0] == 'R') { const x_string = comma_tokenizer.next().?;
rejected += 1; const m_string = comma_tokenizer.next().?;
continue :itemChecking; const a_string = comma_tokenizer.next().?;
} const s_string = comma_tokenizer.next().?;
currentRule = ruleMap.get(condition.trueCondition).?.*;
continue :checking; const x = std.fmt.parseInt(usize, x_string[2..], 10) catch unreachable;
} const m = std.fmt.parseInt(usize, m_string[2..], 10) catch unreachable;
}, const a = std.fmt.parseInt(usize, a_string[2..], 10) catch unreachable;
's' => { const s = std.fmt.parseInt(usize, s_string[2..], 10) catch unreachable;
if (c.operation == '<' and p.s < c.value or c.operation == '>' and p.s > c.value) {
// print("S Accept\n", .{}); return Part{ .x = x, .m = m, .a = a, .s = s };
if (condition.trueCondition[0] == 'A') { }
accepted += 1;
try acceptedParts.append(p); fn is_expression_accepted(expression: Expression, part: Part) bool {
continue :itemChecking; if (expression.less_than) {
} else if (condition.trueCondition[0] == 'R') { return switch (expression.attribute) {
rejected += 1; Attributes.x => part.x < expression.value,
continue :itemChecking; Attributes.m => part.m < expression.value,
} Attributes.a => part.a < expression.value,
// print("S true condition: {s}\n", .{condition.trueCondition}); Attributes.s => part.s < expression.value,
currentRule = ruleMap.get(condition.trueCondition).?.*; };
continue :checking; } else {
} return switch (expression.attribute) {
}, Attributes.x => part.x > expression.value,
else => unreachable, Attributes.m => part.m > expression.value,
Attributes.a => part.a > expression.value,
Attributes.s => part.s > expression.value,
};
}
}
fn run_workflow(part: Part, workflow: Workflow) StateUnion {
for (workflow.rules) |rule| {
switch (rule) {
RuleType.terminal => |terminal| {
return StateUnion{ .terminal = terminal.accept };
},
RuleType.non_terminal_instant => |non_terminal_instant| {
return StateUnion{ .non_terminal = non_terminal_instant.next_rule };
},
RuleType.terminal_expression => |_terminal_expression| {
const terminal_expression: TerminalExpression = _terminal_expression;
if (is_expression_accepted(terminal_expression.expression, part)) {
return StateUnion{ .terminal = terminal_expression.accept };
} }
// print("Reached end of rules\n", .{}); continue;
// print("-----\n", .{}); },
// Reached end of rule. But nothing was satisfied. RuleType.non_terminal => |_non_terminal| {
} const non_terminal: NonTerminal = _non_terminal;
if (is_expression_accepted(non_terminal.expression, part)) {
return StateUnion{ .non_terminal = non_terminal.next_rule };
}
if (currentRule.endAccept) { continue;
accepted += 1; },
try acceptedParts.append(p);
continue :itemChecking;
} else if (currentRule.endReject) {
rejected += 1;
continue :itemChecking;
}
currentRule = ruleMap.get(currentRule.endRule).?.*;
} }
} }
var part1: usize = 0; unreachable;
}
for (acceptedParts.items) |part| { fn tighten_range(xmas_range: XmasRange, range: Range, attribute: Attributes) XmasRange {
var p: Part = part; var new_range = XmasRange{
part1 += p.x + p.m + p.a + p.s; .x = xmas_range.x,
.m = xmas_range.m,
.a = xmas_range.a,
.s = xmas_range.s,
};
switch (attribute) {
Attributes.x => new_range.x = range,
Attributes.m => new_range.m = range,
Attributes.a => new_range.a = range,
Attributes.s => new_range.s = range,
}
return new_range;
}
fn get_split_range(range: Range, expression: Expression) struct { Range, Range } {
if (expression.less_than) {
const truth_range = Range{ .low = range.low, .high = expression.value - 1 };
const false_range = Range{ .low = expression.value, .high = range.high };
return .{ truth_range, false_range };
} else {
const truth_range = Range{ .low = expression.value + 1, .high = range.high };
const false_range = Range{ .low = range.low, .high = expression.value };
return .{ truth_range, false_range };
}
}
fn count_ranges(workflows: std.StringHashMap(Workflow), _range: XmasRange, workflow: Workflow) usize {
var total: usize = 0;
var range = XmasRange{
.x = _range.x,
.m = _range.m,
.a = _range.a,
.s = _range.s,
};
for (workflow.rules) |rule| {
switch (rule) {
RuleType.terminal => |_terminal| {
const terminal: Terminal = _terminal;
if (terminal.accept) {
total += range.sum();
}
},
RuleType.non_terminal_instant => |_non_terminal_instant| {
const non_terminal_instant: NonTerminalInstant = _non_terminal_instant;
total += count_ranges(workflows, range, workflows.get(non_terminal_instant.next_rule).?);
},
RuleType.terminal_expression => |_terminal_expression| {
const terminal_expression: TerminalExpression = _terminal_expression;
const current_range = switch (_terminal_expression.expression.attribute) {
Attributes.x => range.x,
Attributes.m => range.m,
Attributes.a => range.a,
Attributes.s => range.s,
};
const truth_range, const false_range = get_split_range(current_range, terminal_expression.expression);
if (!truth_range.is_empty()) {
if (terminal_expression.accept) {
const tigher_range = tighten_range(range, truth_range, terminal_expression.expression.attribute);
total += tigher_range.sum();
}
}
if (!false_range.is_empty()) {
range = tighten_range(range, false_range, terminal_expression.expression.attribute);
}
},
RuleType.non_terminal => |_non_terminal| {
const non_terminal: NonTerminal = _non_terminal;
const current_range = switch (non_terminal.expression.attribute) {
Attributes.x => range.x,
Attributes.m => range.m,
Attributes.a => range.a,
Attributes.s => range.s,
};
const truth_range, const false_range = get_split_range(current_range, non_terminal.expression);
if (!truth_range.is_empty()) {
const tigher_range = tighten_range(range, truth_range, non_terminal.expression.attribute);
total += count_ranges(workflows, tigher_range, workflows.get(non_terminal.next_rule).?);
}
if (!false_range.is_empty()) {
range = tighten_range(range, false_range, non_terminal.expression.attribute);
}
},
}
}
return total;
}
pub fn solve(input: [][]const u8) !void {
const allocator = std.heap.page_allocator;
const workflow_count = for (input, 0..) |line, i| {
if (line.len == 0) {
break i;
}
} else unreachable;
const parts_count = input.len - workflow_count - 1;
var workflows = std.StringHashMap(Workflow).init(allocator);
const parts = try allocator.alloc(Part, parts_count);
for (input) |line| {
if (line.len == 0) {
break;
}
const workflow = try build_workflow(allocator, line);
try workflows.put(workflow.name, workflow);
}
for (input[workflow_count + 1 ..], 0..) |line, i| {
parts[i] = build_part(line);
}
const accepted_parts = try allocator.alloc(Part, parts_count);
var accepted_parts_index: usize = 0;
var next_workflow = workflows.get("in").?;
for (parts) |part| {
workflowLoop: while (true) {
const workflow_result = run_workflow(part, next_workflow);
switch (workflow_result) {
State.terminal => |accept| {
if (accept) {
accepted_parts[accepted_parts_index] = part;
accepted_parts_index += 1;
}
break :workflowLoop;
},
State.non_terminal => |next_rule| {
next_workflow = workflows.get(next_rule).?;
},
}
}
next_workflow = workflows.get("in").?;
}
var part1: usize = 0;
for (accepted_parts[0..accepted_parts_index]) |part| {
part1 += part.x + part.m + part.a + part.s;
} }
print("Part 1: {}\n", .{part1}); print("Part 1: {}\n", .{part1});
allocator.free(accepted_parts);
allocator.free(parts);
const xmas_range = XmasRange{ .x = Range{ .low = 1, .high = 4000 }, .m = Range{ .low = 1, .high = 4000 }, .a = Range{ .low = 1, .high = 4000 }, .s = Range{ .low = 1, .high = 4000 } };
const part2 = count_ranges(workflows, xmas_range, workflows.get("in").?);
print("Part 2: {}\n", .{part2});
} }

View File

@ -14,9 +14,9 @@ const std = @import("std");
// const day13 = @import("./day13/day13.zig"); // const day13 = @import("./day13/day13.zig");
// const day15 = @import("./day15/day15.zig"); // const day15 = @import("./day15/day15.zig");
// const day16 = @import("./day16/day16.zig"); // const day16 = @import("./day16/day16.zig");
const day17 = @import("./day17/day17.zig"); // const day17 = @import("./day17/day17.zig");
// const day18 = @import("./day18/day18.zig"); // const day18 = @import("./day18/day18.zig");
// const day19 = @import("./day19/day19.zig"); const day19 = @import("./day19/day19.zig");
// const day21 = @import("./day21/day21.zig"); // const day21 = @import("./day21/day21.zig");
// const day23 = @import("./day23/day23.zig"); // const day23 = @import("./day23/day23.zig");
// const day24 = @import("./day24/day24.zig"); // const day24 = @import("./day24/day24.zig");
@ -24,8 +24,8 @@ const utils = @import("utils.zig");
pub fn main() !void { pub fn main() !void {
const allocator = std.heap.page_allocator; const allocator = std.heap.page_allocator;
const input = try utils.getInput("./src/day17/input.txt", allocator); const input = try utils.getInput("./src/day19/input.txt", allocator);
defer allocator.free(input); defer allocator.free(input);
try day17.solve(input); try day19.solve(input);
} }