Skip to content
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

feat: Is connected vertex #47

Merged
merged 3 commits into from
Sep 6, 2024
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
28 changes: 28 additions & 0 deletions lib/graph/file.graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,34 @@ class FileGraphIml implements FileGraphAbstract {

return resultVertices;
}
public async isConnected(
sourceVertexId: uuidType,
targetVertexId: uuidType,
): Promise<boolean> {
const startingVertex = await this.findOne(
vertex => vertex.id === sourceVertexId,
);
if (!startingVertex) throw createError('VERTEX_NOT_FOUND', sourceVertexId);
const stack: IVertex<object>[] = [startingVertex];
const visited = new Set();
while (stack.length > 0) {
const currentVertex = stack.pop();
if (!currentVertex) continue;
if (currentVertex.id === targetVertexId) return true;
visited.add(currentVertex.id);

const vertexLinks = currentVertex.links;
if (!vertexLinks.length) continue;

await this.storageFile.searchLine(linkedVertex => {
const isUnvisited =
vertexLinks.includes(linkedVertex.id) &&
!visited.has(linkedVertex.id);
if (isUnvisited) stack.push(linkedVertex);
});
}
return false;
}

private updateArc(
targetVertexId: uuidType,
Expand Down
12 changes: 12 additions & 0 deletions lib/interfaces/graph.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,16 @@ export abstract class FileGraphAbstract {
vertexId: uuidType,
maxLevel: number,
): Promise<IVertex<T>[]>;

/**
* Checks if there is a path between two vertices in the graph using Depth-First Search (DFS).
*
* @param {uuidType} sourceVertexId - The ID of the starting vertex.
* @param {uuidType} targetVertexId - The ID of the target vertex.
* @returns {Promise<boolean>} - Returns a promise that resolves to `true` if a path exists between the vertices, otherwise `false`.
*/
public abstract isConnected(
sourceVertexId: uuidType,
targetVertexId: uuidType,
): Promise<boolean>;
}
74 changes: 41 additions & 33 deletions test/graph.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import assert from 'node:assert';
import { before, describe, it } from 'node:test';
import { before, describe, test } from 'node:test';
import { FileGraph, IUuidArray, uuidType } from 'lib';
import { writeFileSync } from 'node:fs';
import { createError } from 'lib/utils';
Expand All @@ -12,7 +12,7 @@ let globId = '' as uuidType;
before(() => writeFileSync(pathGraph, ''));

