diff --git a/src/day04.zig b/src/day04.zig new file mode 100644 index 0000000..2e495a6 --- /dev/null +++ b/src/day04.zig @@ -0,0 +1,99 @@ +const std = @import("std"); +const util = @import("util.zig"); +const mem = std.mem; + +pub fn main() !void { + const input = @embedFile("data/day04.txt"); + const sln = try solve(util.gpa, input); + std.debug.print("{d}\n", .{sln.value}); + std.debug.print("{d}\n", .{sln.copies}); +} + +const Solution = struct { + value: usize, + copies: usize, +}; + +fn solve(alloc: mem.Allocator, input: []const u8) !Solution { + var value: usize = 0; + var card_counts = std.AutoHashMap(usize, usize).init(alloc); + defer card_counts.deinit(); + + var card: usize = 0; + var it = util.splitLines(input); + while (it.next()) |line| : (card += 1) { + if (line.len == 0) continue; + + const sep = mem.indexOfScalar(u8, line, ':').?; + var line_it = util.split(line[sep + 1 ..], '|'); + const winning = try util.parseIntsScalar(usize, alloc, line_it.next().?, .{}); + defer alloc.free(winning); + const mine = try util.parseIntsScalar(usize, alloc, line_it.next().?, .{}); + defer alloc.free(mine); + + var matches: usize = 0; + for (mine) |n| { + if (mem.containsAtLeast(usize, winning, 1, &[_]usize{n})) { + matches += 1; + } + } + if (matches > 0) { + value += std.math.pow(usize, 2, matches - 1); + } + + // Initialise with 1 copy of current card + const copies = blk: { + const entry = try card_counts.getOrPut(card); + if (entry.found_existing) { + entry.value_ptr.* += 1; + } else { + entry.value_ptr.* = 1; + } + break :blk entry.value_ptr.*; + }; + + // 1 additional card for each match, multiplied by copies of this card + for (1..matches + 1) |offset| { + const winning_card_entry = try card_counts.getOrPut(card + offset); + if (winning_card_entry.found_existing) { + winning_card_entry.value_ptr.* += copies; + } else { + winning_card_entry.value_ptr.* = copies; + } + } + } + + var copy_count: usize = 0; + var copy_it = card_counts.valueIterator(); + while (copy_it.next()) |v| { + copy_count += v.*; + } + + return .{ .value = value, .copies = copy_count }; +} + +test "silver" { + const input = + \\Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53 + \\Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19 + \\Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1 + \\Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83 + \\Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36 + \\Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11 + ; + const sln = try solve(std.testing.allocator, input); + try std.testing.expectEqual(@as(usize, 13), sln.value); +} + +test "gold" { + const input = + \\Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53 + \\Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19 + \\Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1 + \\Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83 + \\Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36 + \\Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11 + ; + const sln = try solve(std.testing.allocator, input); + try std.testing.expectEqual(@as(usize, 30), sln.copies); +} diff --git a/src/util.zig b/src/util.zig index b32ab82..d262933 100644 --- a/src/util.zig +++ b/src/util.zig @@ -1,4 +1,37 @@ const std = @import("std"); +const mem = std.mem; var gpa_impl = std.heap.GeneralPurposeAllocator(.{}){}; +/// General purpose allocator. pub const gpa = gpa_impl.allocator(); + +pub const ParseIntsOptions = struct { + base: u8 = 10, + sep: u8 = ' ', +}; + +/// Parses separated integers. Sequential separators are treated as one. Caller owns memory. +pub fn parseIntsScalar(comptime T: type, alloc: std.mem.Allocator, input: []const u8, opts: ParseIntsOptions) ![]T { + var items = std.ArrayList(T).init(alloc); + errdefer items.deinit(); + + var it = split(input, opts.sep); + while (it.next()) |i| { + if (i.len == 0) continue; + try items.append(try std.fmt.parseInt(T, i, opts.base)); + } + return try items.toOwnedSlice(); +} + +/// Shortcut to splitScalar with a byte buffer. +pub fn split(buffer: []const u8, sep: u8) mem.SplitIterator(u8, .scalar) { + return mem.splitScalar(u8, buffer, sep); +} + +pub fn splitLines(buffer: []const u8) mem.SplitIterator(u8, .scalar) { + return split(buffer, '\n'); +} + +pub fn splitSpace(buffer: []const u8) mem.SplitIterator(u8, .scalar) { + return split(buffer, ' '); +}