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

This commit is contained in:
2026-06-30 17:00:41 +03:00
parent 898f43bf73
commit 7832ec4f07
4 changed files with 193 additions and 120 deletions
+39 -48
View File
@@ -1,60 +1,51 @@
# Graph with Reflection on Code Refactored Implementation
# Graph with Reflection
This repository contains a minimal, selfcontained Python implementation of an undirected graph that uses a single, consistent approach: an adjacency list represented by a dictionary of sets.
The original assignment required that the solution use only one approach; this refactor removes any mixedstrategy code and provides a clean, welldocumented API.
This project implements an undirected graph using an adjacency list and provides reflection capabilities that record all operations performed on the graph.
## Features
- **Add / remove nodes** Nodes are any hashable Python objects.
- **Add / remove edges** Undirected edges; selfloops (reflexive edges) are allowed.
- **Query adjacency** Retrieve neighbors, check for an edge, list all nodes or edges.
- **Automatic node creation** Adding an edge automatically creates missing nodes.
- **Readable representation** `__repr__` and `__str__` give a quick overview of the graph.
- **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.
## Installation
```bash
npm install
```
## Running Tests
```bash
npm test
```
## Usage
```python
from src.index import Graph
```js
const { createGraph } = require('./src/index');
# Create an empty graph
g = Graph()
const graph = createGraph();
# Add edges (nodes are created automatically)
g.add_edge("A", "B")
g.add_edge("B", "C")
g.add_edge("C", "A") # triangle
g.add_edge("D", "D") # reflexive edge
graph.addNode('a');
graph.addNode('b');
graph.addEdge('a', 'b');
print(g) # Pretty print
# Query
print("Neighbors of B:", g.neighbors("B"))
print("Has edge (A, D)?", g.has_edge("A", "D"))
# Modify
g.remove_edge("A", "B")
g.remove_node("C")
print("After modifications:")
print(g)
console.log(graph.getNeighbors('a')); // ['b']
console.log(graph.getLog());
// [
// { method: 'addNode', args: ['a'] },
// { method: 'addNode', args: ['b'] },
// { method: 'addEdge', args: ['a', 'b'] }
// ]
```
## Running the Example
```bash
python -m src.index
```
The script will output the graph state after each operation.
## Project Structure
```
src/
└── index.py # Graph implementation
README.md # Documentation
```
## License
This project is released under the MIT License.
The `getLog()` method returns a snapshot of all recorded operations, allowing you to inspect the history of changes made to the graph.
+5 -9
View File
@@ -1,19 +1,15 @@
{
"name": "graph-reflexive",
"name": "graph-reflection",
"version": "1.0.0",
"description": "Graph implementation with reflexive property using adjacency list",
"description": "Graph implementation with reflection using adjacency list",
"main": "src/index.js",
"scripts": {
"test": "jest"
},
"keywords": [
"graph",
"reflexive",
"adjacency-list"
],
"author": "Auto-generated",
"keywords": [],
"author": "",
"license": "MIT",
"devDependencies": {
"jest": "^29.7.0"
"jest": "^29.6.1"
}
}
+81 -3
View File
@@ -1,5 +1,83 @@
const Graph = require('./graph');
class Graph {
constructor() {
this.adj = new Map();
this[Graph._logSymbol] = [];
}
module.exports = {
Graph
addNode(node) {
if (!this.adj.has(node)) {
this.adj.set(node, new Set());
}
}
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);
}
}
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);
}
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() {
return Array.from(this.adj.keys());
}
getLog() {
return this[Graph._logSymbol].slice();
}
}
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 };
+67 -59
View File
@@ -1,79 +1,87 @@
const { Graph } = require('../src');
describe('Graph', () => {
let g;
const { createGraph } = require('../src/index');
describe('Graph with reflection', () => {
let graph;
beforeEach(() => {
g = new Graph();
graph = createGraph();
});
test('initially empty', () => {
expect(g.size()).toBe(0);
expect(g.edgesCount()).toBe(0);
test('initial log is empty', () => {
expect(graph.getLog()).toEqual([]);
});
test('addVertex increases size and adds reflexive edge', () => {
g.addVertex('a');
expect(g.size()).toBe(1);
expect(g.isReflexive()).toBe(true);
expect(g.hasEdge('a', 'a')).toBe(true);
test('addNode records operation', () => {
graph.addNode('a');
expect(graph.getLog()).toEqual([{ method: 'addNode', args: ['a'] }]);
expect(graph.getNodes()).toEqual(['a']);
});
test('addEdge connects vertices and updates adjacency', () => {
g.addEdge('a', 'b');
expect(g.size()).toBe(2);
expect(g.hasEdge('a', 'b')).toBe(true);
expect(g.hasEdge('b', 'a')).toBe(true);
expect(g.getNeighbors('a')).toEqual(expect.arrayContaining(['a', 'b']));
expect(g.getNeighbors('b')).toEqual(expect.arrayContaining(['a', 'b']));
test('addEdge records operation and creates nodes', () => {
graph.addEdge('a', 'b');
expect(graph.getLog()).toEqual([{ method: 'addEdge', args: ['a', 'b'] }]);
expect(graph.getNodes().sort()).toEqual(['a', 'b']);
expect(graph.getNeighbors('a')).toEqual(['b']);
expect(graph.getNeighbors('b')).toEqual(['a']);
expect(graph.hasEdge('a', 'b')).toBe(true);
});
test('removeEdge removes connection', () => {
g.addEdge('a', 'b');
g.removeEdge('a', 'b');
expect(g.hasEdge('a', 'b')).toBe(false);
expect(g.hasEdge('b', 'a')).toBe(false);
// self-loops remain
expect(g.hasEdge('a', 'a')).toBe(true);
expect(g.hasEdge('b', 'b')).toBe(true);
test('removeEdge records operation', () => {
graph.addEdge('a', 'b');
graph.removeEdge('a', 'b');
expect(graph.getLog()).toEqual([
{ method: 'addEdge', args: ['a', 'b'] },
{ method: 'removeEdge', args: ['a', 'b'] }
]);
expect(graph.hasEdge('a', 'b')).toBe(false);
});
test('removeVertex removes vertex and incident edges', () => {
g.addEdge('a', 'b');
g.addEdge('a', 'c');
g.removeVertex('a');
expect(g.size()).toBe(2);
expect(g.hasEdge('b', 'a')).toBe(false);
expect(g.hasEdge('c', 'a')).toBe(false);
expect(g.hasEdge('b', 'c')).toBe(false);
test('removeNode records operation and removes edges', () => {
graph.addEdge('a', 'b');
graph.addEdge('a', 'c');
graph.removeNode('a');
expect(graph.getLog()).toEqual([
{ method: 'addEdge', args: ['a', 'b'] },
{ method: 'addEdge', args: ['a', 'c'] },
{ method: 'removeNode', args: ['a'] }
]);
expect(graph.getNodes().sort()).toEqual(['b', 'c']);
expect(graph.getNeighbors('b')).toEqual([]);
expect(graph.getNeighbors('c')).toEqual([]);
});
test('edges and edgesCount work correctly', () => {
g.addEdge('a', 'b');
g.addEdge('b', 'c');
g.addEdge('c', 'a');
expect(g.edgesCount()).toBe(3);
const edges = g.edges();
expect(edges).toEqual(expect.arrayContaining([
['a', 'b'],
['b', 'c'],
['c', 'a']
]));
test('getNeighbors does not record operation', () => {
graph.addNode('a');
graph.getNeighbors('a');
expect(graph.getLog()).toEqual([{ method: 'addNode', args: ['a'] }]);
});
test('self-loop handling', () => {
g.addVertex('x');
expect(g.hasEdge('x', 'x')).toBe(true);
g.removeEdge('x', 'x');
expect(g.hasEdge('x', 'x')).toBe(false);
test('log is a copy and not affected by external mutation', () => {
graph.addNode('a');
const log = graph.getLog();
log.push({ method: 'fake', args: [] });
expect(graph.getLog()).toEqual([{ method: 'addNode', args: ['a'] }]);
});
test('reflexivity can be toggled', () => {
g.addVertex('p');
g.addVertex('q');
g.removeReflexive();
expect(g.isReflexive()).toBe(false);
g.makeReflexive();
expect(g.isReflexive()).toBe(true);
test('graph uses adjacency list internally', () => {
graph.addNode('a');
expect(graph.adj instanceof Map).toBe(true);
expect(graph.adj.get('a') instanceof Set).toBe(true);
});
test('handles duplicate nodes and edges gracefully', () => {
graph.addNode('a');
graph.addNode('a');
expect(graph.getNodes()).toEqual(['a']);
graph.addEdge('a', 'a');
expect(graph.hasEdge('a', 'a')).toBe(true);
graph.addEdge('a', 'a');
expect(graph.getNeighbors('a')).toEqual(['a']);
});
test('handles non-existing nodes and edges', () => {
expect(graph.getNeighbors('x')).toEqual([]);
expect(graph.hasEdge('x', 'y')).toBe(false);
graph.removeEdge('x', 'y'); // should not throw
graph.removeNode('x'); // should not throw
});
});