I think today’s problem is quite practical. NonConsecutiveRange is a structure that is often used when dealing with actual problems, so I have encapsulated it.
const std = @import("std");
const ConsecutiveRange = packed struct {
end: usize,
start: usize,
const Backing = std.meta.Int(.unsigned, @bitSizeOf(ConsecutiveRange));
pub fn packStartEnd(start: usize, end: usize) ConsecutiveRange {
return .{ .start = start, .end = end };
}
pub fn asc(_: void, a: ConsecutiveRange, b: ConsecutiveRange) bool {
return @as(Backing, @bitCast(a)) < @as(Backing, @bitCast(b));
}
};
const NonConsecutiveRange = struct {
consecutive_ranges: []ConsecutiveRange,
const Builder = struct {
raw: std.ArrayList(ConsecutiveRange) = .empty,
to_merge: ConsecutiveRange = undefined,
merged: std.ArrayList(ConsecutiveRange) = .empty,
pub const init: Builder = .{};
pub fn append(self: *Builder, allocator: std.mem.Allocator, new_consecutive_range: ConsecutiveRange) !void {
try self.raw.append(allocator, new_consecutive_range);
}
pub fn toOwnedNonConsecutiveRange(self: *Builder, allocator: std.mem.Allocator) !NonConsecutiveRange {
defer {
self.raw.deinit(allocator);
self.* = undefined;
}
if (self.raw.items.len == 0) return .{ .consecutive_ranges = &[0]ConsecutiveRange{} };
std.mem.sortUnstable(ConsecutiveRange, self.raw.items, {}, ConsecutiveRange.asc);
self.to_merge = self.raw.items[0];
for (self.raw.items[1..]) |r| {
if (r.start > self.to_merge.end + 1) {
try self.merged.append(allocator, self.to_merge);
self.to_merge = r;
continue;
}
if (r.end <= self.to_merge.end) continue;
self.to_merge = .packStartEnd(self.to_merge.start, r.end);
}
try self.merged.append(allocator, self.to_merge);
return .{ .consecutive_ranges = try self.merged.toOwnedSlice(allocator) };
}
};
pub fn contain(self: NonConsecutiveRange, value: usize) bool {
var bound_low: usize = 0;
var bound_high: usize = self.consecutive_ranges.len;
while (bound_low < bound_high) {
const mid = bound_low + (bound_high - bound_low) / 2;
const cr = self.consecutive_ranges[mid];
if (value < cr.start) bound_high = mid else if (value > cr.end) bound_low = mid + 1 else return true;
}
return false;
}
};
pub fn part1(input: []const u8) !usize {
var gpa: std.heap.DebugAllocator(.{}) = .init;
const allocator = gpa.allocator();
var line_it = std.mem.splitScalar(u8, input, '\n');
var sum: usize = 0;
const ncrs = read_ranges: {
var ncrbuilder: NonConsecutiveRange.Builder = .init;
while (line_it.next()) |line| {
if (line.len == 0) break :read_ranges try ncrbuilder.toOwnedNonConsecutiveRange(allocator);
var it = std.mem.splitScalar(u8, line, '-');
const start_str = it.next().?;
const end_str = it.next().?;
const start = try std.fmt.parseUnsigned(usize, start_str, 10);
const end = try std.fmt.parseUnsigned(usize, end_str, 10);
const cr: ConsecutiveRange = .packStartEnd(start, end);
try ncrbuilder.append(allocator, cr);
}
unreachable;
};
scan_food: while (line_it.next()) |line| {
if (line.len == 0) break :scan_food;
const food = try std.fmt.parseUnsigned(usize, line, 10);
if (ncrs.contain(food)) sum += 1;
}
return sum;
}
pub fn part2(input: []const u8) !usize {
var gpa: std.heap.DebugAllocator(.{}) = .init;
const allocator = gpa.allocator();
var line_it = std.mem.splitScalar(u8, input, '\n');
var sum: usize = 0;
const ncrs = read_ranges: {
var ncrbuilder: NonConsecutiveRange.Builder = .init;
while (line_it.next()) |line| {
if (line.len == 0) break :read_ranges try ncrbuilder.toOwnedNonConsecutiveRange(allocator);
var it = std.mem.splitScalar(u8, line, '-');
const start_str = it.next().?;
const end_str = it.next().?;
const start = try std.fmt.parseUnsigned(usize, start_str, 10);
const end = try std.fmt.parseUnsigned(usize, end_str, 10);
const cr: ConsecutiveRange = .packStartEnd(start, end);
try ncrbuilder.append(allocator, cr);
}
unreachable;
};
for (ncrs.consecutive_ranges) |cr| {
sum += cr.end - cr.start + 1;
}
return sum;
}