From 7832ec4f0788aee92449e6858797ff24c9b3c24e Mon Sep 17 00:00:00 2001 From: kuzakhmetovartur Date: Tue, 30 Jun 2026 17:00:41 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20solution=20for=20'=D0=9F=D0=BE=D0=B2?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D0=BD=D1=8B=D0=B9=20=D1=8D=D0=BA=D0=B7=D0=B0?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=20#2:=20=D0=93=D1=80=D0=B0=D1=84=20=D1=81=20?= =?UTF-8?q?=D1=80=D0=B5=D1=84=D0=BB=D0=B5=D0=BA=D1=81=D0=B8=D0=B5=D0=B9=20?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=BA=D0=BE=D0=B4'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 87 ++++++++++++++----------------- package.json | 14 ++--- src/index.js | 86 +++++++++++++++++++++++++++++-- test/graph.test.js | 126 ++++++++++++++++++++++++--------------------- 4 files changed, 193 insertions(+), 120 deletions(-) diff --git a/README.md b/README.md index 0587c1c..b12694b 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,51 @@ -# Graph with Reflection on Code – Refactored Implementation +# Graph with Reflection -This repository contains a minimal, self‑contained 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 mixed‑strategy code and provides a clean, well‑documented 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; self‑loops (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. \ No newline at end of file +The `getLog()` method returns a snapshot of all recorded operations, allowing you to inspect the history of changes made to the graph. \ No newline at end of file diff --git a/package.json b/package.json index 4c4991c..d05e567 100644 --- a/package.json +++ b/package.json @@ -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" } } \ No newline at end of file diff --git a/src/index.js b/src/index.js index 2dda1cb..4adf141 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,83 @@ -const Graph = require('./graph'); +class Graph { + constructor() { + this.adj = new Map(); + this[Graph._logSymbol] = []; + } -module.exports = { - Graph -}; \ No newline at end of file + 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 }; \ No newline at end of file diff --git a/test/graph.test.js b/test/graph.test.js index 3ad6447..fb18528 100644 --- a/test/graph.test.js +++ b/test/graph.test.js @@ -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 }); }); \ No newline at end of file