diff --git a/README.md b/README.md index fd62b0f..aa87625 100644 --- a/README.md +++ b/README.md @@ -86,12 +86,13 @@ isCyclic(graphB); ```ts declare type Graph = { - size: number; - adjacencyMatrix: { [u: string]: { [v: string]: number } }; + [u: string]: { + [v: string]: number; + }; }; ``` -This is the representation of a graph that _graph-fns_ uses. It includes an [adjacency matrix](https://en.wikipedia.org/wiki/Adjacency_matrix) to describe the number of edges between each vertex. A value of `0` indicates there is no edge between the two vertices and a value of `1` indicates there's a single edge connecting them from `u` to `v`. +An [adjacency matrix](https://en.wikipedia.org/wiki/Adjacency_matrix) that describes the edges between each vertex. A value of `0` indicates there is no edge between the two vertices and a value of `1` indicates there is an edge connecting them (directed from `u` to `v`). ### D3Graph diff --git a/index.ts b/index.ts index c8fb467..a20725e 100644 --- a/index.ts +++ b/index.ts @@ -1,7 +1,4 @@ -type Graph = { - size: number; - adjacencyMatrix: { [u: string]: { [v: string]: number } }; -}; +type Graph = { [u: string]: { [v: string]: number } }; type D3Graph = { nodes: Array<{ @@ -14,71 +11,60 @@ type D3Graph = { }; const addEdge = (graph: Graph, edge: [string, string]): Graph => { - const size = graph.size; - const adjacencyMatrix: Graph["adjacencyMatrix"] = {}; + const result: Graph = {}; - for (let u in graph.adjacencyMatrix) { - adjacencyMatrix[u] = {}; - for (let v in graph.adjacencyMatrix[u]) { - adjacencyMatrix[u][v] = graph.adjacencyMatrix[u][v]; + for (let u in graph) { + result[u] = {}; + for (let v in graph[u]) { + result[u][v] = graph[u][v]; } } - adjacencyMatrix[edge[0]][edge[1]] += 1; + result[edge[0]][edge[1]] += 1; - return { - size, - adjacencyMatrix, - }; + return result; }; const addVertex = (graph: Graph, vertex: string): Graph => { - if (graph.adjacencyMatrix[vertex]) return graph; + if (graph[vertex]) return graph; - const size = graph.size + 1; - const adjacencyMatrix: Graph["adjacencyMatrix"] = {}; + const result: Graph = {}; - for (let u in graph.adjacencyMatrix) { - adjacencyMatrix[u] = {}; - for (let v in graph.adjacencyMatrix[u]) { - adjacencyMatrix[u][v] = graph.adjacencyMatrix[u][v]; + for (let u in graph) { + result[u] = {}; + for (let v in graph[u]) { + result[u][v] = graph[u][v]; } } - for (let v in adjacencyMatrix) adjacencyMatrix[v][vertex] = 0; - adjacencyMatrix[vertex] = {}; - for (let v in adjacencyMatrix) adjacencyMatrix[vertex][v] = 0; + for (let v in result) result[v][vertex] = 0; + result[vertex] = {}; + for (let v in result) result[vertex][v] = 0; - return { - size, - adjacencyMatrix, - }; + return result; }; const create = (size: number = 0, id: (i: number) => string = (i) => i.toString(10)): Graph => { - const adjacencyMatrix: Graph["adjacencyMatrix"] = {}; + const result: Graph = {}; for (let i = 0; i < size; i++) { const u = id(i); - adjacencyMatrix[u] = {}; + result[u] = {}; for (let j = 0; j < size; j++) { const v = id(j); - adjacencyMatrix[u][v] = 0; + result[u][v] = 0; } } - return { - size, - adjacencyMatrix, - }; + return result; }; const edges = (graph: Graph): Array<[string, string]> => { const result = []; - for (let u in graph.adjacencyMatrix) { - for (let v in graph.adjacencyMatrix[u]) { - for (let i = 0; i < graph.adjacencyMatrix[u][v]; i++) { + for (let u in graph) { + for (let v in graph[u]) { + for (let i = 0; i < graph[u][v]; i++) { result.push([u, v]); } } @@ -88,45 +74,45 @@ const edges = (graph: Graph): Array<[string, string]> => { }; const fromD3 = (d3Graph: D3Graph): Graph => { - const size = d3Graph.nodes.length; - const adjacencyMatrix: Graph["adjacencyMatrix"] = {}; + const result: Graph = {}; for (let u of d3Graph.nodes) { - adjacencyMatrix[u.id] = {}; + result[u.id] = {}; for (let v of d3Graph.nodes) { - adjacencyMatrix[u.id][v.id] = 0; + result[u.id][v.id] = 0; } } for (let link of d3Graph.links) { - adjacencyMatrix[link.source][link.target] += 1; + result[link.source][link.target] += 1; } - return { - size, - adjacencyMatrix, - }; + return result; }; const indegrees = (graph: Graph): { [id: string]: number } => { const result: { [id: string]: number } = {}; - for (let i in graph.adjacencyMatrix) result[i] = 0; - for (let u in graph.adjacencyMatrix) { - for (let v in graph.adjacencyMatrix[u]) { - result[v] += graph.adjacencyMatrix[u][v]; + + for (let i in graph) result[i] = 0; + for (let u in graph) { + for (let v in graph[u]) { + result[v] += graph[u][v]; } } + return result; }; const isCyclic = (graph: Graph): boolean => { const visited: Set = new Set(); const path: Set = new Set(); - for (let i in graph.adjacencyMatrix) { + + for (let i in graph) { if (visited.has(i)) continue; const cycleFound = _isCyclic(graph, visited, path, i); if (cycleFound) return true; } + return false; }; @@ -139,8 +125,8 @@ const _isCyclic = ( visited.add(vertex); path.add(vertex); - for (let i in graph.adjacencyMatrix[vertex]) { - if (graph.adjacencyMatrix[vertex][i] > 0) { + for (let i in graph[vertex]) { + if (graph[vertex][i] > 0) { if (visited.has(i)) { if (path.has(i)) return true; continue; @@ -157,63 +143,55 @@ const _isCyclic = ( const outdegrees = (graph: Graph): { [id: string]: number } => { const result: { [id: string]: number } = {}; - for (let i in graph.adjacencyMatrix) result[i] = 0; - for (let u in graph.adjacencyMatrix) { - for (let v in graph.adjacencyMatrix[u]) { - result[v] += graph.adjacencyMatrix[v][u]; + for (let i in graph) result[i] = 0; + for (let u in graph) { + for (let v in graph[u]) { + result[v] += graph[v][u]; } } return result; }; const removeEdge = (graph: Graph, edge: [string, string]): Graph => { - const size = graph.size; - const adjacencyMatrix: Graph["adjacencyMatrix"] = {}; + const result: Graph = {}; - for (let u in graph.adjacencyMatrix) { - adjacencyMatrix[u] = {}; - for (let v in graph.adjacencyMatrix[u]) { - adjacencyMatrix[u][v] = graph.adjacencyMatrix[u][v]; + for (let u in graph) { + result[u] = {}; + for (let v in graph[u]) { + result[u][v] = graph[u][v]; } } - if (adjacencyMatrix[edge[0]][edge[1]] > 0) { - adjacencyMatrix[edge[0]][edge[1]] -= 1; + if (result[edge[0]][edge[1]] > 0) { + result[edge[0]][edge[1]] -= 1; } - return { - size, - adjacencyMatrix, - }; + return result; }; const removeVertex = (graph: Graph, vertex: string): Graph => { - const size = graph.size - 1; - const adjacencyMatrix: Graph["adjacencyMatrix"] = {}; + const result: Graph = {}; - for (let u in graph.adjacencyMatrix) { + for (let u in graph) { if (u === vertex) continue; - adjacencyMatrix[u] = {}; - for (let v in graph.adjacencyMatrix[u]) { + result[u] = {}; + for (let v in graph[u]) { if (v === vertex) continue; - adjacencyMatrix[u][v] = graph.adjacencyMatrix[u][v]; + result[u][v] = graph[u][v]; } } - return { - size, - adjacencyMatrix, - }; + return result; }; const toD3 = (graph: Graph): D3Graph => { const nodes = []; const links = []; - for (let u in graph.adjacencyMatrix) { + for (let u in graph) { nodes[nodes.length] = { id: u }; - for (let v in graph.adjacencyMatrix[u]) { - for (let i = 0; i < graph.adjacencyMatrix[u][v]; i++) { + for (let v in graph[u]) { + for (let i = 0; i < graph[u][v]; i++) { links[links.length] = { source: u, target: v }; } } @@ -230,7 +208,7 @@ const topologicalSort = (graph: Graph): Array => { const queue: Array = []; const indegree = indegrees(graph); - for (let i in graph.adjacencyMatrix) { + for (let i in graph) { if (indegree[i] === 0) { queue.push(i); visited.add(i); @@ -240,9 +218,9 @@ const topologicalSort = (graph: Graph): Array => { while (queue.length > 0) { const v = queue.shift(); result.push(v); - for (let i in graph.adjacencyMatrix) { - if (graph.adjacencyMatrix[v][i] > 0 && !visited.has(i)) { - indegree[i] -= graph.adjacencyMatrix[v][i]; + for (let i in graph) { + if (graph[v][i] > 0 && !visited.has(i)) { + indegree[i] -= graph[v][i]; if (indegree[i] <= 0) { queue.push(i); visited.add(i); @@ -255,22 +233,19 @@ const topologicalSort = (graph: Graph): Array => { }; const transpose = (graph: Graph): Graph => { - const adjacencyMatrix: Graph["adjacencyMatrix"] = {}; + const result: Graph = {}; - for (let u in graph.adjacencyMatrix) { - adjacencyMatrix[u] = {}; - for (let v in graph.adjacencyMatrix[u]) { - adjacencyMatrix[u][v] = graph.adjacencyMatrix[v][u]; + for (let u in graph) { + result[u] = {}; + for (let v in graph[u]) { + result[u][v] = graph[v][u]; } } - return { - size: graph.size, - adjacencyMatrix, - }; + return result; }; -const vertices = (graph: Graph): Array => Object.keys(graph.adjacencyMatrix); +const vertices = (graph: Graph): Array => Object.keys(graph); export { D3Graph, diff --git a/test.ts b/test.ts index 93303ac..07cf412 100644 --- a/test.ts +++ b/test.ts @@ -23,20 +23,14 @@ test("addEdge", (t) => { t.deepEqual( addEdge( { - size: 2, - adjacencyMatrix: { - a: { a: 0, b: 0 }, - b: { a: 0, b: 0 }, - }, + a: { a: 0, b: 0 }, + b: { a: 0, b: 0 }, }, ["a", "b"], ), { - size: 2, - adjacencyMatrix: { - a: { a: 0, b: 1 }, - b: { a: 0, b: 0 }, - }, + a: { a: 0, b: 1 }, + b: { a: 0, b: 0 }, }, ); }); @@ -44,38 +38,20 @@ test("addEdge", (t) => { test("addVertex", (t) => { t.plan(2); - t.deepEqual( - addVertex( - { - size: 0, - adjacencyMatrix: {}, - }, - "a", - ), - { - size: 1, - adjacencyMatrix: { - a: { a: 0 }, - }, - }, - ); + t.deepEqual(addVertex({}, "a"), { + a: { a: 0 }, + }); t.deepEqual( addVertex( { - size: 1, - adjacencyMatrix: { - a: { a: 0 }, - }, + a: { a: 0 }, }, "b", ), { - size: 2, - adjacencyMatrix: { - a: { a: 0, b: 0 }, - b: { a: 0, b: 0 }, - }, + a: { a: 0, b: 0 }, + b: { a: 0, b: 0 }, }, ); }); @@ -86,11 +62,8 @@ test("create", (t) => { t.deepEqual( create(2, (i) => `${i}!`), { - size: 2, - adjacencyMatrix: { - "0!": { "0!": 0, "1!": 0 }, - "1!": { "0!": 0, "1!": 0 }, - }, + "0!": { "0!": 0, "1!": 0 }, + "1!": { "0!": 0, "1!": 0 }, }, ); }); @@ -100,12 +73,9 @@ test("edges", (t) => { t.deepEqual( edges({ - size: 3, - adjacencyMatrix: { - a: { a: 0, b: 1, c: 0 }, - b: { a: 0, b: 0, c: 1 }, - c: { a: 0, b: 0, c: 0 }, - }, + a: { a: 0, b: 1, c: 0 }, + b: { a: 0, b: 0, c: 1 }, + c: { a: 0, b: 0, c: 0 }, }), [ ["a", "b"], @@ -127,12 +97,9 @@ test("fromD3", (t) => { ], }), { - size: 3, - adjacencyMatrix: { - a: { a: 1, b: 1, c: 1 }, - b: { a: 0, b: 0, c: 0 }, - c: { a: 0, b: 0, c: 0 }, - }, + a: { a: 1, b: 1, c: 1 }, + b: { a: 0, b: 0, c: 0 }, + c: { a: 0, b: 0, c: 0 }, }, ); }); @@ -142,12 +109,9 @@ test("indegrees", (t) => { t.deepEqual( indegrees({ - size: 3, - adjacencyMatrix: { - a: { a: 1, b: 1, c: 1 }, - b: { a: 0, b: 1, c: 0 }, - c: { a: 0, b: 0, c: 0 }, - }, + a: { a: 1, b: 1, c: 1 }, + b: { a: 0, b: 1, c: 0 }, + c: { a: 0, b: 0, c: 0 }, }), { a: 1, b: 2, c: 1 }, ); @@ -158,88 +122,64 @@ test("isCyclic", (t) => { t.equal( isCyclic({ - size: 1, - adjacencyMatrix: { - a: { a: 0 }, - }, + a: { a: 0 }, }), false, ); t.equal( isCyclic({ - size: 2, - adjacencyMatrix: { - a: { a: 0, b: 0 }, - b: { a: 0, b: 0 }, - }, + a: { a: 0, b: 0 }, + b: { a: 0, b: 0 }, }), false, ); t.equal( isCyclic({ - size: 2, - adjacencyMatrix: { - a: { a: 0, b: 1 }, - b: { a: 0, b: 0 }, - }, + a: { a: 0, b: 1 }, + b: { a: 0, b: 0 }, }), false, ); t.equal( isCyclic({ - size: 3, - adjacencyMatrix: { - a: { a: 0, b: 1, c: 1 }, - b: { a: 0, b: 0, c: 0 }, - c: { a: 0, b: 0, c: 0 }, - }, + a: { a: 0, b: 1, c: 1 }, + b: { a: 0, b: 0, c: 0 }, + c: { a: 0, b: 0, c: 0 }, }), false, ); t.equal( isCyclic({ - size: 1, - adjacencyMatrix: { - a: { a: 1 }, - }, + a: { a: 1 }, }), true, ); t.equal( isCyclic({ - size: 2, - adjacencyMatrix: { - a: { a: 0, b: 1 }, - b: { a: 1, b: 0 }, - }, + a: { a: 0, b: 1 }, + b: { a: 1, b: 0 }, }), true, ); t.equal( isCyclic({ - size: 3, - adjacencyMatrix: { - a: { a: 0, b: 1, c: 0 }, - b: { a: 0, b: 0, c: 1 }, - c: { a: 1, b: 0, c: 0 }, - }, + a: { a: 0, b: 1, c: 0 }, + b: { a: 0, b: 0, c: 1 }, + c: { a: 1, b: 0, c: 0 }, }), true, ); t.equal( isCyclic({ - size: 2, - adjacencyMatrix: { - a: { a: 1, b: 0 }, - b: { a: 0, b: 1 }, - }, + a: { a: 1, b: 0 }, + b: { a: 0, b: 1 }, }), true, ); @@ -250,12 +190,9 @@ test("outdegrees", (t) => { t.deepEqual( outdegrees({ - size: 3, - adjacencyMatrix: { - a: { a: 1, b: 1, c: 1 }, - b: { a: 0, b: 1, c: 0 }, - c: { a: 0, b: 0, c: 0 }, - }, + a: { a: 1, b: 1, c: 1 }, + b: { a: 0, b: 1, c: 0 }, + c: { a: 0, b: 0, c: 0 }, }), { a: 3, b: 1, c: 0 }, ); @@ -267,22 +204,16 @@ test("removeEdge", (t) => { t.deepEqual( removeEdge( { - size: 3, - adjacencyMatrix: { - a: { a: 0, b: 1, c: 0 }, - b: { a: 0, b: 0, c: 1 }, - c: { a: 1, b: 0, c: 0 }, - }, + a: { a: 0, b: 1, c: 0 }, + b: { a: 0, b: 0, c: 1 }, + c: { a: 1, b: 0, c: 0 }, }, ["a", "b"], ), { - size: 3, - adjacencyMatrix: { - a: { a: 0, b: 0, c: 0 }, - b: { a: 0, b: 0, c: 1 }, - c: { a: 1, b: 0, c: 0 }, - }, + a: { a: 0, b: 0, c: 0 }, + b: { a: 0, b: 0, c: 1 }, + c: { a: 1, b: 0, c: 0 }, }, ); }); @@ -293,21 +224,15 @@ test("removeVertex", (t) => { t.deepEqual( removeVertex( { - size: 3, - adjacencyMatrix: { - a: { a: 0, b: 1, c: 0 }, - b: { a: 0, b: 0, c: 1 }, - c: { a: 1, b: 0, c: 0 }, - }, + a: { a: 0, b: 1, c: 0 }, + b: { a: 0, b: 0, c: 1 }, + c: { a: 1, b: 0, c: 0 }, }, "b", ), { - size: 2, - adjacencyMatrix: { - a: { a: 0, c: 0 }, - c: { a: 1, c: 0 }, - }, + a: { a: 0, c: 0 }, + c: { a: 1, c: 0 }, }, ); }); @@ -316,12 +241,9 @@ test("toD3", (t) => { t.plan(1); t.deepEqual( toD3({ - size: 3, - adjacencyMatrix: { - a: { a: 1, b: 1, c: 1 }, - b: { a: 0, b: 0, c: 0 }, - c: { a: 0, b: 0, c: 0 }, - }, + a: { a: 1, b: 1, c: 1 }, + b: { a: 0, b: 0, c: 0 }, + c: { a: 0, b: 0, c: 0 }, }), { nodes: [{ id: "a" }, { id: "b" }, { id: "c" }], @@ -339,68 +261,50 @@ test("topologicalSort", (t) => { t.deepEqual( topologicalSort({ - size: 1, - adjacencyMatrix: { - a: { a: 0 }, - }, + a: { a: 0 }, }), ["a"], ); t.deepEqual( topologicalSort({ - size: 3, - adjacencyMatrix: { - a: { a: 0, b: 0, c: 0 }, - b: { a: 0, b: 0, c: 0 }, - c: { a: 0, b: 0, c: 0 }, - }, + a: { a: 0, b: 0, c: 0 }, + b: { a: 0, b: 0, c: 0 }, + c: { a: 0, b: 0, c: 0 }, }), ["a", "b", "c"], ); t.deepEqual( topologicalSort({ - size: 3, - adjacencyMatrix: { - a: { a: 0, b: 1, c: 0 }, - b: { a: 0, b: 0, c: 0 }, - c: { a: 1, b: 0, c: 0 }, - }, + a: { a: 0, b: 1, c: 0 }, + b: { a: 0, b: 0, c: 0 }, + c: { a: 1, b: 0, c: 0 }, }), ["c", "a", "b"], ); t.deepEqual( topologicalSort({ - size: 3, - adjacencyMatrix: { - a: { a: 0, b: 0, c: 0 }, - b: { a: 1, b: 0, c: 0 }, - c: { a: 1, b: 0, c: 0 }, - }, + a: { a: 0, b: 0, c: 0 }, + b: { a: 1, b: 0, c: 0 }, + c: { a: 1, b: 0, c: 0 }, }), ["b", "c", "a"], ); t.deepEqual( topologicalSort({ - size: 2, - adjacencyMatrix: { - a: { a: 0, b: 0 }, - b: { a: 10, b: 0 }, - }, + a: { a: 0, b: 0 }, + b: { a: 10, b: 0 }, }), ["b", "a"], ); t.throws(() => { topologicalSort({ - size: 2, - adjacencyMatrix: { - a: { a: 0, b: 1 }, - b: { a: 1, b: 0 }, - }, + a: { a: 0, b: 1 }, + b: { a: 1, b: 0 }, }); }); }); @@ -410,71 +314,47 @@ test("transpose", (t) => { t.deepEqual( transpose({ - size: 1, - adjacencyMatrix: { - a: { a: 0 }, - }, + a: { a: 0 }, }), { - size: 1, - adjacencyMatrix: { - a: { a: 0 }, - }, + a: { a: 0 }, }, ); t.deepEqual( transpose({ - size: 2, - adjacencyMatrix: { - a: { a: 0, b: 1 }, - b: { a: 0, b: 0 }, - }, + a: { a: 0, b: 1 }, + b: { a: 0, b: 0 }, }), { - size: 2, - adjacencyMatrix: { - a: { a: 0, b: 0 }, - b: { a: 1, b: 0 }, - }, + a: { a: 0, b: 0 }, + b: { a: 1, b: 0 }, }, ); t.deepEqual( transpose({ - size: 3, - adjacencyMatrix: { - a: { a: 0, b: 1, c: 1 }, - b: { a: 0, b: 0, c: 1 }, - c: { a: 0, b: 0, c: 0 }, - }, + a: { a: 0, b: 1, c: 1 }, + b: { a: 0, b: 0, c: 1 }, + c: { a: 0, b: 0, c: 0 }, }), { - size: 3, - adjacencyMatrix: { - a: { a: 0, b: 0, c: 0 }, - b: { a: 1, b: 0, c: 0 }, - c: { a: 1, b: 1, c: 0 }, - }, + a: { a: 0, b: 0, c: 0 }, + b: { a: 1, b: 0, c: 0 }, + c: { a: 1, b: 1, c: 0 }, }, ); t.deepEqual( transpose({ - size: 3, - adjacencyMatrix: { - a: { a: 1, b: 1, c: 1 }, - b: { a: 1, b: 1, c: 1 }, - c: { a: 1, b: 1, c: 1 }, - }, + a: { a: 1, b: 1, c: 1 }, + b: { a: 1, b: 1, c: 1 }, + c: { a: 1, b: 1, c: 1 }, }), { - size: 3, - adjacencyMatrix: { - a: { a: 1, b: 1, c: 1 }, - b: { a: 1, b: 1, c: 1 }, - c: { a: 1, b: 1, c: 1 }, - }, + a: { a: 1, b: 1, c: 1 }, + b: { a: 1, b: 1, c: 1 }, + c: { a: 1, b: 1, c: 1 }, }, ); }); @@ -484,12 +364,9 @@ test("vertices", (t) => { t.deepEqual( vertices({ - size: 3, - adjacencyMatrix: { - a: { a: 0, b: 0, c: 0 }, - b: { a: 0, b: 0, c: 0 }, - c: { a: 0, b: 0, c: 0 }, - }, + a: { a: 0, b: 0, c: 0 }, + b: { a: 0, b: 0, c: 0 }, + c: { a: 0, b: 0, c: 0 }, }), ["a", "b", "c"], );