describe('Vertex CRUD Operations', () => {
it('create a vertex and find it by ID', async () => {
test('create a vertex and find test by ID', async () => {
const createdVertex = await graph.createVertex(data);
const foundVertex = await graph.findOne<typeof data>(
vertex => vertex.id === createdVertex.id,
Expand All @@ -25,7 +25,7 @@ describe('Vertex CRUD Operations', () => {
globId = createdVertex.id;
});

it('creates multiple vertices', async () => {
test('creates multiple vertices', async () => {
const vertices = [{ name: 'Alex' }, { city: 'LA' }];
const createdVertices = await graph.createVertices(vertices);
assert.strictEqual(createdVertices.length, vertices.length);
Expand All @@ -34,7 +34,7 @@ describe('Vertex CRUD Operations', () => {
);
});

it('find all vertices matching a predicate', async () => {
test('find all vertices matching a predicate', async () => {
const vertices = [{ name: 'Alice' }, { name: 'Bob' }, { name: 'Alice' }];
await graph.createVertices(vertices);
const foundVertices = await graph.findAll<any>(
Expand All @@ -46,22 +46,22 @@ describe('Vertex CRUD Operations', () => {
);
});

it('update the vertex name', async () => {
test('update the vertex name', async () => {
const isUpdated = await graph.updateVertex<typeof data>(
vertex => vertex.id === globId && { data: { name: 'Dupe' } },
);
assert.strictEqual(isUpdated, true);
});

it('verify the updated vertex name', async () => {
test('verify the updated vertex name', async () => {
const foundVertex = await graph.findOne<typeof data>(
vertex => vertex.id === globId,
);

assert.strictEqual(foundVertex?.data.name, 'Dupe');
});

it('create a vertex and delete it', async () => {
test('create a vertex and delete test', async () => {
const createdVertex = await graph.createVertex(data);
const deleteVertex = await graph.deleteVertex<typeof data>(
vertex => vertex.id === createdVertex.id,
Expand All @@ -72,11 +72,10 @@ describe('Vertex CRUD Operations', () => {
});

describe('Links operations', () => {
let createdVertexId: uuidType;

async function createVertexAndArc() {
createdVertexId = (await graph.createVertex(data)).id;
return graph.createArc(globId, createdVertexId);
const createdVertexId = (await graph.createVertex(data)).id;
await graph.createArc(globId, createdVertexId);
return createdVertexId;
}

async function checkLinksPresence(
Expand All @@ -92,27 +91,22 @@ describe('Links operations', () => {
);
}

it('create an arc between two vertices', async () => {
test('create an arc between two vertices', async () => {
const newArcCreated = await createVertexAndArc();

assert.equal(newArcCreated, true, 'Arc creation failed');
await checkLinksPresence(globId, createdVertexId, true);
await checkLinksPresence(globId, newArcCreated, true);
});

it('checks the existence of an arc between two vertices', async () => {
const hasArc = await graph.hasArc(globId, createdVertexId);
assert.equal(hasArc, true);
});
test('remove an arc between two vertices', async () => {
const newArcCreatedId = await createVertexAndArc();

it('remove an arc between two vertices', async () => {
await createVertexAndArc();
const removedArc = await graph.removeArc(globId, createdVertexId);
const removedArc = await graph.removeArc(globId, newArcCreatedId);

assert.equal(removedArc, true, 'Arc removal failed');
await checkLinksPresence(globId, createdVertexId, false);
await checkLinksPresence(globId, newArcCreatedId, false);
});

it('create edges between multiple vertices', async () => {
test('create edges between multiple vertices', async () => {
const createVertices = await graph.createVertices([
{ name: 'Vertex 1' },
{ name: 'Vertex 2' },
Expand All @@ -129,11 +123,11 @@ describe('Links operations', () => {
await checkLinksPresence(ids[i + 1], ids[i], true);
}
});
it('create arcs between multiple vertices', async () => {
test('create arcs between multiple vertices', async () => {
const createVertices = await graph.createVertices([
{ name: 'Vertex 1' },
{ name: 'Vertex 2' },
{ name: 'Vertex 3' },
{ name: 'V-1' },
{ name: 'V-2' },
{ name: 'V-3' },
]);

const ids = createVertices.map(result => result.id) as IUuidArray;
Expand All @@ -144,11 +138,12 @@ describe('Links operations', () => {
for (let i = 0; i < ids.length - 1; i++)
await checkLinksPresence(ids[i], ids[i + 1], true);
});
it('retrieve all vertices up to the specified depth level in a graph', async () => {

test('retrieve all vertices up to the specified depth level in a graph', async () => {
const createVertices = await graph.createVertices([
{ name: 'Vertex 1' },
{ name: 'Vertex 2' },
{ name: 'Vertex 3' },
{ name: 'V-A' },
{ name: 'V-B' },
{ name: 'V-C' },
]);
const ids = createVertices.map(result => result.id) as IUuidArray;
await graph.createArcs(ids);
Expand All @@ -159,7 +154,7 @@ describe('Links operations', () => {
assert.equal(vertex.id, ids[index]);
});
});
it('error for negative level', async () => {
test('error for negative level', async () => {
try {
await graph.findUpToLevel('A' as uuidType, -1);
assert.fail('Expected error not thrown');
Expand All @@ -168,7 +163,7 @@ describe('Links operations', () => {
}
});

it('error if start vertex does not exist', async () => {
test('error if start vertex does not exist', async () => {
try {
await graph.findUpToLevel('NonExistentVertex' as uuidType, 1);
assert.fail('Expected error not thrown');
Expand All @@ -179,4 +174,17 @@ describe('Links operations', () => {
);
}
});

test('check if two vertices are connected', async () => {
const createVertices = await graph.createVertices([
{ name: 'V-0' },
{ name: 'V-0-1' },
{ name: 'V-1' },
]);
const ids = createVertices.map(result => result.id) as IUuidArray;
await graph.createArcs(ids);

const result = await graph.isConnected(ids[0], ids.at(-1));
assert.equal(result, true);
});
});
Loading