From 45eb3b2cde83fa7aaa8d9b6ce92bb7b5c1275a10 Mon Sep 17 00:00:00 2001 From: Jeremy Kaplan Date: Wed, 25 Dec 2024 02:44:40 -0800 Subject: [PATCH] 2024 Day 23 Part 2 --- 2024/src/aoc.zig | 42 +++++++++++++++++ 2024/src/day23.zig | 115 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+) diff --git a/2024/src/aoc.zig b/2024/src/aoc.zig index 19e8b68..cfdbccc 100644 --- a/2024/src/aoc.zig +++ b/2024/src/aoc.zig @@ -48,6 +48,12 @@ pub fn AutoHashSet(comptime T: type) type { self.map.deinit(); } + pub fn clone(self: Self) !Self { + return .{ + .map = try self.map.clone(), + }; + } + pub fn put(self: *Self, v: T) !void { return self.map.put(v, .{}); } @@ -66,6 +72,10 @@ pub fn AutoHashSet(comptime T: type) type { return self.map.count(); } + pub fn empty(self: Self) bool { + return self.count() == 0; + } + pub fn pop(self: *Self) ?T { var it = self.iterator(); const key_ptr = it.next() orelse return null; @@ -77,6 +87,38 @@ pub fn AutoHashSet(comptime T: type) type { pub fn remove(self: *Self, v: T) bool { return self.map.remove(v); } + + pub fn of(allocator: std.mem.Allocator, v: T) !Self { + var s = Self.init(allocator); + try s.put(v); + return s; + } + + pub fn with(self: Self, v: T) !Self { + var n = try self.clone(); + try n.put(v); + return n; + } + + pub fn @"union"(self: Self, other: Self) !Self { + var u = self.clone(); + var vs = other.iterator(); + while (vs.next()) |v| { + try u.put(v); + } + return u; + } + + pub fn intersect(self: Self, other: Self) !Self { + var ix = try self.clone(); + var vs = self.iterator(); + while (vs.next()) |v| { + if (!other.contains(v.*)) { + _ = ix.remove(v.*); + } + } + return ix; + } }; } diff --git a/2024/src/day23.zig b/2024/src/day23.zig index 65d3f5f..0cf208f 100644 --- a/2024/src/day23.zig +++ b/2024/src/day23.zig @@ -23,6 +23,11 @@ pub fn main() !void { try stdout.print("{}\n", .{try part1(allocator, edges.items)}); try bw.flush(); + + var password = try part2(allocator, edges.items); + defer password.deinit(); + try stdout.print("{s}\n", .{password.items}); + try bw.flush(); } // I really don't want to have to figure out string hashing again, so turn the @@ -42,6 +47,13 @@ fn nameStartsWith(n: Name, c: u8) bool { return n >> 8 == c; } +fn formatName(n: Name) [2]u8 { + return [2]u8{ + @intCast(n >> 8), + @intCast(n & 0xff), + }; +} + const Edge = struct { a: Name, b: Name, @@ -110,6 +122,28 @@ const Graph = struct { } return null; } + + fn popNode(self: *Graph) ?struct { Name, Set(Name) } { + const src = self.nodes.pop() orelse return null; + return .{ src, self.removeEdgeReferences(src) }; + } + + fn removeNode(self: *Graph, src: Name) ?Set(Name) { + _ = self.nodes.remove(src) or return null; + return self.removeEdgeReferences(src); + } + + fn removeEdgeReferences(self: *Graph, src: Name) Set(Name) { + var entry = self.edges.fetchRemove(src).?; + var it = entry.value.iterator(); + while (it.next()) |dst| { + if (self.edges.getPtr(dst.*)) |set| { + _ = set.remove(src); + } + } + + return entry.value; + } }; fn part1(allocator: Allocator, edges: []const Edge) !usize { @@ -139,3 +173,84 @@ fn part1(allocator: Allocator, edges: []const Edge) !usize { return lans.count(); } + +fn part2(allocator: Allocator, edges: []const Edge) !List(u8) { + var graph = try Graph.init(allocator, edges); + defer graph.deinit(); + + var incl = Set(Name).init(allocator); + defer incl.deinit(); + + var prop = try graph.nodes.clone(); + defer prop.deinit(); + + var excl = Set(Name).init(allocator); + defer excl.deinit(); + + var lan = Set(Name).init(allocator); + try findCliques(graph, incl, prop, excl, &lan); + defer lan.deinit(); + + return try formatPassword(allocator, lan); +} + +// https://en.wikipedia.org/wiki/Bron%E2%80%93Kerbosch_algorithm +fn findCliques(graph: Graph, included: Set(Name), proposed: Set(Name), excluded: Set(Name), best: *Set(Name)) !void { + if (proposed.empty() and excluded.empty() and included.count() > best.count()) { + best.deinit(); + best.* = try included.clone(); + } + + var p = try proposed.clone(); + defer p.deinit(); + + var x = try excluded.clone(); + defer x.deinit(); + + var it = proposed.iterator(); + while (it.next()) |v| { + const neighbors = graph.edges.get(v.*).?; + + var incl = try included.with(v.*); + defer incl.deinit(); + + var prop = try p.intersect(neighbors); + defer prop.deinit(); + + var excl = try x.intersect(neighbors); + defer excl.deinit(); + + try findCliques(graph, incl, prop, excl, best); + + _ = p.remove(v.*); + try x.put(v.*); + } +} + +fn formatPassword(allocator: Allocator, lan: Set(Name)) !List(u8) { + var names = List(Name).init(allocator); + defer names.deinit(); + { + var it = lan.iterator(); + while (it.next()) |n| { + try names.append(n.*); + } + } + + std.mem.sort(Name, names.items, {}, comptime std.sort.asc(Name)); + + var buf = List(u8).init(allocator); + var w = buf.writer(); + + if (names.items.len > 0) { + try w.print("{s}", .{formatName(names.items[0])}); + } + + if (names.items.len > 1) { + for (names.items[1..]) |n| { + try w.print(",{s}", .{formatName(n)}); + } + } + + return buf; +}