256 lines
6.5 KiB
Zig
256 lines
6.5 KiB
Zig
const std = @import("std");
|
|
const aoc = @import("aoc.zig");
|
|
|
|
const Allocator = std.mem.Allocator;
|
|
const List = std.ArrayList;
|
|
const Map = std.AutoHashMap;
|
|
const Set = aoc.AutoHashSet;
|
|
const PriorityQueue = std.PriorityQueue;
|
|
|
|
pub fn main() !void {
|
|
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{};
|
|
defer _ = gpa.deinit();
|
|
const allocator = gpa.allocator();
|
|
|
|
const text = try aoc.readAll(allocator, "input/day23.txt");
|
|
defer allocator.free(text);
|
|
|
|
var edges = try aoc.parseAll(Edge, allocator, text, "\n");
|
|
defer edges.deinit();
|
|
|
|
var bw = std.io.bufferedWriter(std.io.getStdOut().writer());
|
|
const stdout = bw.writer();
|
|
|
|
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
|
|
// strings into non-slice values. ("pre-hash" them?)
|
|
const Name = u16;
|
|
|
|
fn parseName(s: []const u8) Name {
|
|
if (s.len != 2) unreachable;
|
|
|
|
const hi: u16 = @intCast(s[0]);
|
|
const lo: u16 = @intCast(s[1]);
|
|
|
|
return (hi << 8) + lo;
|
|
}
|
|
|
|
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,
|
|
|
|
pub fn parse(_: Allocator, line: []const u8) !Edge {
|
|
var parts = std.mem.tokenizeScalar(u8, line, '-');
|
|
const a = parts.next().?;
|
|
const b = parts.next().?;
|
|
|
|
return Edge{
|
|
.a = parseName(a),
|
|
.b = parseName(b),
|
|
};
|
|
}
|
|
};
|
|
|
|
const Graph = struct {
|
|
allocator: Allocator,
|
|
nodes: Set(Name),
|
|
edges: Map(Name, Set(Name)),
|
|
|
|
fn init(allocator: Allocator, edges: []const Edge) !Graph {
|
|
var g = Graph{
|
|
.allocator = allocator,
|
|
.nodes = Set(Name).init(allocator),
|
|
.edges = Map(Name, Set(Name)).init(allocator),
|
|
};
|
|
|
|
for (edges) |edge| {
|
|
try g.nodes.put(edge.a);
|
|
try g.nodes.put(edge.b);
|
|
|
|
try g.addEdge(edge.a, edge.b);
|
|
try g.addEdge(edge.b, edge.a);
|
|
}
|
|
|
|
return g;
|
|
}
|
|
|
|
fn deinit(self: *Graph) void {
|
|
var it = self.edges.valueIterator();
|
|
while (it.next()) |set| set.deinit();
|
|
|
|
self.edges.deinit();
|
|
self.nodes.deinit();
|
|
}
|
|
|
|
fn addEdge(self: *Graph, src: Name, dst: Name) !void {
|
|
var res = try self.edges.getOrPut(src);
|
|
if (!res.found_existing) {
|
|
res.value_ptr.* = Set(Name).init(self.allocator);
|
|
}
|
|
try res.value_ptr.put(dst);
|
|
}
|
|
|
|
fn hasEdge(self: Graph, src: Name, dst: Name) bool {
|
|
if (self.edges.get(src)) |set| {
|
|
return set.contains(dst);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
fn neighbors(self: Graph, src: Name) ?Set(Name).Iterator {
|
|
if (self.edges.get(src)) |set| {
|
|
return set.iterator();
|
|
}
|
|
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 {
|
|
var graph = try Graph.init(allocator, edges);
|
|
defer graph.deinit();
|
|
|
|
var lans = Set([3]Name).init(allocator);
|
|
defer lans.deinit();
|
|
|
|
var nodes = graph.nodes.iterator();
|
|
while (nodes.next()) |n| {
|
|
if (!nameStartsWith(n.*, 't')) continue;
|
|
|
|
var us = graph.neighbors(n.*).?;
|
|
while (us.next()) |u| {
|
|
var vs = graph.neighbors(u.*).?;
|
|
while (vs.next()) |v| {
|
|
if (graph.hasEdge(v.*, n.*)) {
|
|
var lan = [3]Name{ n.*, u.*, v.* };
|
|
std.mem.sort(Name, &lan, {}, comptime std.sort.asc(Name));
|
|
|
|
try lans.put(lan);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|