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

This commit is contained in:
2026-07-01 15:59:36 +03:00
parent 2b6ecd84d0
commit 1c6bbc04af
5 changed files with 347 additions and 86 deletions
+105
View File
@@ -0,0 +1,105 @@
const { Graph, Node, ReflectionNode, RewritingNode } = require('../index');
describe('Graph with Reflection and Rewriting Nodes', () => {
test('ReflectionNode creates reflected nodes with copied edges', () => {
const graph = new Graph();
const a = new Node('A');
const b = new Node('B');
const c = new Node('C');
graph.addNode(a);
graph.addNode(b);
graph.addNode(c);
graph.addEdge('A', 'B');
graph.addEdge('B', 'C');
const r = new ReflectionNode('R');
graph.addNode(r);
graph.addEdge('R', 'B');
r.reflect(graph);
const bRef = graph.getNode('B_ref');
expect(bRef).toBeDefined();
expect(bRef.type).toBe('generic');
const edges = graph.edges.get('B_ref');
expect(edges).toContain('C');
});
test('RewritingNode replaces target node with new node', () => {
const graph = new Graph();
const a = new Node('A');
const b = new Node('B');
const c = new Node('C');
graph.addNode(a);
graph.addNode(b);
graph.addNode(c);
graph.addEdge('A', 'B');
graph.addEdge('B', 'C');
const w = new RewritingNode('W');
graph.addNode(w);
graph.addEdge('W', 'C');
const d = new Node('D');
w.rewrite(graph, 'C', d);
expect(graph.getNode('C')).toBeUndefined();
expect(graph.getNode('D')).toBeDefined();
const edges = graph.edges.get('B');
expect(edges).toContain('D');
});
test('Circular references are handled without infinite recursion', () => {
const graph = new Graph();
const x = new Node('X');
const y = new Node('Y');
graph.addNode(x);
graph.addNode(y);
graph.addEdge('X', 'Y');
graph.addEdge('Y', 'X');
const r = new ReflectionNode('R');
graph.addNode(r);
graph.addEdge('R', 'X');
expect(() => r.reflect(graph)).not.toThrow();
const xRef = graph.getNode('X_ref');
expect(xRef).toBeDefined();
const edges = graph.edges.get('X_ref');
expect(edges).toContain('Y');
});
test('Graph traversal works correctly after reflection and rewriting', () => {
const graph = new Graph();
const a = new Node('A');
const b = new Node('B');
const c = new Node('C');
graph.addNode(a);
graph.addNode(b);
graph.addNode(c);
graph.addEdge('A', 'B');
graph.addEdge('B', 'C');
const r = new ReflectionNode('R');
graph.addNode(r);
graph.addEdge('R', 'B');
r.reflect(graph);
const w = new RewritingNode('W');
graph.addNode(w);
graph.addEdge('W', 'C');
const d = new Node('D');
w.rewrite(graph, 'C', d);
const traversal = graph.traverse('A');
// Should visit A, B, D, and B_ref (which points to D)
expect(traversal).toContain('A');
expect(traversal).toContain('B');
expect(traversal).toContain('D');
expect(traversal).toContain('B_ref');
// Ensure no duplicate nodes in traversal
const unique = new Set(traversal);
expect(unique.size).toBe(traversal.length);
});
});
+136 -2
View File
@@ -1,2 +1,136 @@
export { Graph } from './graph.js';
export { Node, ReflectionNode, RewritingNode } from './nodes.js';
const { strict: assert } = require('assert');
class Node {
constructor(id, type = 'generic') {
this.id = id;
this.type = type;
}
}
class ReflectionNode extends Node {
constructor(id) {
super(id, 'reflection');
}
/**
* Reflects all outgoing edges of this node by creating copies of the target nodes.
* @param {Graph} graph - The graph instance to operate on.
*/
reflect(graph) {
const targets = graph.edges.get(this.id) || new Set();
for (const targetId of targets) {
const targetNode = graph.getNode(targetId);
if (!targetNode) continue;
const newId = `${targetId}_ref`;
// Avoid duplicate reflection
if (graph.getNode(newId)) continue;
const newNode = new Node(newId, targetNode.type);
graph.addNode(newNode);
const targetTargets = graph.edges.get(targetId) || new Set();
for (const tt of targetTargets) {
graph.addEdge(newId, tt);
}
}
}
}
class RewritingNode extends Node {
constructor(id) {
super(id, 'rewriting');
}
/**
* Rewrites a target node in the graph with a new node.
* @param {Graph} graph - The graph instance to operate on.
* @param {string} targetId - The id of the node to replace.
* @param {Node} newNode - The new node that will replace the target.
*/
rewrite(graph, targetId, newNode) {
graph.replaceNode(targetId, newNode);
}
}
class Graph {
constructor() {
this.nodes = new Map(); // id -> Node
this.edges = new Map(); // id -> Set of target ids
}
addNode(node) {
assert(node && node.id, 'Node must have an id');
this.nodes.set(node.id, node);
if (!this.edges.has(node.id)) {
this.edges.set(node.id, new Set());
}
}
addEdge(fromId, toId) {
assert(this.nodes.has(fromId), `Source node ${fromId} does not exist`);
assert(this.nodes.has(toId), `Target node ${toId} does not exist`);
if (!this.edges.has(fromId)) {
this.edges.set(fromId, new Set());
}
this.edges.get(fromId).add(toId);
}
getNode(id) {
return this.nodes.get(id);
}
/**
* Replaces an existing node with a new node, preserving edges.
* @param {string} oldId - The id of the node to replace.
* @param {Node} newNode - The new node that will replace the old one.
*/
replaceNode(oldId, newNode) {
if (!this.nodes.has(oldId)) {
throw new Error(`Node ${oldId} not found`);
}
const oldTargets = this.edges.get(oldId) ? new Set(this.edges.get(oldId)) : new Set();
// Remove old node and its edges
this.edges.delete(oldId);
this.nodes.delete(oldId);
// Add new node
this.addNode(newNode);
// Rewire edges from other nodes that pointed to oldId
for (const [from, targets] of this.edges.entries()) {
if (targets.has(oldId)) {
targets.delete(oldId);
targets.add(newNode.id);
}
}
// Add edges from new node to oldTargets
for (const target of oldTargets) {
this.addEdge(newNode.id, target);
}
}
/**
* Depth-first traversal starting from a node.
* @param {string} startId - The starting node id.
* @param {Set<string>} visited - Internal set to track visited nodes.
* @returns {string[]} - Array of visited node ids in traversal order.
*/
traverse(startId, visited = new Set()) {
if (!this.nodes.has(startId)) return [];
if (visited.has(startId)) return [];
visited.add(startId);
const result = [startId];
const targets = this.edges.get(startId) || new Set();
for (const t of targets) {
result.push(...this.traverse(t, visited));
}
return result;
}
}
module.exports = {
Node,
ReflectionNode,
RewritingNode,
Graph,
};