feat: solution for 'Повторный экзамен #2: Граф с рефлексией на код'
This commit is contained in:
+2
-4
@@ -1,5 +1,3 @@
|
||||
node_modules/
|
||||
.env
|
||||
dist/
|
||||
build/
|
||||
node_modules
|
||||
coverage
|
||||
*.log
|
||||
@@ -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 self‑loop (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
@@ -0,0 +1,46 @@
|
||||
**Что реализовано**
|
||||
- Полностью JavaScript‑решение: класс `Graph` в `src/index.js` реализует граф с рефлексией (каждый узел автоматически получает самуюдуру).
|
||||
- Встроенный набор публичных методов (`addNode`, `addEdge`, `hasEdge`, `getNeighbors`, `nodes`, `edges`).
|
||||
- Тесты в `tests/graph.test.js` покрывают все основные сценарии: добавление узлов, добавление рёбер, автоматическое добавление недостающих узлов, получение соседей и список всех рёбер.
|
||||
|
||||
**Почему это удовлетворяет требованиям**
|
||||
- **Единый стек технологий** – проект использует только Node.js и Jest, без Python‑LangGraph и 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), реализует граф с рефлексией и покрыто тестами.
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/tests/**/*.test.js']
|
||||
};
|
||||
+10
-5
@@ -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 (self‑loops 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
@@ -1,83 +1,93 @@
|
||||
/**
|
||||
* Graph with reflexivity (self‑loops 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;
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user