Skip to content

feat: algorithm dfs & bfs #70

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 200 additions & 0 deletions __tests__/unit/bfs.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import breadthFirstSearch from "../../packages/graph/src/bfs";
import { Graph } from "@antv/graphlib";

const data = {
nodes: [
{
id: 'A',
data: {}
},
{
id: 'B',
data: {}
},
{
id: 'C',
data: {}
},
{
id: 'D',
data: {}
},
{
id: 'E',
data: {}
},
{
id: 'F',
data: {}
},
{
id: 'G',
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 graph = new Graph<any, any>(data);

describe('breadthFirstSearch', () => {
it('should perform BFS operation on graph', () => {
const enterNodeCallback = jest.fn();
const leaveNodeCallback = jest.fn();

// Traverse graphs without callbacks first.
breadthFirstSearch(graph, 'A');

// Traverse graph with enterNode and leaveNode callbacks.
breadthFirstSearch(graph, 'A', {
enter: enterNodeCallback,
leave: leaveNodeCallback,
});
expect(enterNodeCallback).toHaveBeenCalledTimes(graph.getAllNodes().length);
expect(leaveNodeCallback).toHaveBeenCalledTimes(graph.getAllNodes().length);

const nodeA = 'A';
const nodeB = 'B';
const nodeC = 'C';
const nodeD = 'D';
const nodeE = 'E';
const nodeF = 'F';
const nodeG = 'G';

const enterNodeParamsMap = [
{ currentNode: nodeA, previousNode: '' },
{ currentNode: nodeB, previousNode: nodeA },
{ currentNode: nodeD, previousNode: nodeB },
{ currentNode: nodeE, previousNode: nodeD },
{ currentNode: nodeC, previousNode: nodeE },
{ currentNode: nodeF, previousNode: nodeC },
{ currentNode: nodeG, previousNode: nodeF },
];

for (let callIndex = 0; callIndex < 6; callIndex += 1) {
const params = enterNodeCallback.mock.calls[callIndex][0];
expect(params.current).toEqual(enterNodeParamsMap[callIndex].currentNode);
expect(params.previous).toEqual(
enterNodeParamsMap[callIndex].previousNode &&
enterNodeParamsMap[callIndex].previousNode,
);
}

const leaveNodeParamsMap = [
{ currentNode: nodeA, previousNode: '' },
{ currentNode: nodeB, previousNode: nodeA },
{ currentNode: nodeD, previousNode: nodeB },
{ currentNode: nodeE, previousNode: nodeD },
{ currentNode: nodeC, previousNode: nodeE },
{ currentNode: nodeF, previousNode: nodeC },
{ currentNode: nodeG, previousNode: nodeF },
];

for (let callIndex = 0; callIndex < 6; callIndex += 1) {
const params = leaveNodeCallback.mock.calls[callIndex][0];
expect(params.current).toEqual(leaveNodeParamsMap[callIndex].currentNode);
expect(params.previous).toEqual(
leaveNodeParamsMap[callIndex].previousNode &&
leaveNodeParamsMap[callIndex].previousNode,
);
}
});

it('should allow to create custom node visiting logic', () => {

const enterNodeCallback = jest.fn();
const leaveNodeCallback = jest.fn();

// Traverse graph with enterNode and leaveNode callbacks.
breadthFirstSearch(graph, 'A', {
enter: enterNodeCallback,
leave: leaveNodeCallback,
allowTraversal: ({ current, next }) => {
return !(current === 'A' && next === 'B');
},
});

expect(enterNodeCallback).toHaveBeenCalledTimes(4);
expect(leaveNodeCallback).toHaveBeenCalledTimes(4);

const enterNodeParamsMap = [
{ currentNode: 'A', previousNode: '' },
{ currentNode: 'D', previousNode: 'A' },
{ currentNode: 'E', previousNode: 'D' },
{ currentNode: 'F', previousNode: 'E' },
{ currentNode: 'D', previousNode: 'F' },
];

for (let callIndex = 0; callIndex < 4; callIndex += 1) {
const params = enterNodeCallback.mock.calls[callIndex][0];
expect(params.current).toEqual(enterNodeParamsMap[callIndex].currentNode);
expect(params.previous).toEqual(
enterNodeParamsMap[callIndex].previousNode,
);
}

const leaveNodeParamsMap = [
{ currentNode: 'A', previousNode: '' },
{ currentNode: 'D', previousNode: 'A' },
{ currentNode: 'E', previousNode: 'D' },
{ currentNode: 'F', previousNode: 'E' },
{ currentNode: 'D', previousNode: 'F' },
];

for (let callIndex = 0; callIndex < 4; callIndex += 1) {
const params = leaveNodeCallback.mock.calls[callIndex][0];
expect(params.current).toEqual(leaveNodeParamsMap[callIndex].currentNode);
expect(params.previous).toEqual(
leaveNodeParamsMap[callIndex].previousNode,
);
}
});
});
183 changes: 183 additions & 0 deletions __tests__/unit/dfs.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import depthFirstSearch from "../../packages/graph/src/dfs";
import { Graph } from "@antv/graphlib";


const data = {
nodes: [
{
id: 'A',
data: {}
},
{
id: 'B',
data: {}
},
{
id: 'C',
data: {}
},
{
id: 'D',
data: {}
},
{
id: 'E',
data: {}
},
{
id: 'F',
data: {}
},
{
id: 'G',
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 graph = new Graph<any, any>(data);
describe('depthFirstSearch', () => {
it('should perform DFS operation on graph', () => {

const enterNodeCallback = jest.fn();
const leaveNodeCallback = jest.fn();

// Traverse graphs without callbacks first to check default ones.
depthFirstSearch(graph, 'A');

// Traverse graph with enterNode and leaveNode callbacks.
depthFirstSearch(graph, 'A', {
enter: enterNodeCallback,
leave: leaveNodeCallback,
});
expect(enterNodeCallback).toHaveBeenCalledTimes(graph.getAllNodes().length);
expect(leaveNodeCallback).toHaveBeenCalledTimes(graph.getAllNodes().length);

const enterNodeParamsMap = [
{ currentNode: 'A', previousNode: '' },
{ currentNode: 'B', previousNode: 'A' },
{ currentNode: 'C', previousNode: 'B' },
{ currentNode: 'G', previousNode: 'C' },
{ currentNode: 'D', previousNode: 'A' },
{ currentNode: 'F', previousNode: 'D' },
{ currentNode: 'E', previousNode: 'F' },
];
for (let callIndex = 0; callIndex < data.nodes.length; callIndex += 1) {
const params = enterNodeCallback.mock.calls[callIndex][0];
expect(params.previous).toEqual(
enterNodeParamsMap[callIndex].previousNode,
);
}

depthFirstSearch(graph, 'B', {
enter: enterNodeCallback,
leave: leaveNodeCallback,
});
const leaveNodeParamsMap = [
{ currentNode: 'G', previousNode: 'C' },
{ currentNode: 'C', previousNode: 'B' },
{ currentNode: 'B', previousNode: 'A' },
{ currentNode: 'E', previousNode: 'F' },
{ currentNode: 'F', previousNode: 'D' },
{ currentNode: 'D', previousNode: 'A' },
{ currentNode: 'A', previousNode: '' },
];

for (let callIndex = 0; callIndex < data.nodes.length; callIndex += 1) {
const params = leaveNodeCallback.mock.calls[callIndex][0];
expect(params.previous).toEqual(
leaveNodeParamsMap[callIndex].previousNode,
);
}
});

it('allow users to redefine node visiting logic', () => {
const enterNodeCallback = jest.fn();
const leaveNodeCallback = jest.fn();
depthFirstSearch(graph, 'A', {
enter: enterNodeCallback,
leave: leaveNodeCallback,
allowTraversal: ({ current: currentNode, next: nextNode }) => {
return !(currentNode === 'A' && nextNode === 'B');
},
});
expect(enterNodeCallback).toHaveBeenCalledTimes(4);
expect(leaveNodeCallback).toHaveBeenCalledTimes(4);

const enterNodeParamsMap = [
{ currentNode: 'A', previousNode: '' },
{ currentNode: 'D', previousNode: 'A' },
{ currentNode: 'F', previousNode: 'D' },
{ currentNode: 'E', previousNode: 'F' },
];

for (let callIndex = 0; callIndex < 4; callIndex += 1) {
const params = enterNodeCallback.mock.calls[callIndex][0];
expect(params.previous && params.previous).toEqual(
enterNodeParamsMap[callIndex].previousNode,
);
}
const leaveNodeParamsMap = [
{ currentNode: 'E', previousNode: 'F' },
{ currentNode: 'F', previousNode: 'D' },
{ currentNode: 'D', previousNode: 'A' },
{ currentNode: 'A', previousNode: '' },
];
for (let callIndex = 0; callIndex < 4; callIndex += 1) {
const params = leaveNodeCallback.mock.calls[callIndex][0];
expect(params.current).toEqual(leaveNodeParamsMap[callIndex].currentNode);
expect(params.previous).toEqual(
leaveNodeParamsMap[callIndex].previousNode,
);
}
});
});
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"build:ci": "pnpm -r run build:ci",
"prepare": "husky install",
"test": "jest",
"test_one": "jest ./__tests__/unit/kCore.spec.ts",
"test_one": "jest ./__tests__/unit/dfs.spec.ts",
"coverage": "jest --coverage",
"build:site": "vite build",
"deploy": "gh-pages -d site/dist",
Expand Down
Loading