diff --git a/__tests__/unit/kCore.spec.ts b/__tests__/unit/kCore.spec.ts new file mode 100644 index 0000000..32e95a6 --- /dev/null +++ b/__tests__/unit/kCore.spec.ts @@ -0,0 +1,114 @@ +import { Edge, Graph, Node } from "@antv/graphlib"; +import { kCore } from "../../packages/graph/src"; + +const graph = new Graph({ + nodes: [ + { + id: 'A', + data: {} + }, + { + id: 'B', + data: {} + }, + { + id: 'C', + data: {} + }, + { + id: 'D', + data: {} + }, + { + id: 'E', + data: {} + }, + { + id: 'F', + data: {} + }, + { + id: 'G', + data: {} + }, + { + id: 'H', + data: {} + }, + ], + edges: [ + { + id: 'e1', + source: 'A', + target: 'B', + data: {} + }, + { + id: 'e2', + source: 'B', + target: 'C', + data: {} + }, + { + id: 'e3', + source: 'C', + target: 'G', + data: {} + }, + { + id: 'e4', + source: 'A', + target: 'D', + data: {} + }, + { + id: 'e5', + source: 'A', + target: 'E', + data: {} + }, + { + id: 'e6', + source: 'E', + target: 'F', + data: {} + }, + { + id: 'e7', + source: 'F', + target: 'D', + data: {} + }, + { + id: 'e8', + source: 'D', + target: 'E', + data: {} + }, + ], +}); + +const validateEdge = (edge: Edge, nodes: Node[]) => { + return nodes.findIndex(n => edge.source === n.id) >= 0 && nodes.findIndex(n => edge.target === n.id) >= 0; +} +describe('k-core algorithm unit test', () => { + const nodes = graph.getAllNodes(); + const edges = graph.getAllEdges(); + it('k=1', () => { + const { nodes: kNodes, edges: kEdges } = kCore(graph, 1); + expect(kNodes.length).toBe(nodes.filter(n => graph.getDegree(n.id) >= 1).length); + expect(kEdges).toStrictEqual(edges.filter(e => validateEdge(e, nodes))); + }); + + it('k=2', () => { + const { nodes: kNodes, edges: kEdges } = kCore(graph, 2); + expect(kNodes.length).toBe(nodes.filter(n => graph.getDegree(n.id) >= 2).length); + expect(kEdges).toStrictEqual(edges.filter(e => validateEdge(e, nodes))); + }); + + it('k=3', () => { + const { nodes: kNodes, edges: kEdges } = kCore(graph, 3); + expect(kNodes.length).toBe(nodes.filter(n => graph.getDegree(n.id) >= 3).length); + expect(kEdges).toStrictEqual(edges.filter(e => validateEdge(e, nodes))); + }); +}); \ No newline at end of file diff --git a/package.json b/package.json index cb0ec1a..36c2fa5 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "build:ci": "pnpm -r run build:ci", "prepare": "husky install", "test": "jest", + "test_one": "jest ./__tests__/unit/kCore.spec.ts", "coverage": "jest --coverage", "build:site": "vite build", "deploy": "gh-pages -d site/dist", @@ -73,4 +74,4 @@ "access": "public", "registry": "https://registry.npmjs.org" } -} +} \ No newline at end of file diff --git a/packages/graph/src/index.ts b/packages/graph/src/index.ts index ab5387c..bcbd01f 100644 --- a/packages/graph/src/index.ts +++ b/packages/graph/src/index.ts @@ -2,3 +2,4 @@ export * from "./pageRank"; export * from "./findPath"; export * from "./louvain"; export * from "./iLouvain"; +export * from "./k-core"; \ No newline at end of file diff --git a/packages/graph/src/k-core.ts b/packages/graph/src/k-core.ts new file mode 100644 index 0000000..b2a2dd6 --- /dev/null +++ b/packages/graph/src/k-core.ts @@ -0,0 +1,23 @@ +import { Graph } from "./types"; + +/** +Finds the k-core of a given graph. +@param graph - The input graph. +@param k - The minimum degree required for a node to be considered part of the k-core. Default is 1. +@returns An object containing the nodes and edges of the k-core. +*/ +export function kCore( + graph: Graph, + k: number = 1,) { + const nodes = graph.getAllNodes(); + let edges = graph.getAllEdges(); + nodes.sort((a, b) => graph.getDegree(a.id, 'both') - graph.getDegree(b.id, 'both')); + let i = 0; + while (true) { + const curNode = nodes[i]; + if (graph.getDegree(curNode.id, 'both') >= k) break; + nodes.splice(i, 1);//remove node + edges = edges.filter(e => !(e.source === i || e.target === i)); + } + return { nodes, edges }; +} \ No newline at end of file