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});
}