const aoc = @import("./aoc.zig"); const std = @import("std"); const MappingRange = struct { src_start: u64, dest_start: u64, size: u64, }; const MappingSet = std.BoundedArray(MappingRange, 256); const SeedsArray = std.BoundedArray(u64, 32); const Input = struct { seeds: SeedsArray, seed_to_soil : MappingSet, soil_to_fertilizer : MappingSet, fertilizer_to_water : MappingSet, water_to_light : MappingSet, light_to_temperature : MappingSet, temperature_to_humidity: MappingSet, humidity_to_location : MappingSet, fn get_mappings(self: *const Input) [7]*const MappingSet { return [_]*const MappingSet{ &self.seed_to_soil, &self.soil_to_fertilizer, &self.fertilizer_to_water, &self.water_to_light, &self.light_to_temperature, &self.temperature_to_humidity, &self.humidity_to_location, }; } }; fn parse_seeds(result: *SeedsArray, text: []const u8) !void { var i: usize = 0; var tokens = std.mem.tokenizeScalar(u8, text, ' '); while (tokens.next()) |token| : (i += 1) { const number = try std.fmt.parseInt(u32, token, 10); try result.append(number); } } fn parse_mapping_range(line: []const u8) !MappingRange { const first_space = std.mem.indexOfScalar(u8, line, ' ') orelse @panic("invalid format"); const last_space = std.mem.lastIndexOfScalar(u8, line, ' ') orelse @panic("invalid format"); return MappingRange{ .dest_start = try std.fmt.parseInt(u64, line[0..first_space], 10), .src_start = try std.fmt.parseInt(u64, line[(first_space+1)..last_space], 10), .size = try std.fmt.parseInt(u64, line[(last_space+1)..], 10) }; } fn parse_input(lines: []const []const u8) !Input { var seeds = try SeedsArray.init(0); var mapping_sets: [7]MappingSet = undefined; for (&mapping_sets) |*set| { set.* = try MappingSet.init(0); } const seeds_colon = std.mem.indexOfScalar(u8, lines[0], ':') orelse @panic("invalid format"); try parse_seeds(&seeds, lines[0][(seeds_colon+1)..]); var mappign_set_idx: u32 = 0; for (lines[2..]) |line| { if (std.mem.endsWith(u8, line, "map:")) continue; if (std.mem.eql(u8, line, "")) { mappign_set_idx += 1; continue; } try mapping_sets[mappign_set_idx].append(try parse_mapping_range(line)); } return Input{ .seeds = seeds, .seed_to_soil = mapping_sets[0], .soil_to_fertilizer = mapping_sets[1], .fertilizer_to_water = mapping_sets[2], .water_to_light = mapping_sets[3], .light_to_temperature = mapping_sets[4], .temperature_to_humidity = mapping_sets[5], .humidity_to_location = mapping_sets[6], }; } fn is_in_range(x: u64, range_start: u64, range_size: u64) bool { return range_start <= x and x < (range_start + range_size); } fn apply_mapping(src: u64, mapping_set: *const MappingSet) u64 { for (mapping_set.slice()) |mapping| { if (is_in_range(src, mapping.src_start, mapping.size)) { return mapping.dest_start + (src - mapping.src_start); } } return src; } fn apply_seed_to_location(seed: u64, input: *const Input) u64 { const mapping_sequence = input.get_mappings(); var value = seed; inline for (mapping_sequence) |mapping_set| { value = apply_mapping(value, mapping_set); } return value; } pub fn part1(input: *aoc.Input) !aoc.Result { const parsed = try parse_input(input.lines); var lowest_location: ?u64 = null; for (parsed.seeds.slice()) |seed| { const value = apply_seed_to_location(seed, &parsed); if (lowest_location) |location| { lowest_location = @min(location, value); } else { lowest_location = value; } } return .{ .uint = @intCast(lowest_location.?) }; } fn remove_range(mapping_set: *MappingSet, from: u64, to: u64) !void { for (0.., mapping_set.slice()) |i, *range| { const range_start = range.src_start; const range_end = range.src_start + range.size-1; if (range_start == from and to == range_end) { _ = mapping_set.swapRemove(i); break; } else if (range_start < from and to == range_end) { range.size -= (to-from)+1; break; } else if (range_start == from and to < range_end) { range.src_start = to; range.dest_start += to - range_start; range.size -= (to-from)+1; break; } else if (range_start < from and to < range_end) { range.size = from - range_start + 1; try mapping_set.append(MappingRange{ .src_start = to, .dest_start = range.dest_start + (to - range.src_start), .size = range_end - to + 1 }); break; } } } fn apply_mapping_to_range_set(src_set: *MappingSet, mapping_set: *const MappingSet) !void { var new_src_set = try MappingSet.init(0); var unmapped_ranges = try MappingSet.init(0); try unmapped_ranges.appendSlice(src_set.slice()); for (src_set.slice()) |*src| { for (0.., mapping_set.slice()) |i, mapping| { _ = i; const overlap_start = @max(src.dest_start, mapping.src_start); const overlap_end = @min(src.dest_start+src.size-1, mapping.src_start+mapping.size-1); if (overlap_start > overlap_end) { continue; } const src_start = src.src_start + (overlap_start - src.dest_start); const overlap_size = (overlap_end - overlap_start) + 1; try remove_range(&unmapped_ranges, src_start, src_start+overlap_size-1); try new_src_set.append(MappingRange{ .src_start = src_start, .dest_start = mapping.dest_start + (overlap_start - mapping.src_start), .size = overlap_size, }); } } try new_src_set.appendSlice(unmapped_ranges.slice()); src_set.* = new_src_set; } pub fn part2(input: *aoc.Input) !aoc.Result { const parsed = try parse_input(input.lines); var seed_to_location = try MappingSet.init(0); for (0..(parsed.seeds.len/2)) |i| { const start_seed = parsed.seeds.get(2*i); const seed_count = parsed.seeds.get(2*i+1); try seed_to_location.append(MappingRange{ .src_start = start_seed, .dest_start = start_seed, .size = seed_count }); } inline for (parsed.get_mappings()) |mapping_set| { try apply_mapping_to_range_set(&seed_to_location, mapping_set); } var lowest_location: ?u64 = null; for (seed_to_location.slice()) |range| { if (lowest_location == null) { lowest_location = range.dest_start; } else { lowest_location = @min(lowest_location.?, range.dest_start); } } return .{ .uint = @intCast(lowest_location.?) }; } const example_input = [_][]const u8{ "seeds: 79 14 55 13", "", "seed-to-soil map:", "50 98 2", "52 50 48", "", "soil-to-fertilizer map:", "0 15 37", "37 52 2", "39 0 15", "", "fertilizer-to-water map:", "49 53 8", "0 11 42", "42 0 7", "57 7 4", "", "water-to-light map:", "88 18 7", "18 25 70", "", "light-to-temperature map:", "45 77 23", "81 45 19", "68 64 13", "", "temperature-to-humidity map:", "0 69 1", "1 0 69", "", "humidity-to-location map:", "60 56 37", "56 93 4", }; test "part 1 example" { try aoc.expectAnswerUInt(part1, 35, &example_input); } test "part 2 example" { try aoc.expectAnswerUInt(part2, 46, &example_input); }