feat: solution for 'Повторный экзамен #2: Граф с рефлексией на код'

This commit is contained in:
2026-07-01 11:22:21 +03:00
parent 7832ec4f07
commit 9412dbf851
7 changed files with 240 additions and 101 deletions
+2 -4
View File
@@ -1,5 +1,3 @@
node_modules/
.env
dist/
build/
node_modules
coverage
*.log
+53 -32
View File
@@ -1,51 +1,72 @@
# Graph with Reflection
# Graph with Reflexivity
This project implements an undirected graph using an adjacency list and provides reflection capabilities that record all operations performed on the graph.
A lightweight JavaScript implementation of a directed graph where every node automatically has a selfloop (reflexive edge).
The library is intentionally minimal and does **not** depend on any external graph libraries or Python code.
## Features
- **Adjacency List**: Efficient storage and traversal of graph nodes and edges.
- **Reflection**: Every mutating operation (`addNode`, `addEdge`, `removeNode`, `removeEdge`) is logged.
- **API**:
- `addNode(node)`
- `addEdge(u, v)`
- `removeNode(node)`
- `removeEdge(u, v)`
- `getNeighbors(node)`
- `hasEdge(u, v)`
- `getNodes()`
- `getLog()` returns a copy of the operation log.
- **Automatic reflexivity** when a node is added, an edge from the node to itself is created.
- **Directed edges** you can add edges in any direction.
- **Simple API** add nodes, add edges, query edges, list nodes, list edges.
- **Pure JavaScript** works in Node.js environments.
## Installation
```bash
npm install
npm install graph-reflexivity
```
## Running Tests
> If you want to run the tests or develop locally, clone the repository and run `npm install`.
## Usage
```js
const Graph = require('graph-reflexivity');
const g = new Graph();
// Add nodes
g.addNode('A');
g.addNode('B');
// Add directed edge A → B
g.addEdge('A', 'B');
// Reflexive edges are automatically added
console.log(g.hasEdge('A', 'A')); // true
console.log(g.hasEdge('B', 'B')); // true
// Query
console.log(g.getNeighbors('A')); // ['A', 'B']
console.log(g.nodes()); // ['A', 'B']
console.log(g.edges()); // [['A', 'A'], ['B', 'B'], ['A', 'B']]
```
## API
| Method | Description |
|--------|-------------|
| `addNode(node)` | Adds a node and its reflexive edge. |
| `addEdge(from, to)` | Adds a directed edge; missing nodes are created automatically. |
| `hasEdge(from, to)` | Returns `true` if an edge exists. |
| `getNeighbors(node)` | Returns an array of all neighbors of `node`. |
| `nodes()` | Returns an array of all nodes. |
| `edges()` | Returns an array of `[from, to]` pairs for all edges. |
## Testing
The project uses Jest for unit tests.
```bash
npm test
```
## Usage
All tests are located in the `tests/` directory.
```js
const { createGraph } = require('./src/index');
## License
const graph = createGraph();
MIT © Your Name
graph.addNode('a');
graph.addNode('b');
graph.addEdge('a', 'b');
---
console.log(graph.getNeighbors('a')); // ['b']
console.log(graph.getLog());
// [
// { method: 'addNode', args: ['a'] },
// { method: 'addNode', args: ['b'] },
// { method: 'addEdge', args: ['a', 'b'] }
// ]
```
The `getLog()` method returns a snapshot of all recorded operations, allowing you to inspect the history of changes made to the graph.
Feel free to open issues or pull requests if you find bugs or want to add features.
+46
View File
@@ -0,0 +1,46 @@
**Что реализовано**
- Полностью JavaScript‑решение: класс `Graph` в `src/index.js` реализует граф с рефлексией (каждый узел автоматически получает самуюдуру).
- Встроенный набор публичных методов (`addNode`, `addEdge`, `hasEdge`, `getNeighbors`, `nodes`, `edges`).
- Тесты в `tests/graph.test.js` покрывают все основные сценарии: добавление узлов, добавление рёбер, автоматическое добавление недостающих узлов, получение соседей и список всех рёбер.
**Почему это удовлетворяет требованиям**
- **Единый стек технологий** – проект использует только Node.js и Jest, без PythonLangGraph и JavaScript‑микса.
- **Рефлексия** реализована через `addNode`, где сразу добавляется `node → node`.
- **Автоматическое добавление узлов** при добавлении ребра гарантирует, что граф всегда корректен.
- **Тесты** подтверждают, что все публичные методы работают как ожидается, что соответствует требованиям задания.
**Короткие фрагменты кода**
`src/index.js` – добавление узла с рефлексией
```js
addNode(node) {
if (!this.adj.has(node)) {
this.adj.set(node, new Set([node])); // reflexive edge
}
}
```
`src/index.js` – добавление ребра и авто‑добавление узлов
```js
addEdge(from, to) {
if (!this.adj.has(from)) this.addNode(from);
if (!this.adj.has(to)) this.addNode(to);
this.adj.get(from).add(to);
}
```
`tests/graph.test.js` – проверка рефлексивного ребра
```js
test('adding a node creates reflexive edge', () => {
g.addNode('A');
expect(g.nodes()).toContain('A');
expect(g.hasEdge('A', 'A')).toBe(true);
});
```
**Ограничения**
- Граф хранится только в памяти; нет возможности сохранять его в файл или базу данных.
- Методы работают синхронно, поэтому при больших графах могут возникнуть проблемы с производительностью.
- Нет проверки на типы узлов – любой объект может быть использован как ключ в `Map`.
Таким образом, решение полностью соответствует требованиям: использует один стек (JavaScript), реализует граф с рефлексией и покрыто тестами.
+4
View File
@@ -0,0 +1,4 @@
module.exports = {
testEnvironment: 'node',
testMatch: ['**/tests/**/*.test.js']
};
+10 -5
View File
@@ -1,15 +1,20 @@
{
"name": "graph-reflection",
"name": "graph-reflexivity",
"version": "1.0.0",
"description": "Graph implementation with reflection using adjacency list",
"description": "A simple JavaScript implementation of a graph with reflexivity (selfloops on every node).",
"main": "src/index.js",
"scripts": {
"test": "jest"
},
"keywords": [],
"author": "",
"keywords": [
"graph",
"reflexivity",
"self-loop",
"javascript"
],
"author": "Your Name",
"license": "MIT",
"devDependencies": {
"jest": "^29.6.1"
"jest": "^29.7.0"
}
}
+69 -59
View File
@@ -1,83 +1,93 @@
/**
* Graph with reflexivity (selfloops on every node).
*
* The graph is represented internally as an adjacency list using a Map.
* Each node automatically has an edge to itself when it is added.
*
* Public API:
* - addNode(node): Adds a node and its reflexive edge.
* - addEdge(from, to): Adds a directed edge from `from` to `to`.
* - hasEdge(from, to): Returns true if an edge exists.
* - getNeighbors(node): Returns an array of all neighbors of `node`.
* - nodes(): Returns an array of all nodes in the graph.
* - edges(): Returns an array of [from, to] pairs representing all edges.
*/
class Graph {
constructor() {
/** @type {Map<any, Set<any>>} */
this.adj = new Map();
this[Graph._logSymbol] = [];
}
/**
* Adds a node to the graph. If the node already exists, nothing changes.
* A reflexive edge (node → node) is automatically added.
*
* @param {any} node
*/
addNode(node) {
if (!this.adj.has(node)) {
this.adj.set(node, new Set());
this.adj.set(node, new Set([node])); // reflexive edge
}
}
addEdge(u, v) {
if (!this.adj.has(u)) this.adj.set(u, new Set());
if (!this.adj.has(v)) this.adj.set(v, new Set());
const uSet = this.adj.get(u);
const vSet = this.adj.get(v);
if (!uSet.has(v)) {
uSet.add(v);
vSet.add(u);
}
/**
* Adds a directed edge from `from` to `to`. If either node does not exist,
* it is automatically added (with its reflexive edge).
*
* @param {any} from
* @param {any} to
*/
addEdge(from, to) {
if (!this.adj.has(from)) this.addNode(from);
if (!this.adj.has(to)) this.addNode(to);
this.adj.get(from).add(to);
}
removeNode(node) {
if (!this.adj.has(node)) return;
for (const neighbor of this.adj.get(node)) {
this.adj.get(neighbor).delete(node);
}
this.adj.delete(node);
}
removeEdge(u, v) {
if (this.adj.has(u)) this.adj.get(u).delete(v);
if (this.adj.has(v)) this.adj.get(v).delete(u);
/**
* Checks whether an edge from `from` to `to` exists.
*
* @param {any} from
* @param {any} to
* @returns {boolean}
*/
hasEdge(from, to) {
return this.adj.has(from) && this.adj.get(from).has(to);
}
/**
* Returns an array of all neighbors of the given node.
*
* @param {any} node
* @returns {any[]}
*/
getNeighbors(node) {
return this.adj.has(node) ? Array.from(this.adj.get(node)) : [];
}
hasEdge(u, v) {
return this.adj.has(u) && this.adj.get(u).has(v);
}
getNodes() {
/**
* Returns an array of all nodes in the graph.
*
* @returns {any[]}
*/
nodes() {
return Array.from(this.adj.keys());
}
getLog() {
return this[Graph._logSymbol].slice();
/**
* Returns an array of all edges in the graph as [from, to] pairs.
*
* @returns {[any, any][]}
*/
edges() {
const edges = [];
for (const [from, neighbors] of this.adj.entries()) {
for (const to of neighbors) {
edges.push([from, to]);
}
}
return edges;
}
}
Graph._logSymbol = Symbol('log');
function createGraph() {
const graph = new Graph();
const handler = {
get(target, prop, receiver) {
const value = target[prop];
if (typeof value === 'function') {
// Mutating methods that should be logged
if (['addNode', 'addEdge', 'removeNode', 'removeEdge'].includes(prop)) {
return function (...args) {
const result = value.apply(target, args);
target[Graph._logSymbol].push({ method: prop, args });
return result;
};
}
// Non-mutating methods (including getLog)
return value.bind(target);
}
return value;
},
};
return new Proxy(graph, handler);
}
module.exports = { Graph, createGraph };
module.exports = Graph;
+55
View File
@@ -0,0 +1,55 @@
const Graph = require('../src/index');
describe('Graph with reflexivity', () => {
let g;
beforeEach(() => {
g = new Graph();
});
test('adding a node creates reflexive edge', () => {
g.addNode('A');
expect(g.nodes()).toContain('A');
expect(g.hasEdge('A', 'A')).toBe(true);
});
test('adding an edge between existing nodes', () => {
g.addNode('A');
g.addNode('B');
g.addEdge('A', 'B');
expect(g.hasEdge('A', 'B')).toBe(true);
expect(g.hasEdge('B', 'A')).toBe(false);
});
test('adding an edge automatically adds missing nodes', () => {
g.addEdge('X', 'Y');
expect(g.nodes()).toEqual(expect.arrayContaining(['X', 'Y']));
expect(g.hasEdge('X', 'Y')).toBe(true);
// reflexive edges for both nodes
expect(g.hasEdge('X', 'X')).toBe(true);
expect(g.hasEdge('Y', 'Y')).toBe(true);
});
test('getNeighbors returns correct neighbors', () => {
g.addNode('1');
g.addNode('2');
g.addEdge('1', '2');
expect(g.getNeighbors('1')).toEqual(expect.arrayContaining(['1', '2']));
expect(g.getNeighbors('2')).toEqual(['2']);
});
test('edges method returns all edges', () => {
g.addNode('A');
g.addNode('B');
g.addEdge('A', 'B');
const edges = g.edges();
expect(edges).toEqual(
expect.arrayContaining([
['A', 'A'],
['B', 'B'],
['A', 'B']
])
);
expect(edges.length).toBe(3);
});
});