const std = @import("std"); const Range = struct { destination: u64, source: u64, length: u64 }; const Map = struct { from: []const u8, to: []const u8, ranges: std.ArrayList(Range) }; pub fn main() !void { const content = @embedFile("data.txt"); var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const allocator = gpa.allocator(); // split the file content on empty lines var sections = std.mem.split(u8, content, "\n\n"); // parse out the first line, collecting seeds into an ArrayList var seedSection = std.mem.split(u8, sections.next().?, " "); var seeds = std.ArrayList(u64).init(allocator); defer seeds.deinit(); _ = seedSection.next(); while (seedSection.next()) |s| { try seeds.append(try std.fmt.parseInt(u64, s, 10)); } var maps = std.ArrayList(Map).init(allocator); defer maps.deinit(); // iterate over each section, parse into Map structs, append them onto the maps ArrayList while (sections.next()) |s| { var lines = std.mem.split(u8, s, "\n"); var firstLine = lines.next().?; var words = std.mem.split(u8, firstLine, " "); // grab the first "word" of the first line, and split it on - var parts = std.mem.split(u8, words.next().?, "-"); const from = parts.next().?; _ = parts.next(); const to = parts.next().?; var ranges = std.ArrayList(Range).init(allocator); // TODO: do I need to deinit ranges... somewhere? while (lines.next()) |line| { if (line.len == 0) { continue; } var nums = std.mem.split(u8, line, " "); var destination = try std.fmt.parseInt(u64, nums.next().?, 10); var source = try std.fmt.parseInt(u64, nums.next().?, 10); var length = try std.fmt.parseInt(u64, nums.next().?, 10); const range = Range{ .destination = destination, .source = source, .length = length }; try ranges.append(range); } const map = Map{ .from = from, .to = to, .ranges = ranges }; try maps.append(map); } var lowestLocation: ?u64 = null; for (seeds.items) |seed| { // loop starting from the "seed" map until we have a location var from: []const u8 = "seed"; var val = seed; while (!std.mem.eql(u8, from, "location")) { const map = findMap(from, maps).?; val = findNewValue(val, map); from = map.to; } if (lowestLocation) |l| { if (val < l) { lowestLocation = val; } } else { lowestLocation = val; } } std.debug.print("Part 1 {?d}\n", .{lowestLocation}); // Part 2 // Can we go backwards? var i: u64 = 0; outer: while (true) { var to: []const u8 = "location"; var val = i; while (!std.mem.eql(u8, to, "seed")) { const map = findMapReverse(to, maps).?; val = findNewValueReverse(val, map); to = map.from; } var idx: u8 = 0; while (idx < seeds.items.len) : (idx += 2) { const lower = seeds.items[idx]; const upper = lower + seeds.items[idx + 1]; if (val >= lower and val < upper) { break :outer; } } i += 1; } std.debug.print("Part 2 {?d}\n", .{i}); } // given the "from" type, find the map that we should follow fn findMap(from: []const u8, maps: std.ArrayList(Map)) ?Map { for (maps.items) |map| { if (std.mem.eql(u8, map.from, from)) { return map; } } return null; } fn findNewValue(val: u64, map: Map) u64 { for (map.ranges.items) |r| { if (val >= r.source and val < r.source + r.length) { return r.destination + val - r.source; } } // if no range fits, return the original value return val; } // given the "to" type, find the map that we should follow fn findMapReverse(to: []const u8, maps: std.ArrayList(Map)) ?Map { for (maps.items) |map| { if (std.mem.eql(u8, map.to, to)) { return map; } } return null; } fn findNewValueReverse(val: u64, map: Map) u64 { for (map.ranges.items) |r| { if (val >= r.destination and val < r.destination + r.length) { return r.source + val - r.destination; } } // if no range fits, return the original value return val; }