aoc/2021/12/src/main.zig

363 lines
10 KiB
Zig

const std = @import("std");
const example =
\\start-A
\\start-b
\\A-c
\\A-b
\\b-d
\\A-end
\\b-end
;
const example2 =
\\dc-end
\\HN-start
\\start-kj
\\dc-start
\\dc-HN
\\LN-dc
\\HN-end
\\kj-sa
\\kj-HN
\\kj-dc
;
const example3 =
\\fs-end
\\he-DX
\\fs-he
\\start-DX
\\pj-DX
\\end-zg
\\zg-sl
\\zg-pj
\\pj-he
\\RW-he
\\fs-DX
\\pj-RW
\\zg-RW
\\start-pj
\\he-WI
\\zg-he
\\pj-fs
\\start-RW
;
const input = @embedFile("../input");
const Graph = struct {
alloc: std.mem.Allocator,
nodes: std.ArrayListUnmanaged(*Node) = .{},
const Self = @This();
pub fn deinit(self: *Self) void {
for (self.nodes.items) |n| {
n.parents.deinit(self.alloc);
n.children.deinit(self.alloc);
self.alloc.destroy(n);
}
self.nodes.deinit(self.alloc);
}
pub fn build_graph(alloc: std.mem.Allocator, text: []const u8) !Self {
var self: Self = .{
.alloc = alloc,
};
var start = try alloc.create(Node);
errdefer alloc.destroy(start);
start.id = "start";
start.children = .{};
start.parents = .{};
var end = try alloc.create(Node);
errdefer alloc.destroy(end);
end.id = "end";
end.children = .{};
end.parents = .{};
// Our input text does not necessarrily contain the Nodes in an order
// that will just assemble into a graph. We temporarily hold an array
// of pointers to all the nodes we're making to facilitate building
// the graph.
try self.nodes.append(self.alloc, start);
try self.nodes.append(self.alloc, end);
var lit = std.mem.tokenize(u8, text, "\n");
while (lit.next()) |line| {
var it = std.mem.tokenize(u8, line, "-");
var first: ?*Node = null;
var second: ?*Node = null;
while (it.next()) |id| {
var n = self.node_exists(id);
if (n == null) {
n = try alloc.create(Node);
errdefer alloc.destroy(n.?);
n.?.id = id;
var big = true;
for (id) |c| {
if (!std.ascii.isUpper(c)) {
big = false;
break;
}
}
n.?.big = big;
n.?.children = .{};
n.?.parents = .{};
try self.nodes.append(self.alloc, n.?);
}
if (first == null) {
first = n;
}
else if (second == null) {
second = n;
}
}
// @LEAK When there is an error, the unmanaged array lists aren't properly freed
//std.log.debug("Adding '{s}' as parent of '{s}'", .{first.?.id, second.?.id});
try second.?.parents.append(self.alloc, first.?);
try first.?.children.append(self.alloc, second.?);
}
//std.debug.assert(start.parent == null);
//std.debug.assert(end.parent != null);
return self;
}
pub fn paths_part1(self: *Self) !Paths {
var paths = try self.node_exists("start").?.walk(alloc, null);
return paths;
}
pub fn node_exists(self: *Self, id: []const u8) ?*Node {
for (self.nodes.items) |n| {
if (std.mem.eql(u8, id, n.id)) {
return n;
}
}
return null;
}
pub fn to_dot(self: *Self, f: std.fs.File) !void {
defer f.close();
_ = try f.writeAll("digraph D {\n");
for (self.nodes.items) |n| {
_ = try f.write(n.id);
_ = try f.write(" [shape=box]\n");
}
for(self.nodes.items) |n| {
if (n.children.items.len > 0) {
_ = try f.write(n.id);
_ = try f.write(" -> {");
for (n.children.items) |c, k| {
if (std.mem.eql(u8, c.id, n.id)) {
continue;
}
_ = try f.write(c.id);
if (k != n.children.items.len - 1) {
_ = try f.write(", ");
}
}
_ = try f.write("}\n");
}
if (n.parents.items.len > 0) {
_ = try f.write(n.id);
_ = try f.write(" -> {");
for (n.parents.items) |c, k| {
if (std.mem.eql(u8, c.id, n.id)) {
continue;
}
_ = try f.write(c.id);
if (k != n.parents.items.len - 1) {
_ = try f.write(", ");
}
}
_ = try f.write("}\n");
}
}
try f.writeAll("}\n");
}
};
const Path = std.ArrayListUnmanaged(*Node);
const Paths = struct {
alloc: std.mem.Allocator,
paths: std.ArrayListUnmanaged(*Path),
const Self = @This(),
pub fn init(alloc: std.mem.Allocator) !Self {
return Self {
.alloc = alloc,
.paths = .{}
};
}
}
const Node = struct {
id: [] const u8,
big: bool = false,
parents: std.ArrayListUnmanaged(*Node) = .{},
children: std.ArrayListUnmanaged(*Node) = .{},
const Self = @This();
fn valid_child_part1(self: *Self, path: std.ArrayList(*Node)) bool {
if (self.big) {
return true;
}
for (path.items) |p| {
if (std.mem.eql(u8, p.id, self.id)) {
return false;
}
}
return true;
}
pub fn walk(self: *Self, alloc: std.mem.Allocator, visited: ?Path) !Paths {
var paths = Path.init(alloc);
// Depth first
if (visited == null) {
var new_path = try alloc.create(Path);
errdefer alloc.destroy(new_path);
try new_path.append(self);
}
else {
try visited.append(alloc, self);
}
return paths;
}
// pub fn paths(self: *Self, alloc: std.mem.Allocator) !std.ArrayList(*Path) {
// var ps = std.ArrayList(*Path).init(alloc);
// if (ps.items.len == 0) {
// var path = try alloc.create(Path);
// errdefer alloc.destroy(path);
// path.* = Path.init(alloc);
// try ps.append(path);
// }
// for (ps.items) |path| {
// try path.append(self);
// }
// if (std.mem.eql(u8, self.id, "end")) {
// return ps;
// }
// return ps;
// }
// pub fn paths_from(self: *Self, alloc: std.mem.Allocator, from: Path) !std.ArrayList(Path) {
// var ps = std.ArrayList(*Path).init(alloc);
// for (self.children.items) |c| {
// if (!c.valid_child_part1(from)) {
// continue;
// }
// var path = alloc.create(std.ArrayList(Path));
// path.* = std.ArrayList(Path).init(alloc);
// for (from.items) |f| {
// try path.append(f);
// }
// try path.append(c);
// try ps.append(path);
// }
// return ps;
// }
};
test "example 1" {
var graph = try Graph.build_graph(std.testing.allocator, example);
defer graph.deinit();
var end = graph.node_exists("end");
var end_paths = try end.?.paths(std.testing.allocator);
for (end_paths.items) |p, k| {
std.log.warn("Path number {}", .{k});
for (p.items) |n| {
std.log.warn("\t{s}", .{n.id});
}
}
// Cleanup
for (end_paths.items) |p| {
p.deinit();
std.testing.allocator.destroy(p);
}
end_paths.deinit();
var paths = try graph.paths_part1();
for (paths.items) |p, k| {
std.log.warn("Path number {}", .{k});
for (p.items) |n| {
std.log.warn("\t{s}", .{n.id});
}
}
for (paths.items) |p| {
p.deinit();
std.testing.allocator.destroy(p);
}
paths.deinit();
}
test "example 1 graph" {
var graph = try Graph.build_graph(std.testing.allocator, example);
defer graph.deinit();
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
var file = try tmp.dir.createFile("example1.dot", .{});
// this closes the file descriptor.
try graph.to_dot(file);
var expected = @embedFile("../examples/1.dot");
var f = try tmp.dir.openFile("example1.dot", .{});
defer f.close();
const actual = try f.readToEndAlloc(std.testing.allocator, std.math.maxInt(u16));
defer std.testing.allocator.free(actual);
try std.testing.expect(std.mem.eql(u8, expected, actual));
}
test "example 2 graph" {
var graph = try Graph.build_graph(std.testing.allocator, example2);
defer graph.deinit();
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
var file = try tmp.dir.createFile("example2.dot", .{});
// this closes the file descriptor.
try graph.to_dot(file);
var expected = @embedFile("../examples/2.dot");
var f = try tmp.dir.openFile("example2.dot", .{});
defer f.close();
const actual = try f.readToEndAlloc(std.testing.allocator, std.math.maxInt(u16));
defer std.testing.allocator.free(actual);
try std.testing.expect(std.mem.eql(u8, expected, actual));
}
test "example 3 graph" {
var graph = try Graph.build_graph(std.testing.allocator, example3);
defer graph.deinit();
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
var file = try tmp.dir.createFile("example3.dot", .{});
// this closes the file descriptor.
try graph.to_dot(file);
var expected = @embedFile("../examples/3.dot");
var f = try tmp.dir.openFile("example3.dot", .{});
defer f.close();
const actual = try f.readToEndAlloc(std.testing.allocator, std.math.maxInt(u16));
defer std.testing.allocator.free(actual);
try std.testing.expect(std.mem.eql(u8, expected, actual));
}
pub fn main() anyerror!void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const alloc = arena.allocator();
// Read our input
var graph = try Graph.build_graph(alloc, input);
defer graph.deinit();
var f = try std.fs.cwd().createFile("graph.dot", .{});
try graph.to_dot(f);
}