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