diff --git a/src/day25.zig b/src/day25.zig new file mode 100644 index 0000000..0882c2c --- /dev/null +++ b/src/day25.zig @@ -0,0 +1,262 @@ +const aoc = @import("./aoc.zig"); +const std = @import("std"); +const Allocator = std.mem.Allocator; + +const max_connections = 16; +const ComponentName = std.BoundedArray(u8, 3); +const WiringComponent = struct { + name: ComponentName, + connected_to: std.BoundedArray(ComponentName, max_connections), + + pub fn format( + self: WiringComponent, + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + _ = fmt; + _ = options; + + try writer.print("{s}:", .{ self.name.constSlice() }); + for (self.connected_to.constSlice()) |other_name| { + try writer.print(" {s}", .{ other_name.constSlice() }); + } + } +}; + +fn parseComponent(line: []const u8) !WiringComponent { + const name_separator = std.mem.indexOfScalar(u8, line, ':') orelse return error.NoSeparator; + var connected_to = std.BoundedArray(ComponentName, max_connections).init(0) catch unreachable; + + var connected_iter = std.mem.splitScalar(u8, line[(name_separator+1)..], ' '); + while (connected_iter.next()) |part| { + const part_trimmed = std.mem.trim(u8, part, " "); + if (part_trimmed.len == 0) continue; + try connected_to.append(try ComponentName.fromSlice(part_trimmed)); + } + + return WiringComponent{ + .name = try ComponentName.fromSlice(line[0..name_separator]), + .connected_to = connected_to + }; +} + +fn parseInput(allocator: Allocator, lines: []const []const u8) !std.ArrayList(WiringComponent) { + var components = std.ArrayList(WiringComponent).init(allocator); + errdefer components.deinit(); + + for (lines) |line| { + try components.append(try parseComponent(line)); + } + + return components; +} + +const ComponentId = u11; +const ComponentGraph = struct { + const ConnectionList = std.BoundedArray(ComponentId, max_connections); + + allocator: Allocator, + names: std.ArrayList(ComponentName), + connections: []ConnectionList, + + fn init(allocator: Allocator, components: []WiringComponent) !ComponentGraph { + var names = std.ArrayList(ComponentName).init(allocator); + errdefer names.deinit(); + + for (components) |component| { + if (getComponentId(names.items, component.name.constSlice()) == null) { + try names.append(component.name); + } + + for (component.connected_to.constSlice()) |other_name| { + if (getComponentId(names.items, other_name.constSlice()) == null) { + try names.append(other_name); + } + } + } + + const connections = try allocator.alloc(ConnectionList, names.items.len); + errdefer allocator.free(connections); + for (connections) |*connection| { + connection.* = ConnectionList.init(0) catch unreachable; + } + + const self = ComponentGraph{ + .allocator = allocator, + .names = names, + .connections = connections + }; + + for (components) |component| { + const id: ComponentId = getComponentId(names.items, component.name.constSlice()) orelse unreachable; + for (component.connected_to.constSlice()) |other_name| { + const other_id = getComponentId(names.items, other_name.constSlice()) orelse unreachable; + + if (std.mem.indexOfScalar(ComponentId, connections[id].constSlice(), other_id) == null) { + try connections[id].append(other_id); + } + if (std.mem.indexOfScalar(ComponentId, connections[other_id].constSlice(), id) == null) { + try connections[other_id].append(id); + } + } + } + + return self; + } + + fn getComponentId(names: []ComponentName, target: []const u8) ?ComponentId { + for (0.., names) |id, name| { + if (std.mem.eql(u8, target, name.constSlice())) { + return @intCast(id); + } + } + return null; + } + + fn getName(self: ComponentGraph, id: ComponentId) []const u8 { + return self.names.items[id].constSlice(); + } + + fn deinit(self: ComponentGraph) void { + self.names.deinit(); + self.allocator.free(self.connections); + } +}; + +fn findPath(allocator: Allocator, used_edges: []bool, graph: ComponentGraph, start: ComponentId, end: ComponentId) !?std.ArrayList(ComponentId) { + const parent = try allocator.alloc(?ComponentId, graph.names.items.len); + defer allocator.free(parent); + @memset(parent, null); + + var stack = std.ArrayList(ComponentId).init(allocator); + defer stack.deinit(); + + try stack.append(start); + parent[start] = start; + + const node_count = graph.names.items.len; + while (stack.popOrNull()) |node_id| { + if (node_id == end) { + break; + } + + for (graph.connections[node_id].constSlice()) |other_node_id| { + const visited_index = node_count * node_id + other_node_id; + if (parent[other_node_id] == null and !used_edges[visited_index]) { + parent[other_node_id] = node_id; + try stack.append(other_node_id); + } + } + } + + if (parent[end] == null) { + return null; + } + + var result = std.ArrayList(ComponentId).init(allocator); + errdefer result.deinit(); + + try result.append(end); + + var current: ?ComponentId = end; + while (true) { + current = parent[current.?]; + try result.insert(0, current.?); + if (current.? == start) break; + } + + return result; +} + +fn countPaths(allocator: Allocator, graph: ComponentGraph, used_edges: []bool, from: ComponentId, to: ComponentId, max_paths: u32) !u32 { + const node_count = graph.names.items.len; + + var path_count: u32 = 0; + while (path_count < max_paths) { + const maybe_path = try findPath(allocator, used_edges, graph, from, to); + if (maybe_path == null) break; + + const path = maybe_path.?; + defer path.deinit(); + + for (0..(path.items.len-1)) |i| { + const segment_from = path.items[i]; + const segment_to = path.items[i+1]; + used_edges[segment_from * node_count + segment_to] = true; + } + + path_count += 1; + } + + return path_count; +} + +fn getIslandSize(allocator: Allocator, graph: ComponentGraph, used_edges: []bool, from: ComponentId) !usize { + const visisted = try allocator.alloc(bool, graph.names.items.len); + defer allocator.free(visisted); + @memset(visisted, false); + + var stack = std.ArrayList(ComponentId).init(allocator); + defer stack.deinit(); + + try stack.append(from); + visisted[from] = true; + + const node_count = graph.names.items.len; + while (stack.popOrNull()) |node_id| { + for (graph.connections[node_id].constSlice()) |other_node_id| { + const visited_index = node_count * node_id + other_node_id; + if (!visisted[other_node_id] and !used_edges[visited_index]) { + visisted[other_node_id] = true; + try stack.append(other_node_id); + } + } + } + + return std.mem.count(bool, visisted, &[_]bool{ true }); +} + +pub fn part1(input: *aoc.Input) !aoc.Result { + const allocator = input.allocator; + const components = try parseInput(allocator, input.lines); + defer components.deinit(); + + const graph = try ComponentGraph.init(allocator, components.items); + defer graph.deinit(); + + // std.debug.print("Graph:\n", .{}); + // for (0.., graph.connections) |node_id, connections| { + // std.debug.print("{s} ->", .{graph.getName(@intCast(node_id))}); + // for (connections.constSlice()) |connection| { + // std.debug.print(" {s}", .{graph.getName(@intCast(connection))}); + // } + // std.debug.print("\n", .{}); + // } + + var source_node: ?ComponentId = null; + var target_node: ?ComponentId = null; + + const node_count = graph.names.items.len; + const used_edges = try allocator.alloc(bool, node_count * node_count); + defer allocator.free(used_edges); + + for (0..(node_count-1)) |i| { + for ((i+1)..node_count) |j| { + @memset(used_edges, false); + const path_count = try countPaths(allocator, graph, used_edges, @intCast(i), @intCast(j), 4); + if (path_count == 3) { + source_node = @intCast(i); + target_node = @intCast(j); + break; + } + } + } + + if (source_node == null or target_node == null) return error.NoSolution; + + const island_size1 = try getIslandSize(allocator, graph, used_edges, source_node.?); + const island_size2 = node_count - island_size1; + + return .{ .uint = @intCast(island_size1 * island_size2) }; +} diff --git a/src/main.zig b/src/main.zig index 248f968..b07efb6 100644 --- a/src/main.zig +++ b/src/main.zig @@ -45,6 +45,7 @@ const Days = [_]aoc.Day{ create_day(@import("./day22.zig")), create_day(@import("./day23.zig")), create_day(@import("./day24.zig")), + create_day(@import("./day25.zig")), }; fn kilobytes(count: u32) u32 { @@ -212,4 +213,5 @@ test { _ = @import("./day22.zig"); _ = @import("./day23.zig"); _ = @import("./day24.zig"); + _ = @import("./day25.zig"); }