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

This commit is contained in:
2026-07-01 16:14:52 +03:00
parent 1c6bbc04af
commit 5801947c0d
8 changed files with 376 additions and 279 deletions
+123 -111
View File
@@ -1,136 +1,148 @@
const { strict: assert } = require('assert');
// src/index.js
//
// A minimal graph implementation that supports custom node types,
// including the required 'Reflection' and 'Rewrite' nodes.
//
// The graph is represented as an adjacency list. Each node has a
// unique id, a type, optional properties, and a list of outgoing
// edges. Edges are represented by the id of the target node.
//
// This module exports a Graph class that can be used to build and
// manipulate the graph. It also exports a small demo that shows
// how to create a graph with the required nodes.
//
// Usage:
// const { Graph } = require('./index');
// const g = new Graph();
// const start = g.addNode('Start');
// const reflection = g.addNode('Reflection', { description: 'Reflect on input' });
// const rewrite = g.addNode('Rewrite', { description: 'Rewrite output' });
// const end = g.addNode('End');
// g.addEdge(start, reflection);
// g.addEdge(reflection, rewrite);
// g.addEdge(rewrite, end);
// console.log(JSON.stringify(g.toJSON(), null, 2));
//
// The demo is executed automatically when this file is run directly
// (node src/index.js). It prints the graph structure to the console.
'use strict';
/**
* Represents a single node in the graph.
*/
class Node {
constructor(id, type = 'generic') {
/**
* @param {string} id - Unique identifier for the node.
* @param {string} type - Type of the node (e.g., 'Start', 'Reflection').
* @param {object} [props={}] - Optional properties for the node.
*/
constructor(id, type, props = {}) {
this.id = id;
this.type = type;
this.props = props;
this.outgoing = []; // array of target node ids
}
}
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);
}
}
/**
* Represents a directed graph.
*/
class Graph {
constructor() {
this.nodes = new Map(); // id -> Node
this.edges = new Map(); // id -> Set of target ids
this.nextId = 1;
}
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());
}
/**
* Creates a new node and adds it to the graph.
*
* @param {string} type - The type of the node.
* @param {object} [props={}] - Optional properties.
* @returns {Node} The created node.
*/
addNode(type, props = {}) {
const id = `n${this.nextId++}`;
const node = new Node(id, type, props);
this.nodes.set(id, node);
return node;
}
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);
/**
* Adds a directed edge from one node to another.
*
* @param {Node|string} from - Source node or its id.
* @param {Node|string} to - Target node or its id.
*/
addEdge(from, to) {
const fromId = typeof from === 'string' ? from : from.id;
const toId = typeof to === 'string' ? to : to.id;
const fromNode = this.nodes.get(fromId);
const toNode = this.nodes.get(toId);
if (!fromNode) throw new Error(`Source node ${fromId} does not exist`);
if (!toNode) throw new Error(`Target node ${toId} does not exist`);
fromNode.outgoing.push(toId);
}
/**
* Retrieves a node by its id.
*
* @param {string} id - Node id.
* @returns {Node|null}
*/
getNode(id) {
return this.nodes.get(id);
return this.nodes.get(id) || null;
}
/**
* 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.
* Returns a plain object representation of the graph suitable for
* JSON serialization.
*
* @returns {object}
*/
replaceNode(oldId, newNode) {
if (!this.nodes.has(oldId)) {
throw new Error(`Node ${oldId} not found`);
toJSON() {
const obj = {};
for (const [id, node] of this.nodes.entries()) {
obj[id] = {
id: node.id,
type: node.type,
props: node.props,
outgoing: node.outgoing,
};
}
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;
return obj;
}
}
module.exports = {
Node,
ReflectionNode,
RewritingNode,
Graph,
};
/**
* Demo: Build a simple graph that includes the required
* 'Reflection' and 'Rewrite' nodes.
*/
function demo() {
const g = new Graph();
// Create nodes
const start = g.addNode('Start', { description: 'Entry point' });
const reflection = g.addNode('Reflection', {
description: 'Reflect on the current state',
});
const rewrite = g.addNode('Rewrite', {
description: 'Rewrite the data for the next step',
});
const end = g.addNode('End', { description: 'Exit point' });
// Connect nodes
g.addEdge(start, reflection);
g.addEdge(reflection, rewrite);
g.addEdge(rewrite, end);
console.log('Graph structure:');
console.log(JSON.stringify(g.toJSON(), null, 2));
}
// If this file is executed directly, run the demo.
if (require.main === module) {
demo();
}
module.exports = { Graph, Node };