Add topological sort and cycle detection
This commit is contained in:
+149
-29
@@ -6,6 +6,8 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
wp_intern WpLogger _log = { .name = wpStr8LitRo("dag_man") };
|
||||||
|
|
||||||
/* ---------------------------------------------------------------------------
|
/* ---------------------------------------------------------------------------
|
||||||
* Pool allocator (arena-backed, intrusive free list)
|
* Pool allocator (arena-backed, intrusive free list)
|
||||||
* -------------------------------------------------------------------------*/
|
* -------------------------------------------------------------------------*/
|
||||||
@@ -63,6 +65,7 @@ typedef struct {
|
|||||||
u64 index;
|
u64 index;
|
||||||
u64 generation;
|
u64 generation;
|
||||||
} PrNodeId;
|
} PrNodeId;
|
||||||
|
typedef PrNodeId *PrNodeIdArray;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
union {
|
union {
|
||||||
@@ -124,6 +127,11 @@ wp_intern void prGraphInit(PrGraph *graph, WpAllocator *allocator, u64 capacity)
|
|||||||
graph->max_count_ever = 0;
|
graph->max_count_ever = 0;
|
||||||
graph->count = 0;
|
graph->count = 0;
|
||||||
|
|
||||||
|
if (!graph->nodes || !graph->vertices) {
|
||||||
|
graph->capacity = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
prPoolInit(&graph->vertex_pool, allocator, sizeof(PrEdgeVertex));
|
prPoolInit(&graph->vertex_pool, allocator, sizeof(PrEdgeVertex));
|
||||||
memset(graph->nodes, 0, capacity * sizeof(PrNode));
|
memset(graph->nodes, 0, capacity * sizeof(PrNode));
|
||||||
memset(graph->vertices, 0, capacity * sizeof(PrEdgeVertex));
|
memset(graph->vertices, 0, capacity * sizeof(PrEdgeVertex));
|
||||||
@@ -134,40 +142,73 @@ wp_intern void prGraphInit(PrGraph *graph, WpAllocator *allocator, u64 capacity)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wp_intern void prGraphAddEdge(PrGraph *graph, PrNodeId from, PrNodeId to) {
|
/* ---------------------------------------------------------------------------
|
||||||
if (!prGraphIsActiveNode(graph, from) || !prGraphIsActiveNode(graph, to)) { return; }
|
* Kahn's algorithm — topological sort / cycle detection
|
||||||
if (from.index == to.index) { return; }
|
*
|
||||||
|
* Returns a WpArray of PrNodeId (sorted topologically). If the result count
|
||||||
|
* is less than graph->count, the graph contains a cycle.
|
||||||
|
* -------------------------------------------------------------------------*/
|
||||||
|
|
||||||
PrEdgeVertex *edge = (PrEdgeVertex *)prPoolAlloc(&graph->vertex_pool);
|
wp_intern PrNodeId *prGraphTopologicalSort(PrGraph *graph, const WpAllocator *allocator) {
|
||||||
|
if (graph->count == 0) { return NULL; }
|
||||||
|
if (!graph->nodes || graph->capacity == 0) { return NULL; }
|
||||||
|
|
||||||
PrEdgeVertex *src = &graph->vertices[from.index];
|
PrNodeIdArray result = wpArrayAllocCapacity(PrNodeId, allocator, graph->count, WP_ARRAY_INIT_NONE);
|
||||||
PrEdgeVertex *dst = &graph->vertices[to.index];
|
if (!result) { return NULL; }
|
||||||
|
|
||||||
edge->source = from;
|
WpAllocator local_arena = wpMemArenaAllocatorInitZero(KiB(16));
|
||||||
edge->target = to;
|
|
||||||
edge->next_forward = src->next_forward;
|
WpU64Array in_degree = wpArrayAllocCapacity(u64, &local_arena, graph->capacity, WP_ARRAY_INIT_FILLED);
|
||||||
edge->next_backward = dst->next_backward;
|
if (!in_degree) { return result; }
|
||||||
src->next_forward = edge;
|
memset(in_degree, 0, wpArrayCapacity(in_degree) * sizeof(u64));
|
||||||
dst->next_backward = edge;
|
|
||||||
|
for (u64 i = 0; i < graph->max_count_ever; i++) {
|
||||||
|
PrNodeId id = prGraphGetNode(graph, i);
|
||||||
|
if (!prGraphIsActiveNode(graph, id)) { continue; }
|
||||||
|
|
||||||
|
PrEdgeVertex *vertex = &graph->vertices[i];
|
||||||
|
PrEdgeVertex *curr = vertex->next_forward;
|
||||||
|
while (curr) {
|
||||||
|
in_degree[curr->target.index]++;
|
||||||
|
curr = curr->next_forward;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WpQueue queue = wpQueueAlloc(u64, &local_arena, graph->count);
|
||||||
|
|
||||||
|
for (u64 i = 0; i < graph->max_count_ever; i++) {
|
||||||
|
PrNodeId id = prGraphGetNode(graph, i);
|
||||||
|
if (!prGraphIsActiveNode(graph, id)) { continue; }
|
||||||
|
if (in_degree[i] == 0) {
|
||||||
|
wpQueuePush(u64, &queue, &i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (queue.count > 0) {
|
||||||
|
u64 *node_idx = wpQueuePop(u64, &queue);
|
||||||
|
if (!node_idx) { break; }
|
||||||
|
|
||||||
|
PrNodeId id = prGraphGetNode(graph, *node_idx);
|
||||||
|
wpArrayAppendCapped(PrNodeId, result, &id);
|
||||||
|
|
||||||
|
PrEdgeVertex *vertex = &graph->vertices[*node_idx];
|
||||||
|
PrEdgeVertex *curr = vertex->next_forward;
|
||||||
|
while (curr) {
|
||||||
|
u64 target_idx = curr->target.index;
|
||||||
|
if (in_degree[target_idx] > 0) {
|
||||||
|
in_degree[target_idx]--;
|
||||||
|
if (in_degree[target_idx] == 0) {
|
||||||
|
wpQueuePush(u64, &queue, &target_idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
curr = curr->next_forward;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
wp_intern PrNodeId prGraphAddNode(PrGraph *graph, PrNodeType type) {
|
/* --- edge management -------------------------------------------------- */
|
||||||
u64 idx = graph->free_head;
|
|
||||||
if (idx == INVALID_NODE_INDEX) { return INVALID_NODE_ID; }
|
|
||||||
|
|
||||||
PrNode *node = &graph->nodes[idx];
|
|
||||||
|
|
||||||
graph->free_head = node->next_free;
|
|
||||||
node->next_free = INVALID_NODE_INDEX;
|
|
||||||
node->type = type;
|
|
||||||
memset(&node->params, 0, sizeof(node->params));
|
|
||||||
|
|
||||||
graph->count++;
|
|
||||||
|
|
||||||
if (idx + 1 > graph->max_count_ever) { graph->max_count_ever = idx + 1; }
|
|
||||||
|
|
||||||
return (PrNodeId){ .index = idx, .generation = node->generation };
|
|
||||||
}
|
|
||||||
|
|
||||||
wp_intern void _unlinkForward(PrGraph *graph, PrNodeId from, PrEdgeVertex *edge) {
|
wp_intern void _unlinkForward(PrGraph *graph, PrNodeId from, PrEdgeVertex *edge) {
|
||||||
if (!prGraphIsActiveNode(graph, from)) { return; }
|
if (!prGraphIsActiveNode(graph, from)) { return; }
|
||||||
@@ -211,6 +252,55 @@ wp_intern void _unlinkBackward(PrGraph *graph, PrNodeId to, PrEdgeVertex *edge)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wp_intern void prGraphAddEdge(PrGraph *graph, PrNodeId from, PrNodeId to) {
|
||||||
|
if (!prGraphIsActiveNode(graph, from) || !prGraphIsActiveNode(graph, to)) { return; }
|
||||||
|
if (from.index == to.index) { return; }
|
||||||
|
|
||||||
|
PrEdgeVertex *edge = (PrEdgeVertex *)prPoolAlloc(&graph->vertex_pool);
|
||||||
|
if (!edge) { return; }
|
||||||
|
|
||||||
|
edge->source = from;
|
||||||
|
edge->target = to;
|
||||||
|
|
||||||
|
/* Link into adjacency chains */
|
||||||
|
PrEdgeVertex *src = &graph->vertices[from.index];
|
||||||
|
PrEdgeVertex *dst = &graph->vertices[to.index];
|
||||||
|
edge->next_forward = src->next_forward;
|
||||||
|
edge->next_backward = dst->next_backward;
|
||||||
|
src->next_forward = edge;
|
||||||
|
dst->next_backward = edge;
|
||||||
|
|
||||||
|
/* Check whether the new edge created a cycle */
|
||||||
|
WpAllocator scratch = wpMemArenaAllocatorInitZero(KiB(16));
|
||||||
|
PrNodeIdArray sorted = prGraphTopologicalSort(graph, &scratch);
|
||||||
|
u64 sorted_count = sorted ? wpArrayCount(sorted) : 0;
|
||||||
|
if (sorted_count < graph->count) {
|
||||||
|
_unlinkForward(graph, from, edge);
|
||||||
|
_unlinkBackward(graph, to, edge);
|
||||||
|
prPoolFree(&graph->vertex_pool, edge);
|
||||||
|
}
|
||||||
|
|
||||||
|
wpMemArenaAllocatorDestroy(&scratch);
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_intern PrNodeId prGraphAddNode(PrGraph *graph, PrNodeType type) {
|
||||||
|
u64 idx = graph->free_head;
|
||||||
|
if (idx == INVALID_NODE_INDEX) { return INVALID_NODE_ID; }
|
||||||
|
|
||||||
|
PrNode *node = &graph->nodes[idx];
|
||||||
|
|
||||||
|
graph->free_head = node->next_free;
|
||||||
|
node->next_free = INVALID_NODE_INDEX;
|
||||||
|
node->type = type;
|
||||||
|
memset(&node->params, 0, sizeof(node->params));
|
||||||
|
|
||||||
|
graph->count++;
|
||||||
|
|
||||||
|
if (idx + 1 > graph->max_count_ever) { graph->max_count_ever = idx + 1; }
|
||||||
|
|
||||||
|
return (PrNodeId){ .index = idx, .generation = node->generation };
|
||||||
|
}
|
||||||
|
|
||||||
wp_intern void prGraphRemoveNode(PrGraph *graph, PrNodeId id) {
|
wp_intern void prGraphRemoveNode(PrGraph *graph, PrNodeId id) {
|
||||||
if (!prGraphIsActiveNode(graph, id)) { return; }
|
if (!prGraphIsActiveNode(graph, id)) { return; }
|
||||||
|
|
||||||
@@ -297,6 +387,10 @@ wp_intern void prGraphDump(const PrGraph *graph) {
|
|||||||
|
|
||||||
i32 main(void) {
|
i32 main(void) {
|
||||||
WpAllocator arena = wpMemArenaAllocatorInitZero(MiB(16));
|
WpAllocator arena = wpMemArenaAllocatorInitZero(MiB(16));
|
||||||
|
if (wpMemAllocatorInvalid(&arena)) {
|
||||||
|
wpLogFatal(&_log, wpStr8Lit("arena init failed"));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
PrGraph graph = {0};
|
PrGraph graph = {0};
|
||||||
prGraphInit(&graph, &arena, 128);
|
prGraphInit(&graph, &arena, 128);
|
||||||
@@ -349,6 +443,32 @@ i32 main(void) {
|
|||||||
printf("\n");
|
printf("\n");
|
||||||
prGraphDump(&graph);
|
prGraphDump(&graph);
|
||||||
|
|
||||||
|
printf("\n=== Try adding cycle 8→1 (should be rejected) ===\n");
|
||||||
|
prGraphAddEdge(&graph, n8, n1);
|
||||||
|
|
||||||
|
printf("\n=== Try adding 8→1 again (still rejected) ===\n");
|
||||||
|
prGraphAddEdge(&graph, n8, n1);
|
||||||
|
|
||||||
|
printf("\n=== Try adding cycle 7→2 (should be rejected) ===\n\n");
|
||||||
|
prGraphAddEdge(&graph, n7, n2);
|
||||||
|
|
||||||
|
prGraphDump(&graph);
|
||||||
|
|
||||||
|
printf("\n=== Topological sort ===\n");
|
||||||
|
PrNodeId *sorted = prGraphTopologicalSort(&graph, &arena);
|
||||||
|
if (sorted) {
|
||||||
|
u64 n = wpArrayCount(sorted);
|
||||||
|
printf(" count: %llu / %llu active\n",
|
||||||
|
(unsigned long long)n,
|
||||||
|
(unsigned long long)graph.count);
|
||||||
|
for (u64 i = 0; i < n; i++) {
|
||||||
|
printf(" [%llu] idx=%llu gen=%llu\n",
|
||||||
|
(unsigned long long)i,
|
||||||
|
(unsigned long long)sorted[i].index,
|
||||||
|
(unsigned long long)sorted[i].generation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
wpMemArenaAllocatorDestroy(&arena);
|
wpMemArenaAllocatorDestroy(&arena);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user