363 lines
10 KiB
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);
|
|
|
|
}
|