From 932729eb9726058ef48e438bbc741e53b8d427b8 Mon Sep 17 00:00:00 2001 From: John Costa Date: Sat, 21 Sep 2024 23:38:11 +0100 Subject: [PATCH] feat(day-19): hell yeah --- AdventOfCode2023/src/day19/day19.zig | 533 ++++++++++++++++++--------- AdventOfCode2023/src/main.zig | 8 +- 2 files changed, 368 insertions(+), 173 deletions(-) diff --git a/AdventOfCode2023/src/day19/day19.zig b/AdventOfCode2023/src/day19/day19.zig index d73e496..8d29166 100644 --- a/AdventOfCode2023/src/day19/day19.zig +++ b/AdventOfCode2023/src/day19/day19.zig @@ -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}); } diff --git a/AdventOfCode2023/src/main.zig b/AdventOfCode2023/src/main.zig index e595190..60cef7b 100644 --- a/AdventOfCode2023/src/main.zig +++ b/AdventOfCode2023/src/main.zig @@ -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); }