diff --git a/src/day05.zig b/src/day05.zig index 9828ab6..7913895 100644 --- a/src/day05.zig +++ b/src/day05.zig @@ -5,117 +5,179 @@ const mem = std.mem; pub fn main() !void { const input = @embedFile("data/day05.txt"); const sln = try solve(util.gpa, input); - std.debug.print("{d}\n", .{sln.a}); - std.debug.print("{d}\n", .{sln.b}); + std.debug.print("{d}\n", .{sln}); + std.debug.print("{d}\n", .{sln}); } -const Solution = struct { - a: usize, - b: usize, -}; +fn Range(comptime T: type) type { + return struct { + start: T, + end: T, -const Transform = struct { - from: []const u8, - to: []const u8, -}; + const Self = @This(); + + /// Returns the intersection of two ranges. + pub fn intersect(self: Self, other: Self) ?Self { + const a = if (self.start < other.start) self else other; + const b = if (self.start < other.start) other else self; + // std.debug.print("{any}/{any}\n", .{ a, b }); + + if (a.start == a.end or b.start == b.end) return null; // Points don't intersect. + + if (a.end < b.start) return null; // No intersection. + if (a.end > b.end) return b; // A is a superset of B. + return .{ .start = b.start, .end = a.end }; + } + + /// Returns the union of two ranges. + pub fn join(self: Self, other: Self) ?Self { + if (self.intersect(other) == null) return null; // Can't union disjoint ranges. + // std.debug.print("merge {any}+{any}\n", .{ self, other }); + // Now guaranteed that they overlap. + const merged = Self{ .start = @min(self.start, other.start), .end = @max(self.end, other.end) }; + std.debug.print("= {any}\n", .{merged}); + return merged; + } + }; +} + +test "range intersect" { + const R = Range(usize); + const e = .{ .start = 2, .end = 5 }; + // Superset + try std.testing.expectEqual(e, (R{ .start = 0, .end = 6 }).intersect(.{ .start = 2, .end = 5 })); + try std.testing.expectEqual(e, (R{ .start = 2, .end = 5 }).intersect(.{ .start = 0, .end = 6 })); + // A < B + try std.testing.expectEqual(e, (R{ .start = 0, .end = 5 }).intersect(.{ .start = 2, .end = 6 })); + // B < A + try std.testing.expectEqual(e, (R{ .start = 2, .end = 6 }).intersect(.{ .start = 0, .end = 5 })); + // No intersect + try std.testing.expect(null == (R{ .start = 0, .end = 3 }).intersect(.{ .start = 4, .end = 6 })); +} const Map = struct { - dest_start: usize, - source_start: usize, - range: usize, + offset: isize, + source: Range(usize), }; const Component = struct { - name: []const u8, - value: usize, + stage: usize, + range: Range(usize), }; -const UsizeSet = std.AutoHashMap(usize, void); +fn mergeRanges(ranges: *std.ArrayList(Component)) void { + var i: usize = 0; + while (i < ranges.items.len) : (i += 1) { + var item = ranges.items[i]; + var j = i + 1; + while (j < ranges.items.len) { + const next = ranges.items[j]; + if (item.range.join(next.range)) |merged| { + const merged_item = .{ .stage = item.stage, .range = merged }; + item = merged_item; + ranges.items[i] = merged_item; + _ = ranges.swapRemove(j); + } else { + j += 1; + } + } + } +} -fn solve(alloc: mem.Allocator, input: []const u8) !Solution { +test "mergeRanges" { + var ranges = std.ArrayList(Component).init(std.testing.allocator); + defer ranges.deinit(); + try ranges.append(.{ .stage = 0, .range = .{ .start = 79, .end = 93 } }); + try ranges.append(.{ .stage = 0, .range = .{ .start = 55, .end = 66 } }); + try ranges.append(.{ .stage = 0, .range = .{ .start = 70, .end = 10 } }); + mergeRanges(&ranges); + try std.testing.expectEqual(.{ .start = 70, .end = 93 }, ranges.items[0].range); + try std.testing.expectEqual(.{ .start = 55, .end = 66 }, ranges.items[1].range); +} + +fn solve(alloc: mem.Allocator, input: []const u8) !usize { var it = util.splitLines(input); const seed_line = it.next().?; const seeds = try util.parseIntsScalar(usize, alloc, seed_line[mem.indexOfScalar(u8, seed_line, ':').? + 1 ..], .{}); defer alloc.free(seeds); - var last_values = UsizeSet.init(alloc); - var working_values = UsizeSet.init(alloc); - defer last_values.deinit(); - defer working_values.deinit(); + var ranges = std.ArrayList(Component).init(alloc); + defer ranges.deinit(); + var lowest_location: usize = std.math.maxInt(usize); - var low_loc: usize = std.math.maxInt(usize); - - var seeds_index: usize = 0; - while (seeds_index < seeds.len) : (seeds_index += 2) { - last_values.clearRetainingCapacity(); - working_values.clearRetainingCapacity(); - - std.debug.print("= seeding {d}/{d} =\n", .{ seeds_index, seeds.len }); - const start = seeds[seeds_index]; - const range = seeds[seeds_index + 1]; - for (start..start + range) |seed| { - // std.debug.print("seed {d}\n", .{seed}); - try working_values.put(seed, {}); - } - - // Reset parse input. - it = util.splitLines(input); - _ = it.next().?; - var current_component: []const u8 = undefined; - while (it.next()) |line| { - if (line.len == 0) { - std.debug.print("== swap ==\n", .{}); - // Copy all remaining - var lv_it = last_values.iterator(); - while (lv_it.next()) |entry| { - try working_values.put(entry.key_ptr.*, {}); - } - // Swap working sets - mem.swap(UsizeSet, &last_values, &working_values); - working_values.clearRetainingCapacity(); - } else if (mem.endsWith(u8, line, "map:")) { - var t_it = util.split(line, ' '); - var comp_it = util.split(t_it.next().?, '-'); - _ = comp_it.next(); // A - _ = comp_it.next(); // to - current_component = comp_it.next().?; // B - } else { - std.debug.print("== {s} {d}/{d}\n", .{ current_component, working_values.count(), last_values.count() }); - const map = try util.parseIntsScalar(usize, alloc, line, .{}); - defer alloc.free(map); - - var to_delete = UsizeSet.init(alloc); - defer to_delete.deinit(); - var lv_it = last_values.iterator(); - while (lv_it.next()) |entry| { - // if (transitioned[index]) { - // std.debug.print("{d} already mapped\n", .{index}); - // continue :map_blk; - // } - const item = entry.key_ptr.*; - if (map[1] <= item and item <= map[1] + map[2]) { - const offset = @as(isize, @intCast(map[0])) - @as(isize, @intCast(map[1])); - const res: usize = @intCast(@as(isize, @intCast(item)) + offset); - // std.debug.print("{d} => {d}\n", .{ item, res }); - try working_values.put(res, {}); - try to_delete.put(item, {}); - } - } - var td_it = to_delete.iterator(); - while (td_it.next()) |entry| { - // std.debug.print("pop {d}\n", .{entry.key_ptr.*}); - _ = last_values.remove(entry.key_ptr.*); - } - } - } - - var lv_it = last_values.iterator(); - while (lv_it.next()) |value| { - low_loc = @min(low_loc, value.key_ptr.*); + { + std.debug.print("= seeding {d} =\n", .{seeds.len}); + var index: usize = 0; + while (index < seeds.len) : (index += 2) { + try ranges.append(.{ + .stage = 0, + .range = .{ + .start = seeds[index], + .end = seeds[index] + seeds[index + 1], + }, + }); } } - return .{ .a = low_loc, .b = 0 }; + var current_stage: usize = 0; + while (it.next()) |line| { + if (line.len == 0) { + std.debug.print("== finalise ==\n", .{}); + for (ranges.items, 0..) |item, index| { + if (item.stage != current_stage) { + ranges.items[index] = .{ .stage = current_stage, .range = item.range }; + } + } + mergeRanges(&ranges); + } else if (mem.endsWith(u8, line, "map:")) { + current_stage += 1; + } else { + std.debug.print("== stage {d} map {s} ==\n", .{ current_stage, line }); + const map_line = try util.parseIntsScalar(usize, alloc, line, .{}); + defer alloc.free(map_line); + const map = Map{ + .offset = @as(isize, @intCast(map_line[0])) - @as(isize, @intCast(map_line[1])), + .source = .{ .start = map_line[1], .end = map_line[1] + map_line[2] }, + }; + + var i: usize = ranges.items.len; + while (i != 0) : (i -= 1) { + if (ranges.items[i - 1].stage == current_stage) continue; + const item = ranges.swapRemove(i - 1); + std.debug.print("item {d}/{d} {d}..{d}\n", .{ i, ranges.items.len + 1, item.range.start, item.range.end }); + + if (item.range.intersect(.{ .start = 0, .end = map.source.start })) |section| { + // Non-transitioning section. + std.debug.print("=> s {d}..{d}\n", .{ section.start, section.end }); + try ranges.append(.{ .stage = item.stage, .range = section }); + } + if (item.range.intersect(map.source)) |section| { + // Transitioning section. + const range = Range(usize){ + .start = @intCast(@as(isize, @intCast(section.start)) + map.offset), + .end = @intCast(@as(isize, @intCast(section.end)) + map.offset), + }; + std.debug.print("=> m {d}..{d}\n", .{ range.start, range.end }); + try ranges.append(.{ + .stage = current_stage, + .range = range, + }); + } + if (item.range.intersect(.{ .start = map.source.end, .end = std.math.maxInt(usize) })) |section| { + // Non-transitioning section. + std.debug.print("=> e {d}..{d}\n", .{ section.start, section.end }); + try ranges.append(.{ .stage = item.stage, .range = section }); + } + } + } + } + + for (ranges.items) |item| { + std.debug.print("{any}\n", .{item.range}); + lowest_location = @min(lowest_location, item.range.start); + } + + return lowest_location; } test "silver" { @@ -156,7 +218,7 @@ test "silver" { ; _ = input; // const sln = try solve(std.testing.allocator, input); - // try std.testing.expectEqual(@as(usize, 35), sln.a); + // try std.testing.expectEqual(@as(usize, 35), sln); } test "gold" { @@ -196,5 +258,5 @@ test "gold" { \\56 93 4 ; const sln = try solve(std.testing.allocator, input); - try std.testing.expectEqual(@as(usize, 46), sln.a); + try std.testing.expectEqual(@as(usize, 46), sln); }