Compare commits

...

3 Commits

Author SHA1 Message Date
Abdelrahman Said 1325902f14 Add scratchpad 2026-07-04 17:07:43 +01:00
Abdelrahman Said f00e5caf97 Add resources 2026-07-04 17:07:29 +01:00
Abdelrahman Said 09f417dceb Update .gitignore 2026-07-04 17:07:23 +01:00
4 changed files with 686 additions and 1 deletions
+4 -1
View File
@@ -1,2 +1,5 @@
scratchpad/
.vscode
*.dSYM
scratchpad/**
!scratchpad/**/
!scratchpad/**/*.c
+2
View File
@@ -4,3 +4,5 @@
- [Topological Sorting using BFS (Kahn's Algorithm)](https://www.geeksforgeeks.org/topological-sorting-indegree-based-solution/)
- [Kahn's Algorithm Explained with Code & Examples](https://dev.to/rui_jiang/kahns-algorithm-for-topological-sorting-explained-with-code-examples-2if5)
- [Understanding Kahn's Algorithm for Topological Sorting](https://blog.devgenius.io/dsa-kahns-algorithm-for-topological-sorting-33c8587985a1)
- [Detect a Cycle in Directed Graph](https://takeuforward.org/data-structure/detect-a-cycle-in-directed-graph-topological-sort-kahns-algorithm-g-23)
- [LeetCode's Kahn's Algorithm](https://leetcodethehardway.com/tutorials/graph-theory/kahns-algorithm)
+327
View File
@@ -0,0 +1,327 @@
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "../src/wapp/wapp.h"
/* ---------------------------------------------------------------------------
* Pool allocator (arena-backed, intrusive free list)
* -------------------------------------------------------------------------*/
typedef struct PrPoolFreeNode PrPoolFreeNode;
struct PrPoolFreeNode {
PrPoolFreeNode *next;
};
typedef struct {
WpAllocator *allocator;
PrPoolFreeNode *free_head;
u64 slot_size;
} PrPool;
static void prPoolInit(PrPool *pool, WpAllocator *arena, u64 slot_size) {
pool->allocator = arena;
pool->free_head = NULL;
pool->slot_size = slot_size;
}
static void *prPoolAlloc(PrPool *pool) {
if (pool->free_head) {
PrPoolFreeNode *node = pool->free_head;
pool->free_head = node->next;
return node;
}
return wpMemAllocatorAlloc(pool->allocator, pool->slot_size);
}
static void prPoolFree(PrPool *pool, void *slot) {
if (!slot) { return; }
PrPoolFreeNode *node = (PrPoolFreeNode *)slot;
node->next = pool->free_head;
pool->free_head = node;
}
/* ---------------------------------------------------------------------------
* Graph — unified nodes with values + forward/backward adjacency
*
* - Nodes live in a flat array; free slots are linked via next_free.
* - Each node has a generation counter bumped on free; handles are
* { index, generation } so stale references are detectable.
* - Edges are pool-allocated PrEdgeNode structs with both forward
* and backward linkage so deletion traces both directions.
* -------------------------------------------------------------------------*/
#define PR_INVALID_INDEX ((u64)-1)
typedef struct PrEdgeNode PrEdgeNode;
struct PrEdgeNode {
PrEdgeNode *pool_next; /* used by pool free list when freed */
PrEdgeNode *next_forward; /* chain in source's forward list */
PrEdgeNode *next_backward; /* chain in target's backward list */
u64 source_idx;
u64 target_idx;
};
typedef struct {
u64 index;
u64 generation;
} PrHandle;
typedef struct {
int value;
u64 generation; /* bumped on every free */
u64 next_free; /* free-list index (PR_INVALID_INDEX = active) */
PrEdgeNode *forward_head; /* outgoing edges */
PrEdgeNode *backward_head; /* incoming edges */
} PrGraphNode;
typedef struct {
PrGraphNode *nodes; /* WpArray of PrGraphNode */
u64 max_nodes;
u64 max_ever; /* highest index ever allocated */
u64 free_head; /* PR_INVALID_INDEX = empty */
u64 count; /* active node count */
PrPool edge_pool;
} PrGraph;
/* --- initialisation ---------------------------------------------------- */
static void prGraphInit(PrGraph *g, WpAllocator *arena, u64 max_nodes) {
g->nodes = wpArrayAllocCapacity(PrGraphNode, arena, max_nodes, WP_ARRAY_INIT_FILLED);
g->max_nodes = max_nodes;
g->max_ever = 0;
g->free_head = PR_INVALID_INDEX;
g->count = 0;
prPoolInit(&g->edge_pool, arena, sizeof(PrEdgeNode));
/* Build the free list — last slot's next_free stays PR_INVALID_INDEX */
for (u64 i = 0; i < max_nodes; i++) {
g->nodes[i].next_free = (i < max_nodes - 1) ? i + 1 : PR_INVALID_INDEX;
}
g->free_head = 0;
}
/* --- handle validation ------------------------------------------------ */
static b8 prHandleValid(PrGraph *g, PrHandle h) {
if (h.index >= g->max_nodes) { return false; }
PrGraphNode *node = &g->nodes[h.index];
return node->generation == h.generation && node->next_free == PR_INVALID_INDEX;
}
/* --- node management -------------------------------------------------- */
static PrHandle prNodeAdd(PrGraph *g, int value) {
if (g->free_head == PR_INVALID_INDEX) {
return (PrHandle){ PR_INVALID_INDEX, 0 };
}
u64 idx = g->free_head;
PrGraphNode *n = &g->nodes[idx];
g->free_head = n->next_free;
n->value = value;
n->next_free = PR_INVALID_INDEX;
n->forward_head = NULL;
n->backward_head = NULL;
g->count++;
if (idx >= g->max_ever) { g->max_ever = idx + 1; }
return (PrHandle){ idx, n->generation };
}
static int *prNodeGetValue(PrGraph *g, PrHandle h) {
if (!prHandleValid(g, h)) { return NULL; }
return &g->nodes[h.index].value;
}
/* --- internal: unlink edge helpers ------------------------------------ */
static PrEdgeNode *_unlinkForward(PrGraph *g, u64 from_idx, u64 target_idx) {
PrGraphNode *src = &g->nodes[from_idx];
PrEdgeNode *prev = NULL;
PrEdgeNode *curr = src->forward_head;
while (curr) {
if (curr->target_idx == target_idx) {
if (prev) { prev->next_forward = curr->next_forward; }
else { src->forward_head = curr->next_forward; }
return curr;
}
prev = curr;
curr = curr->next_forward;
}
return NULL;
}
static void _unlinkBackward(PrGraph *g, u64 to_idx, u64 source_idx) {
PrGraphNode *dst = &g->nodes[to_idx];
PrEdgeNode *prev = NULL;
PrEdgeNode *curr = dst->backward_head;
// This is intended to handle cases where the target node might not be the one
// immediately feeding the node represented by to_idx
while (curr) {
if (curr->source_idx == source_idx) {
if (prev) { prev->next_backward = curr->next_backward; }
else { dst->backward_head = curr->next_backward; }
return;
}
prev = curr;
curr = curr->next_backward;
}
}
/* --- edge management -------------------------------------------------- */
static b8 prEdgeAdd(PrGraph *g, PrHandle from, PrHandle to) {
if (!prHandleValid(g, from) || !prHandleValid(g, to)) { return false; }
PrEdgeNode *edge = (PrEdgeNode *)prPoolAlloc(&g->edge_pool);
if (!edge) { return false; }
edge->source_idx = from.index;
edge->target_idx = to.index;
PrGraphNode *src = &g->nodes[from.index];
edge->next_forward = src->forward_head;
src->forward_head = edge;
PrGraphNode *dst = &g->nodes[to.index];
edge->next_backward = dst->backward_head;
dst->backward_head = edge;
return true;
}
/* --- node removal (tears down incident edges) ------------------------- */
static void prNodeRemove(PrGraph *g, PrHandle h) {
if (!prHandleValid(g, h)) { return; }
u64 idx = h.index;
PrGraphNode *n = &g->nodes[idx];
/* Free outgoing edges: unlink from each target's backward list */
PrEdgeNode *edge = n->forward_head;
while (edge) {
PrEdgeNode *next = edge->next_forward;
_unlinkBackward(g, edge->target_idx, idx);
prPoolFree(&g->edge_pool, edge);
edge = next;
}
/* Free incoming edges: unlink from each source's forward list */
edge = n->backward_head;
while (edge) {
PrEdgeNode *next = edge->next_backward;
/* edge is also in the source's forward list — unlink by target_idx */
_unlinkForward(g, edge->source_idx, idx);
prPoolFree(&g->edge_pool, edge);
edge = next;
}
/* Return node slot to free list with bumped generation */
n->generation++;
n->next_free = g->free_head;
g->free_head = idx;
g->count--;
}
/* --- traversal helpers ------------------------------------------------ */
static void prDump(PrGraph *g) {
printf("Active nodes: %llu\n\n", (unsigned long long)g->count);
for (u64 i = 0; i < g->max_ever; i++) {
PrGraphNode *n = &g->nodes[i];
if (n->next_free != PR_INVALID_INDEX) { continue; }
printf(" [%llu] g=%llu value=%d\n",
(unsigned long long)i,
(unsigned long long)n->generation,
n->value);
printf(" forward ──▶");
PrEdgeNode *e = n->forward_head;
if (!e) { printf(" (none)"); }
while (e) {
PrGraphNode *tv = &g->nodes[e->target_idx];
printf(" %llu[%d]", (unsigned long long)e->target_idx, tv->value);
e = e->next_forward;
if (e) { printf(","); }
}
printf("\n");
printf(" backward ◀──");
e = n->backward_head;
if (!e) { printf(" (none)"); }
while (e) {
PrGraphNode *sv = &g->nodes[e->source_idx];
printf(" %llu[%d]", (unsigned long long)e->source_idx, sv->value);
e = e->next_backward;
if (e) { printf(","); }
}
printf("\n");
}
}
/* ---------------------------------------------------------------------------
* Demo
* -------------------------------------------------------------------------*/
int main(void) {
srand((unsigned)time(NULL));
WpAllocator arena = wpMemArenaAllocatorInitZero(KiB(64));
if (wpMemAllocatorInvalid(&arena)) {
fprintf(stderr, "arena init failed\n");
return 1;
}
PrGraph g;
prGraphInit(&g, &arena, 16);
printf("=== Add 6 nodes ===\n");
PrHandle nodes[10];
for (u64 i = 0; i < 6; i++) {
nodes[i] = prNodeAdd(&g, rand() % 100);
printf(" node[%llu] = handle{%llu g%llu} val=%d\n",
(unsigned long long)i,
(unsigned long long)nodes[i].index,
(unsigned long long)nodes[i].generation,
*prNodeGetValue(&g, nodes[i]));
}
printf("\n=== Add edges: 0→1, 0→2, 1→3, 2→3, 3→4, 4→5 ===\n");
u64 edge_list[][2] = { {0,1}, {0,2}, {1,3}, {2,3}, {3,4}, {4,5} };
for (u64 i = 0; i < 6; i++) {
u64 f = edge_list[i][0], t = edge_list[i][1];
prEdgeAdd(&g, nodes[f], nodes[t]);
}
prDump(&g);
printf("=== Remove node 2 (index %llu) ===\n",
(unsigned long long)nodes[2].index);
prNodeRemove(&g, nodes[2]);
prDump(&g);
printf("=== Stale check ===\n");
printf(" nodes[2] handle{%llu g%llu} valid? %s\n",
(unsigned long long)nodes[2].index,
(unsigned long long)nodes[2].generation,
prHandleValid(&g, nodes[2]) ? "YES" : "NO");
printf("=== Add edge 5→0 (reuses freed edge slots) ===\n");
prEdgeAdd(&g, nodes[5], nodes[0]);
prDump(&g);
printf("=== Add a new node (reuses freed node slot) ===\n");
nodes[6] = prNodeAdd(&g, 42);
printf(" new node = handle{%llu g%llu} val=%d\n",
(unsigned long long)nodes[6].index,
(unsigned long long)nodes[6].generation,
*prNodeGetValue(&g, nodes[6]));
prDump(&g);
wpMemArenaAllocatorDestroy(&arena);
return 0;
}
+353
View File
@@ -0,0 +1,353 @@
// vim:fileencoding=utf-8:foldmethod=marker
#include "../src/wapp/wapp.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* ---------------------------------------------------------------------------
* Pool allocator (arena-backed, intrusive free list)
* -------------------------------------------------------------------------*/
typedef struct PrPoolFreeNode PrPoolFreeNode;
struct PrPoolFreeNode {
PrPoolFreeNode *next;
};
typedef struct {
WpAllocator *allocator;
PrPoolFreeNode *free_head;
u64 slot_size;
} PrPool;
wp_intern void prPoolInit(PrPool *pool, WpAllocator *allocator, u64 slot_size) {
pool->allocator = allocator;
pool->free_head = NULL;
pool->slot_size = slot_size;
}
wp_intern void *prPoolAlloc(PrPool *pool) {
if (pool->free_head) {
PrPoolFreeNode *node = pool->free_head;
pool->free_head = node->next;
return node;
}
return wpMemAllocatorAlloc(pool->allocator, pool->slot_size);
}
wp_intern void prPoolFree(PrPool *pool, void *slot) {
if (!slot) { return; }
PrPoolFreeNode *node = (PrPoolFreeNode *)slot;
node->next = pool->free_head;
pool->free_head = node;
}
/* ---------------------------------------------------------------------------
* Graph
* -------------------------------------------------------------------------*/
#define INVALID_NODE_INDEX (u32)-1
#define INVALID_NODE_ID ((PrNodeId){ .index = INVALID_NODE_INDEX, .generation = INVALID_NODE_INDEX })
typedef enum {
PR_NODE_TYPE_NONE,
PR_NODE_TYPE_READ,
PR_NODE_TYPE_BLUR,
PR_NODE_TYPE_GRADE,
COUNT_NODE_TYPES
} PrNodeType;
typedef struct {
u32 index;
u32 generation;
} PrNodeId;
typedef struct {
union {
WpStr8 path;
f32 blur;
f32 gain;
} params;
PrNodeType type;
u32 generation;
u32 next_free;
} PrNode;
typedef PrNode *PrNodeArray;
/* ---------------------------------------------------------------------------
* Graph Edge/Vertex type
* If source or target are INVALID_NODE_ID, it should be treated as a vertex.
* Otherwise, it should be treated as an edge. Think of it like homogeneous
* coordinates where a vector {0, 0, 0, 0} is treated as a direction, while
* a vector {0, 0, 0, 1} is treated as a point
* ---------------------------------------------------------------------------*/
typedef struct PrEdgeVertex PrEdgeVertex;
struct PrEdgeVertex {
PrEdgeVertex *next;
PrEdgeVertex *prev;
PrNodeId source;
PrNodeId target;
};
typedef PrEdgeVertex *PrEdgeVertexArray;
typedef struct {
PrPool vertex_pool;
PrNodeArray nodes;
PrEdgeVertexArray vertices;
u64 capacity;
u64 max_count_ever;
u64 count;
u32 free_head;
} PrGraph;
wp_intern b8 prGraphIsStaleId(const PrGraph *graph, PrNodeId id) {
u32 generation = graph->nodes[id.index].generation;
return id.generation != generation;
}
wp_intern b8 prGraphIsActiveNode(const PrGraph *graph, PrNodeId id) {
u32 next_free = graph->nodes[id.index].next_free;
return !prGraphIsStaleId(graph, id) && next_free == INVALID_NODE_INDEX;
}
wp_intern PrNodeId prGraphGetNode(const PrGraph *graph, u32 index) {
return (PrNodeId){ .index = index, .generation = graph->nodes[index].generation };
}
wp_intern void prGraphInit(PrGraph *graph, WpAllocator *allocator, u64 capacity) {
graph->nodes = wpArrayAllocCapacity(PrNode, allocator, capacity, WP_ARRAY_INIT_FILLED);
graph->vertices = wpArrayAllocCapacity(PrEdgeVertex, allocator, capacity, WP_ARRAY_INIT_FILLED);
graph->free_head = 0;
graph->capacity = capacity;
graph->max_count_ever = 0;
graph->count = 0;
prPoolInit(&graph->vertex_pool, allocator, sizeof(PrEdgeVertex));
memset(graph->nodes, 0, capacity * sizeof(PrNode));
memset(graph->vertices, 0, capacity * sizeof(PrEdgeVertex));
for (u64 i = 0; i < capacity; ++i) {
graph->nodes[i].next_free = i < capacity - 1 ? i + 1 : INVALID_NODE_INDEX;
graph->vertices[i].source = graph->vertices[i].target = INVALID_NODE_ID;
}
}
wp_intern void prGraphAddEdge(PrGraph *graph, PrNodeId from, PrNodeId to) {
if (!prGraphIsActiveNode(graph, from) || !prGraphIsActiveNode(graph, to)) { return; }
PrEdgeVertex *edge = (PrEdgeVertex *)prPoolAlloc(&graph->vertex_pool);
PrEdgeVertex *src = &graph->vertices[from.index];
PrEdgeVertex *dst = &graph->vertices[to.index];
edge->source = from;
edge->target = to;
edge->next = src->next;
edge->prev = dst->prev;
src->next = edge;
dst->prev = edge;
}
wp_intern PrNodeId prGraphAddNode(PrGraph *graph, PrNodeType type) {
u32 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) {
if (!prGraphIsActiveNode(graph, from)) { return; }
PrEdgeVertex *src = &graph->vertices[from.index];
PrEdgeVertex *curr = src->next;
PrEdgeVertex *prev = NULL;
while (curr) {
if (curr == edge) {
if (prev) {
prev->next = curr->next;
} else {
src->next = curr->next;
}
return;
}
prev = curr;
curr = curr->next;
}
}
wp_intern void _unlinkBackward(PrGraph *graph, PrNodeId to, PrEdgeVertex *edge) {
if (!prGraphIsActiveNode(graph, to)) { return; }
PrEdgeVertex *dst = &graph->vertices[to.index];
PrEdgeVertex *curr = dst->prev;
PrEdgeVertex *prev = NULL;
while (curr) {
if (curr == edge) {
if (prev) {
prev->prev = curr->prev;
} else {
dst->prev = curr->prev;
}
return;
}
prev = curr;
curr = curr->prev;
}
}
wp_intern void prGraphRemoveNode(PrGraph *graph, PrNodeId id) {
if (!prGraphIsActiveNode(graph, id)) { return; }
PrNode *node = &graph->nodes[id.index];
u32 next_gen = node->generation + 1;
memset(node, 0, sizeof(PrNode));
node->generation = next_gen;
node->next_free = graph->free_head;
graph->free_head = id.index;
PrEdgeVertex *vertex = &graph->vertices[id.index];
if (!(vertex->next) && !(vertex->prev)) {
goto REMOVE_NODE_UNSET_POINTERS;
}
PrEdgeVertex *curr = vertex->next;
while (curr) {
PrEdgeVertex *next = curr->next;
_unlinkBackward(graph, curr->target, curr);
prPoolFree(&graph->vertex_pool, curr);
curr = next;
}
curr = vertex->prev;
while (curr) {
PrEdgeVertex *prev = curr->prev;
_unlinkForward(graph, curr->source, curr);
prPoolFree(&graph->vertex_pool, curr);
curr = prev;
}
REMOVE_NODE_UNSET_POINTERS:
vertex->next = vertex->prev = NULL;
graph->count--;
}
wp_intern void prGraphDump(const PrGraph *graph) {
printf("==============INPUTS==============\n");
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];
printf("%u:", id.index + 1);
if (!(vertex->prev)) {
printf(" (none)");
} else {
PrEdgeVertex *curr = vertex->prev;
while (curr) {
printf(" %u", curr->source.index + 1);
curr = curr->prev;
}
}
printf("\n");
}
printf("==============OUTPUTS==============\n");
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];
printf("%u:", id.index + 1);
if (!(vertex->next)) {
printf(" (none)");
} else {
PrEdgeVertex *curr = vertex->next;
while (curr) {
printf(" %u", curr->target.index + 1);
curr = curr->next;
}
}
printf("\n");
}
}
i32 main(void) {
WpAllocator arena = wpMemArenaAllocatorInitZero(MiB(16));
PrGraph graph = {0};
prGraphInit(&graph, &arena, 128);
PrNodeId n1 = prGraphAddNode(&graph, PR_NODE_TYPE_READ);
PrNodeId n2 = prGraphAddNode(&graph, PR_NODE_TYPE_READ);
PrNodeId n3 = prGraphAddNode(&graph, PR_NODE_TYPE_READ);
PrNodeId n4 = prGraphAddNode(&graph, PR_NODE_TYPE_READ);
PrNodeId n5 = prGraphAddNode(&graph, PR_NODE_TYPE_READ);
prGraphAddEdge(&graph, n1, n2);
prGraphAddEdge(&graph, n1, n3);
prGraphAddEdge(&graph, n1, n5);
prGraphAddEdge(&graph, n2, n4);
prGraphAddEdge(&graph, n3, n4);
prGraphAddEdge(&graph, n3, n5);
prGraphDump(&graph);
prGraphRemoveNode(&graph, n3);
printf("\n");
prGraphDump(&graph);
PrNodeId n6 = prGraphAddNode(&graph, PR_NODE_TYPE_READ);
prGraphAddEdge(&graph, n4, n6);
printf("\n");
prGraphDump(&graph);
prGraphRemoveNode(&graph, n5);
printf("\n");
prGraphDump(&graph);
PrNodeId n7 = prGraphAddNode(&graph, PR_NODE_TYPE_READ);
prGraphAddEdge(&graph, n1, n7);
prGraphAddEdge(&graph, n2, n7);
printf("\n");
prGraphDump(&graph);
PrNodeId n8 = prGraphAddNode(&graph, PR_NODE_TYPE_READ);
prGraphAddEdge(&graph, n6, n8);
prGraphAddEdge(&graph, n7, n8);
printf("\n");
prGraphDump(&graph);
wpMemArenaAllocatorDestroy(&arena);
return 0;
}