diff --git a/README.md b/README.md index d418bad..10485b7 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,14 @@ declare const addVertex: (graph: Graph, vertex: string) => Graph; Adds a new vertex to the graph. The new vertex will not have any edges connecting it to existing vertices in the graph. +### children + +```ts +declare const children: (graph: Graph, vertex: string) => Set; +``` + +Returns all the vertices that are children of the given vertex — there is an edge starting at the given vertex going to the child vertex. If there is an edge that both starts and ends at the given vertex, it will be considered a child of itself and included in the result. + ### clone ```ts @@ -148,6 +156,16 @@ The `size` argument defines how many vertices with which to seed the graph. Addi The `id` function can be provided to specify how to generate ID's for each of the seed vertices. The `i` argument passed is the "index" of the vertex being created — a unique positive integer starting at 0, incrementing by 1 for each vertex. The default function will simply convert `i` to a string (`(i) => i.toString(10)`) resulting in ID's like: `"0"`, `"1"`, `"2"` etc. +### descendants + +```ts +declare const descendants: (graph: Graph, vertex: string) => Set; +``` + +Given a [DAG](https://en.wikipedia.org/wiki/Directed_acyclic_graph), recursively finds all the vertices under the given vertex. + +Note: If the given graph contains cycles (checked with `isCyclic`), an error will be thrown. + ### edges ```ts diff --git a/index.ts b/index.ts index eb032d2..c8df8f4 100644 --- a/index.ts +++ b/index.ts @@ -30,6 +30,18 @@ const addVertex = (graph: Graph, vertex: string): Graph => { return result; }; +const children = (graph: Graph, vertex: string): Set => { + const result: Set = new Set(); + + for (let v in graph[vertex]) { + if (graph[vertex][v] > 0) { + result.add(v); + } + } + + return result; +}; + const clone = (graph: Graph): Graph => { const result: Graph = {}; @@ -58,6 +70,18 @@ const create = (size: number = 0, id: (i: number) => string = (i) => i.toString( return result; }; +const descendants = (graph: Graph, vertex: string): Set => { + if (isCyclic(graph)) throw "Cannot retrieve descendants in a graph that contains cycles."; + + let result: Set = new Set(); + + for (let child of children(graph, vertex)) { + result = new Set([...result, child, ...descendants(graph, child)]); + } + + return result; +}; + const edges = (graph: Graph): Array<[string, string]> => { const result = []; @@ -262,8 +286,10 @@ export { Graph, addEdge, addVertex, + children, clone, create, + descendants, edges, fromD3, indegrees, diff --git a/test.ts b/test.ts index 3a81cde..0e52d93 100644 --- a/test.ts +++ b/test.ts @@ -1,8 +1,10 @@ import { addEdge, addVertex, + children, clone, create, + descendants, edges, fromD3, indegrees, @@ -59,6 +61,32 @@ test("addVertex", (t) => { ); }); +test("children", (t) => { + t.plan(2); + + t.deepEqual( + children( + { + a: { a: 0, b: 1, c: 1 }, + b: { a: 0, b: 0, c: 0 }, + c: { a: 0, b: 0, c: 0 }, + }, + "a", + ), + new Set(["b", "c"]), + ); + + t.deepEqual( + children( + { + a: { a: 1 }, + }, + "a", + ), + new Set(["a"]), + ); +}); + test("clone", (t) => { t.plan(4); @@ -85,6 +113,45 @@ test("create", (t) => { ); }); +test("descendants", (t) => { + t.plan(3); + + t.deepEqual( + descendants( + { + a: { a: 0, b: 1, c: 0 }, + b: { a: 0, b: 0, c: 1 }, + c: { a: 0, b: 0, c: 0 }, + }, + "a", + ), + new Set(["b", "c"]), + ); + + t.deepEqual( + descendants( + { + a: { a: 0, b: 1, c: 0, d: 0 }, + b: { a: 0, b: 0, c: 1, d: 1 }, + c: { a: 0, b: 0, c: 0, d: 1 }, + d: { a: 0, b: 0, c: 0, d: 0 }, + }, + "a", + ), + new Set(["b", "c", "d"]), + ); + + t.throws(() => { + descendants( + { + a: { a: 0, b: 1 }, + b: { a: 1, b: 0 }, + }, + "a", + ); + }); +}); + test("edges", (t) => { t.plan(2);