const std = @import("std"); const builtin = @import("builtin"); const Cave = struct { links: std.ArrayList([]const u8), }; const CaveSystem = struct { system: std.StringHashMap(Cave), allocator: std.mem.Allocator, pub fn init(allocator: std.mem.Allocator) !CaveSystem { return CaveSystem{ .system = std.StringHashMap(Cave).init(allocator), .allocator = allocator, }; } pub fn deinit(self: *CaveSystem) void { var it = self.system.iterator(); while (it.next()) |entry| { var value = entry.value_ptr.*; for (value.links.items) |item| { self.allocator.free(item); } value.links.deinit(); self.allocator.free(entry.key_ptr.*); } self.system.deinit(); } }; pub fn readInput(allocator: std.mem.Allocator, reader: anytype) !CaveSystem { var result = try CaveSystem.init(allocator); while (true) { var line: []u8 = undefined; if (builtin.os.tag == .windows and !builtin.is_test) { // NOTE: Read another byte on windows due to two-byte eol. // NOTE: Check if in testing since tests only add single-byte eol in multiline strings. line = reader.readUntilDelimiterAlloc(allocator, '\r', 512) catch break; _ = try reader.readByte(); } else { line = reader.readUntilDelimiterAlloc(allocator, '\n', 512) catch break; } defer allocator.free(line); var it = std.mem.split(u8, line, "-"); var key = try allocator.dupe(u8, it.next().?); var free_the_key = false; var value = try allocator.dupe(u8, it.next().?); var res = try result.system.getOrPut(key); if (!res.found_existing) { var list = std.ArrayList([]const u8).init(allocator); res.value_ptr.* = Cave{ .links = list, }; } else { free_the_key = true; } try res.value_ptr.*.links.append(try allocator.dupe(u8, value)); res = try result.system.getOrPut(value); if (!res.found_existing) { var list = std.ArrayList([]const u8).init(allocator); res.value_ptr.* = Cave{ .links = list, }; } else { allocator.free(value); } try res.value_ptr.*.links.append(try allocator.dupe(u8, key)); if (free_the_key) { allocator.free(key); } } return result; } fn walkCaves(current_cave: []const u8, cave_system: *CaveSystem, visited_small_caves: *std.StringHashMap(u32), check_twice: bool, did_visit_twice: bool) u32 { if (std.mem.eql(u8, current_cave, "end")) { return 1; } var result: u32 = 0; var is_lower = true; for (current_cave) |c| { if (!std.ascii.isLower(c)) { is_lower = false; break; } } var checked_twice = did_visit_twice; if (is_lower) { if (visited_small_caves.contains(current_cave)) { checked_twice = true; } visited_small_caves.put(current_cave, 1) catch unreachable; } if (cave_system.system.get(current_cave)) |this_cave| { // std.log.debug("at: {s}", .{current_cave}); for (this_cave.links.items) |cave| { if (std.mem.eql(u8, cave, "start")) { continue; } // std.log.debug("visiting: {s}", .{cave}); if (check_twice) { if (visited_small_caves.contains(cave) and checked_twice) { continue; } } else { if (visited_small_caves.contains(cave)) { continue; } } var visited_clone = visited_small_caves.clone() catch unreachable; defer visited_clone.deinit(); result += walkCaves(cave, cave_system, &visited_clone, check_twice, checked_twice); } } return result; } pub fn task1(allocator: std.mem.Allocator, input: *CaveSystem) !u32 { var visited = std.StringHashMap(u32).init(allocator); defer visited.deinit(); return walkCaves("start", input, &visited, false, false); } pub fn task2(allocator: std.mem.Allocator, input: *CaveSystem) !u32 { var visited = std.StringHashMap(u32).init(allocator); defer visited.deinit(); return walkCaves("start", input, &visited, true, false); } pub fn main() !void { var allocator = std.heap.page_allocator; const file = try std.fs.cwd().openFile("input.txt", .{ .read = true }); defer file.close(); var input = try readInput(allocator, file.reader()); defer input.deinit(); const task_1_result = task1(allocator, &input); std.log.info("Task 1 result: {}", .{task_1_result}); const task_2_result = task2(allocator, &input); std.log.info("Task 2 result: {}", .{task_2_result}); }