const std = @import("std");
const PATH = "../input/day16.txt";
const Str = []const u8;
const InputType = u10;
const Range = [2]InputType;
const ticket_lenght = 20;
const Ticket = [ticket_lenght]InputType;
const Data = struct {
ranges: []Range,
tickets: []Ticket,
};
pub fn first(allocator: ?std.mem.Allocator) anyerror!usize {
const file = @embedFile(PATH);
const ranges = try parseRanges(allocator.?, file);
defer allocator.?.free(ranges);
var sum: usize = 0;
var empty_lines: u2 = 0;
var lines = std.mem.split(u8, file, "\n");
while (lines.next()) |line| {
if (line.len == 0) {
empty_lines += 1;
continue;
}
if (empty_lines < 2) continue;
if (line[0] < 48 or line[0] > 57) continue;
var numbers = std.mem.tokenize(u8, line, ",");
nums: while (numbers.next()) |num_str| {
const num = try std.fmt.parseUnsigned(InputType, num_str, 0);
for (ranges) |range| {
if (num >= range[0] and num <= range[1]) continue :nums;
}
sum += num;
}
}
return sum;
}
pub fn second(allocator: ?std.mem.Allocator) anyerror!usize {
const data = try parseData(allocator.?, @embedFile(PATH));
defer {
allocator.?.free(data.ranges);
allocator.?.free(data.tickets);
}
var prod: usize = 1;
var found_columns = std.StaticBitSet(ticket_lenght).initEmpty();
var found_ranges: u20 = 0;
while (true) {
var idx: usize = 0;
ranges: while (idx < data.ranges.len / 2) : (idx += 1) {
std.debug.assert(idx < data.ranges.len / 2);
if (found_ranges & @as(u20, 1) << @intCast(u5, idx) != 0) continue;
// starting value is out of range, guaranteen uniqueness
var match: usize = ticket_lenght;
var col: usize = 0;
cols: while (col < ticket_lenght) : (col += 1) {
std.debug.assert(col < ticket_lenght);
// skip already found columns
if (found_columns.isSet(col)) continue;
for (data.tickets) |ticket| {
if (!( // first range
(ticket[col] >= data.ranges[2 * idx][0] and
ticket[col] <= data.ranges[2 * idx][1]) or
// second range
(ticket[col] >= data.ranges[2 * idx + 1][0] and
ticket[col] <= data.ranges[2 * idx + 1][1])))
{
// number out of range, go to next column
continue :cols;
}
}
// there was a previous valid match, so this range is not unique
if (match != 20) continue :ranges;
match = col;
}
// only one matching column found
found_columns.set(match);
found_ranges |= @as(u20, 1) << @intCast(u5, idx);
// some "departure"
if (idx < 6) prod *= data.tickets[0][match];
// first six range found
if (found_ranges & 0b111111 == 0b111111) return prod;
}
}
unreachable;
}
fn parseData(allocator: std.mem.Allocator, input: Str) !Data {
var lines = std.mem.split(u8, input, "\n");
var data: Data = undefined;
var ranges = std.ArrayList(Range).init(allocator);
while (lines.next()) |line| {
if (line.len == 0) break;
var name_range = std.mem.tokenize(u8, line, ":");
while (name_range.next()) |nr| {
var rngs = std.mem.tokenize(u8, nr, " ");
while (rngs.next()) |rng| {
if (rng[0] < 48 or rng[0] > 57) continue; // skip non-numbers
var lo_hi = std.mem.tokenize(u8, rng, "-");
try ranges.append(.{
try std.fmt.parseUnsigned(InputType, lo_hi.next().?, 0),
try std.fmt.parseUnsigned(InputType, lo_hi.next().?, 0),
});
}
}
}
data.ranges = ranges.toOwnedSlice();
var tickets = std.ArrayList(Ticket).init(allocator);
lines: while (lines.next()) |line| {
if (line.len == 0) continue;
if (line[0] < 48 or line[0] > 57) continue;
var ticket: Ticket = undefined;
var numbers = std.mem.tokenize(u8, line, ",");
var idx: usize = 0;
nums: while (numbers.next()) |num_str| : (idx += 1) {
ticket[idx] = try std.fmt.parseUnsigned(InputType, num_str, 0);
for (data.ranges) |range| {
if (ticket[idx] >= range[0] and ticket[idx] <= range[1]) continue :nums;
}
continue :lines;
}
try tickets.append(ticket);
}
data.tickets = tickets.toOwnedSlice();
return data;
}
fn parseRanges(allocator: std.mem.Allocator, input: Str) ![]Range {
var lines = std.mem.split(u8, input, "\n");
var ranges = std.ArrayList(Range).init(allocator);
while (lines.next()) |line| {
if (line.len == 0) break;
var name_range = std.mem.tokenize(u8, line, ":");
while (name_range.next()) |nr| {
var rngs = std.mem.tokenize(u8, nr, " ");
while (rngs.next()) |rng| {
if (rng[0] < 48 or rng[0] > 57) continue; // skip non-numbers
var lo_hi = std.mem.tokenize(u8, rng, "-");
try ranges.append(.{
try std.fmt.parseUnsigned(InputType, lo_hi.next().?, 0),
try std.fmt.parseUnsigned(InputType, lo_hi.next().?, 0),
});
}
}
}
return ranges.toOwnedSlice();
}
test "day16a" {
try std.testing.expectEqual(@as(usize, 19087), try first(std.testing.allocator));
}
test "day16b" {
try std.testing.expectEqual(@as(usize, 1382443095281), try second(std.testing.allocator));
}