diff options
| author | Orfeas <38209077+0xfea5@users.noreply.github.com> | 2023-12-07 06:56:53 +0200 |
|---|---|---|
| committer | Orfeas <38209077+0xfea5@users.noreply.github.com> | 2025-10-28 23:20:45 +0200 |
| commit | 3ffc9de9efd3b3650eaf3ccc77e434fa000a9a08 (patch) | |
| tree | 49aee2fcbb4a5f7a57a67ea49b6b0f3cefa35117 | |
| parent | create template file and update init script (diff) | |
| download | aoc23-3ffc9de9efd3b3650eaf3ccc77e434fa000a9a08.tar.gz aoc23-3ffc9de9efd3b3650eaf3ccc77e434fa000a9a08.zip | |
day5
| -rw-r--r-- | day02/solution.zig | 6 | ||||
| -rw-r--r-- | day04/solution.zig | 6 | ||||
| -rw-r--r-- | day05/example.txt | 33 | ||||
| -rw-r--r-- | day05/solution.zig | 276 |
4 files changed, 309 insertions, 12 deletions
diff --git a/day02/solution.zig b/day02/solution.zig index 5e8d6f6..b40274d 100644 --- a/day02/solution.zig +++ b/day02/solution.zig | |||
| @@ -45,10 +45,10 @@ pub fn parseBall(text: []const u8) Ball { | |||
| 45 | const trimmed = mem.trim(u8, text, &std.ascii.whitespace); | 45 | const trimmed = mem.trim(u8, text, &std.ascii.whitespace); |
| 46 | var splitBalls = mem.splitScalar(u8, trimmed, ' '); | 46 | var splitBalls = mem.splitScalar(u8, trimmed, ' '); |
| 47 | 47 | ||
| 48 | const nballs = std.fmt.parseInt(u64, splitBalls.next() orelse unreachable, 10) catch unreachable; | 48 | const nballs = std.fmt.parseInt(u64, splitBalls.next().?, 10) catch unreachable; |
| 49 | var color: Color = undefined; | 49 | var color: Color = undefined; |
| 50 | color = blk: { | 50 | color = blk: { |
| 51 | const s = splitBalls.next() orelse unreachable; | 51 | const s = splitBalls.next().?; |
| 52 | 52 | ||
| 53 | if (mem.eql(u8, s, "red")) { | 53 | if (mem.eql(u8, s, "red")) { |
| 54 | break :blk .red; | 54 | break :blk .red; |
| @@ -152,7 +152,7 @@ pub fn main() !void { | |||
| 152 | var splitGame = mem.splitScalar(u8, line, ':'); | 152 | var splitGame = mem.splitScalar(u8, line, ':'); |
| 153 | _ = splitGame.next(); | 153 | _ = splitGame.next(); |
| 154 | 154 | ||
| 155 | const game = try parseGame(splitGame.next() orelse unreachable); | 155 | const game = try parseGame(splitGame.next().?); |
| 156 | try games.append(game); | 156 | try games.append(game); |
| 157 | } | 157 | } |
| 158 | 158 | ||
diff --git a/day04/solution.zig b/day04/solution.zig index 081277a..6f67569 100644 --- a/day04/solution.zig +++ b/day04/solution.zig | |||
| @@ -39,12 +39,12 @@ fn parseCard(text: []const u8) !Card { | |||
| 39 | 39 | ||
| 40 | var prefix = mem.splitScalar(u8, text, ':'); | 40 | var prefix = mem.splitScalar(u8, text, ':'); |
| 41 | _ = prefix.next(); // ignore 'Card ##' prefix | 41 | _ = prefix.next(); // ignore 'Card ##' prefix |
| 42 | const postfix = prefix.next() orelse unreachable; | 42 | const postfix = prefix.next().?; |
| 43 | 43 | ||
| 44 | var allNumbers = mem.splitScalar(u8, postfix, '|'); | 44 | var allNumbers = mem.splitScalar(u8, postfix, '|'); |
| 45 | 45 | ||
| 46 | var winningText = mem.splitScalar(u8, allNumbers.next() orelse unreachable, ' '); | 46 | var winningText = mem.splitScalar(u8, allNumbers.next().?, ' '); |
| 47 | var ownedText = mem.splitScalar(u8, allNumbers.next() orelse unreachable, ' '); | 47 | var ownedText = mem.splitScalar(u8, allNumbers.next().?, ' '); |
| 48 | // split produces some empty strings that must be ignored later | 48 | // split produces some empty strings that must be ignored later |
| 49 | 49 | ||
| 50 | while (winningText.next()) |numText| { | 50 | while (winningText.next()) |numText| { |
diff --git a/day05/example.txt b/day05/example.txt new file mode 100644 index 0000000..f756727 --- /dev/null +++ b/day05/example.txt | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | seeds: 79 14 55 13 | ||
| 2 | |||
| 3 | seed-to-soil map: | ||
| 4 | 50 98 2 | ||
| 5 | 52 50 48 | ||
| 6 | |||
| 7 | soil-to-fertilizer map: | ||
| 8 | 0 15 37 | ||
| 9 | 37 52 2 | ||
| 10 | 39 0 15 | ||
| 11 | |||
| 12 | fertilizer-to-water map: | ||
| 13 | 49 53 8 | ||
| 14 | 0 11 42 | ||
| 15 | 42 0 7 | ||
| 16 | 57 7 4 | ||
| 17 | |||
| 18 | water-to-light map: | ||
| 19 | 88 18 7 | ||
| 20 | 18 25 70 | ||
| 21 | |||
| 22 | light-to-temperature map: | ||
| 23 | 45 77 23 | ||
| 24 | 81 45 19 | ||
| 25 | 68 64 13 | ||
| 26 | |||
| 27 | temperature-to-humidity map: | ||
| 28 | 0 69 1 | ||
| 29 | 1 0 69 | ||
| 30 | |||
| 31 | humidity-to-location map: | ||
| 32 | 60 56 37 | ||
| 33 | 56 93 4 | ||
diff --git a/day05/solution.zig b/day05/solution.zig index 059a120..c22af8b 100644 --- a/day05/solution.zig +++ b/day05/solution.zig | |||
| @@ -9,15 +9,279 @@ const fin = mem.trim(u8, @embedFile("./input.txt"), &std.ascii.whitespace); | |||
| 9 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; | 9 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; |
| 10 | const allocator = gpa.allocator(); | 10 | const allocator = gpa.allocator(); |
| 11 | 11 | ||
| 12 | pub fn part1() void { | 12 | // |
| 13 | // Part 1 goes here | 13 | // I represent the input in the form of: |
| 14 | // | ||
| 15 | // Input = [ | ||
| 16 | // Map [ | ||
| 17 | // Translation { | ||
| 18 | // src = Range { | ||
| 19 | // begin, | ||
| 20 | // len, | ||
| 21 | // }, | ||
| 22 | // dst = Range { | ||
| 23 | // begin, | ||
| 24 | // len, | ||
| 25 | // } | ||
| 26 | // } | ||
| 27 | // ... | ||
| 28 | // ] | ||
| 29 | // ... | ||
| 30 | // ] | ||
| 31 | // | ||
| 32 | // I filled unmapped areas with identity maps (1-1 translation src -> dest) | ||
| 33 | // to avoid edge cases. | ||
| 34 | // | ||
| 35 | // | ||
| 36 | const Range = struct { | ||
| 37 | begin: i64, | ||
| 38 | len: i64, | ||
| 39 | }; | ||
| 40 | |||
| 41 | const Translation = struct { | ||
| 42 | src: Range, | ||
| 43 | dst: Range, | ||
| 44 | }; | ||
| 45 | |||
| 46 | const Map = struct { | ||
| 47 | ranges: ArrayList(Translation), | ||
| 48 | |||
| 49 | pub fn init() Map { | ||
| 50 | const ranges = ArrayList(Translation).init(allocator); | ||
| 51 | |||
| 52 | return Map{ | ||
| 53 | .ranges = ranges, | ||
| 54 | }; | ||
| 55 | } | ||
| 56 | |||
| 57 | pub fn deinit(self: *@This()) void { | ||
| 58 | self.ranges.deinit(); | ||
| 59 | } | ||
| 60 | }; | ||
| 61 | |||
| 62 | fn parseSeeds(text: []const u8) ArrayList(i64) { | ||
| 63 | var result = ArrayList(i64).init(allocator); | ||
| 64 | var textNumbers = mem.splitScalar(u8, text, ' '); | ||
| 65 | |||
| 66 | // Ignore header | ||
| 67 | _ = textNumbers.next(); | ||
| 68 | |||
| 69 | while (textNumbers.next()) |textNum| { | ||
| 70 | const n = std.fmt.parseInt(i64, textNum, 10) catch unreachable; | ||
| 71 | result.append(n) catch unreachable; | ||
| 72 | } | ||
| 73 | |||
| 74 | return result; | ||
| 75 | } | ||
| 76 | |||
| 77 | fn parseTranslation(text: []const u8) Translation { | ||
| 78 | var splitNums = mem.splitScalar(u8, text, ' '); | ||
| 79 | const dst_begin = std.fmt.parseInt(i64, splitNums.next().?, 10) catch unreachable; | ||
| 80 | const src_begin = std.fmt.parseInt(i64, splitNums.next().?, 10) catch unreachable; | ||
| 81 | const len = std.fmt.parseInt(i64, splitNums.next().?, 10) catch unreachable; | ||
| 82 | |||
| 83 | return Translation{ | ||
| 84 | .src = .{ | ||
| 85 | .begin = src_begin, | ||
| 86 | .len = len, | ||
| 87 | }, | ||
| 88 | .dst = .{ | ||
| 89 | .begin = dst_begin, | ||
| 90 | .len = len, | ||
| 91 | }, | ||
| 92 | }; | ||
| 93 | } | ||
| 94 | |||
| 95 | fn translationLess(@"_": void, lhs: Translation, rhs: Translation) bool { | ||
| 96 | return rangeLess(@"_", lhs.src, rhs.src); | ||
| 97 | } | ||
| 98 | |||
| 99 | fn parseMap(text: []const u8) Map { | ||
| 100 | var splitLines = mem.splitScalar(u8, text, '\n'); | ||
| 101 | var translations = ArrayList(Translation).init(allocator); | ||
| 102 | defer translations.deinit(); | ||
| 103 | |||
| 104 | // Ignore header | ||
| 105 | _ = splitLines.next(); | ||
| 106 | |||
| 107 | while (splitLines.next()) |line| { | ||
| 108 | translations.append(parseTranslation(line)) catch unreachable; | ||
| 109 | } | ||
| 110 | mem.sort(Translation, translations.items, {}, translationLess); | ||
| 111 | |||
| 112 | var gapFilled = ArrayList(Translation).init(allocator); | ||
| 113 | const items = translations.items; | ||
| 114 | |||
| 115 | // Fill gap from 0 up to the first segment | ||
| 116 | if (items[0].src.begin > 0) { | ||
| 117 | gapFilled.append(.{ .src = .{ | ||
| 118 | .begin = 0, | ||
| 119 | .len = items[0].src.begin, | ||
| 120 | }, .dst = .{ | ||
| 121 | .begin = 0, | ||
| 122 | .len = items[0].src.begin, | ||
| 123 | } }) catch unreachable; | ||
| 124 | } | ||
| 125 | |||
| 126 | // Fill intermediate gaps | ||
| 127 | for (items[0 .. items.len - 1], 0..) |tr, i| { | ||
| 128 | const curr = tr.src; | ||
| 129 | const next = items[i + 1].src; | ||
| 130 | gapFilled.append(items[i]) catch unreachable; | ||
| 131 | if (rangeAdjacent(curr, next) == null) { | ||
| 132 | gapFilled.append(.{ .src = .{ | ||
| 133 | .begin = curr.begin + curr.len, | ||
| 134 | .len = next.begin - curr.begin - curr.len, | ||
| 135 | }, .dst = .{ | ||
| 136 | .begin = curr.begin + curr.len, | ||
| 137 | .len = next.begin - curr.begin - curr.len, | ||
| 138 | } }) catch unreachable; | ||
| 139 | } | ||
| 140 | } | ||
| 141 | |||
| 142 | // Fill gap from the end of the last segment to +infinity | ||
| 143 | const len = items.len; | ||
| 144 | gapFilled.append(.{ .src = .{ | ||
| 145 | .begin = items[len - 1].src.begin, | ||
| 146 | .len = 4_000_000_000, | ||
| 147 | }, .dst = .{ | ||
| 148 | .begin = items[len - 1].src.begin, | ||
| 149 | .len = 4_000_000_000, | ||
| 150 | } }) catch unreachable; | ||
| 151 | |||
| 152 | return Map{ | ||
| 153 | .ranges = gapFilled, | ||
| 154 | }; | ||
| 155 | } | ||
| 156 | |||
| 157 | fn evalSeed(seed: i64, maps: []const Map) i64 { | ||
| 158 | var s = seed; | ||
| 159 | |||
| 160 | for (maps) |map| { | ||
| 161 | for (map.ranges.items) |range| { | ||
| 162 | if (range.src.begin <= s and s < range.src.begin + range.src.len) { | ||
| 163 | s += range.dst.begin - range.src.begin; | ||
| 164 | break; | ||
| 165 | } | ||
| 166 | } | ||
| 167 | } | ||
| 168 | |||
| 169 | return s; | ||
| 170 | } | ||
| 171 | |||
| 172 | fn rangeLess(_: void, lhs: Range, rhs: Range) bool { | ||
| 173 | return lhs.begin < rhs.begin or (lhs.begin == rhs.begin and lhs.len > rhs.len); | ||
| 174 | } | ||
| 175 | |||
| 176 | fn rangeMin(lhs: Range, rhs: Range) Range { | ||
| 177 | return if (rangeLess({}, lhs, rhs)) lhs else rhs; | ||
| 14 | } | 178 | } |
| 15 | 179 | ||
| 16 | pub fn part2() void { | 180 | fn rangeMax(lhs: Range, rhs: Range) Range { |
| 17 | // Part 2 goes here | 181 | return if (rangeLess({}, lhs, rhs)) rhs else lhs; |
| 182 | } | ||
| 183 | |||
| 184 | fn rangeOverlap(lhs: Range, rhs: Range) ?Range { | ||
| 185 | const min = rangeMin(lhs, rhs); | ||
| 186 | const max = rangeMax(lhs, rhs); | ||
| 187 | |||
| 188 | if (min.begin + min.len <= max.begin) { | ||
| 189 | return null; | ||
| 190 | } | ||
| 191 | |||
| 192 | return Range{ | ||
| 193 | .begin = max.begin, | ||
| 194 | .len = @min(min.begin + min.len - max.begin, max.len), | ||
| 195 | }; | ||
| 196 | } | ||
| 197 | |||
| 198 | fn rangeAdjacent(lhs: Range, rhs: Range) ?Range { | ||
| 199 | const min = rangeMin(lhs, rhs); | ||
| 200 | const max = rangeMax(lhs, rhs); | ||
| 201 | |||
| 202 | if (min.begin + min.len != max.begin) { | ||
| 203 | return null; | ||
| 204 | } | ||
| 205 | |||
| 206 | return Range{ | ||
| 207 | .begin = max.begin, | ||
| 208 | .len = @min(min.begin + min.len - max.begin, max.len), | ||
| 209 | }; | ||
| 210 | } | ||
| 211 | |||
| 212 | pub fn part1(seeds: []const i64, maps: []const Map) void { | ||
| 213 | var ans: i64 = std.math.maxInt(i64); | ||
| 214 | |||
| 215 | for (seeds) |seed| { | ||
| 216 | const s = evalSeed(seed, maps); | ||
| 217 | ans = @min(ans, s); | ||
| 218 | } | ||
| 219 | |||
| 220 | print("{d}\n", .{ans}); | ||
| 221 | } | ||
| 222 | |||
| 223 | fn evalNextState(m: Map, ranges: ArrayList(Range)) ArrayList(Range) { | ||
| 224 | var result = ArrayList(Range).init(allocator); | ||
| 225 | |||
| 226 | // Translate all seed range(s) | ||
| 227 | for (ranges.items) |r| { | ||
| 228 | |||
| 229 | // For every translation | ||
| 230 | for (m.ranges.items) |t| { | ||
| 231 | const overlap = rangeOverlap(t.src, r) orelse continue; | ||
| 232 | |||
| 233 | result.append(.{ | ||
| 234 | .begin = t.dst.begin + overlap.begin - t.src.begin, | ||
| 235 | .len = overlap.len, | ||
| 236 | }) catch unreachable; | ||
| 237 | } | ||
| 238 | } | ||
| 239 | |||
| 240 | assert(result.items.len > 0); | ||
| 241 | mem.sort(Range, result.items, {}, rangeLess); | ||
| 242 | |||
| 243 | return result; | ||
| 244 | } | ||
| 245 | |||
| 246 | fn evalLowest(startRange: Range, maps: []const Map) i64 { | ||
| 247 | var ranges = ArrayList(Range).init(allocator); | ||
| 248 | defer ranges.deinit(); | ||
| 249 | |||
| 250 | // Start with a single seed range | ||
| 251 | ranges.append(startRange) catch unreachable; | ||
| 252 | |||
| 253 | // For every map | ||
| 254 | for (maps) |m| { | ||
| 255 | const nextRanges = evalNextState(m, ranges); | ||
| 256 | ranges.deinit(); | ||
| 257 | ranges = nextRanges; | ||
| 258 | } | ||
| 259 | |||
| 260 | return ranges.items[0].begin; | ||
| 261 | } | ||
| 262 | |||
| 263 | pub fn part2(seeds: []const i64, maps: []const Map) void { | ||
| 264 | var ans: i64 = std.math.maxInt(i64); | ||
| 265 | |||
| 266 | for (0..seeds.len / 2) |i| { | ||
| 267 | const begin: i64 = @intCast(seeds[i * 2]); | ||
| 268 | const len: i64 = @intCast(seeds[i * 2 + 1]); | ||
| 269 | |||
| 270 | ans = @min(ans, evalLowest(.{ .begin = begin, .len = len }, maps)); | ||
| 271 | } | ||
| 272 | print("{d}\n", .{ans}); | ||
| 18 | } | 273 | } |
| 19 | 274 | ||
| 20 | pub fn main() !void { | 275 | pub fn main() !void { |
| 21 | part1(); | 276 | var splitLines = mem.splitSequence(u8, fin, "\n\n"); |
| 22 | part2(); | 277 | const seedsText = splitLines.next().?; |
| 278 | const seeds = parseSeeds(seedsText); | ||
| 279 | |||
| 280 | var maps = ArrayList(Map).init(allocator); | ||
| 281 | while (splitLines.next()) |mapText| { | ||
| 282 | maps.append(parseMap(mapText)) catch unreachable; | ||
| 283 | } | ||
| 284 | |||
| 285 | part1(seeds.items, maps.items); | ||
| 286 | part2(seeds.items, maps.items); | ||
| 23 | } | 287 | } |
