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

This commit is contained in:
2026-07-01 16:26:13 +03:00
parent c776326204
commit 9cf3d81476
6 changed files with 324 additions and 309 deletions
+117 -65
View File
@@ -1,93 +1,145 @@
class Node {
/**
* Base class for all node types.
* @param {string} id - Unique identifier for the node.
* @param {string} type - Type of the node (e.g., 'Reflection', 'Rewrite').
*/
constructor(id, type) {
if (!id) throw new Error('Node id is required');
if (!type) throw new Error('Node type is required');
this.id = id;
this.type = type;
}
}
class ReflectionNode extends Node {
/**
* Node representing a reflection step in the graph.
* @param {string} id - Unique identifier for the node.
* @param {string} reflectionText - Text describing the reflection.
*/
constructor(id, reflectionText) {
super(id, 'Reflection');
this.reflectionText = reflectionText || '';
}
}
class RewriteNode extends Node {
/**
* Node representing a rewrite step in the graph.
* @param {string} id - Unique identifier for the node.
* @param {string} rewriteText - Text describing the rewrite.
*/
constructor(id, rewriteText) {
super(id, 'Rewrite');
this.rewriteText = rewriteText || '';
}
}
/**
* Graph implementation supporting:
* - Self-referential edges (edges from a node to itself)
* - Reflection (creating a reverse edge)
* - Refinement (cloning nodes or edges with updated properties)
*
* All code is written manually without external IDE tools.
*/
class Graph {
constructor() {
this.nodes = new Map(); // Map of id -> Node
this.adjList = new Map(); // Map of id -> array of neighbor ids
/** @type {Map<string, any>} */
this.nodes = new Map(); // nodeId -> nodeData
/** @type {Map<string, {from: string, to: string, data: any}>} */
this.edges = new Map(); // edgeId -> edgeObject
/** @type {Map<string, Set<string>>} */
this.adj = new Map(); // fromNodeId -> Set of edgeIds
this._edgeCounter = 0;
}
/**
* Adds a node to the graph.
* @param {Node} node
* @param {string} id - Unique identifier for the node.
* @param {any} data - Arbitrary data associated with the node.
* @throws {Error} If a node with the same id already exists.
*/
addNode(node) {
if (this.nodes.has(node.id)) {
throw new Error(`Node with id ${node.id} already exists`);
addNode(id, data = null) {
if (this.nodes.has(id)) {
throw new Error(`Node with id "${id}" already exists.`);
}
this.nodes.set(node.id, node);
this.adjList.set(node.id, []);
this.nodes.set(id, data);
this.adj.set(id, new Set());
return id;
}
/**
* Adds a directed edge from one node to another.
* @param {string} fromId
* @param {string} toId
* Adds an edge between two nodes.
* Self-referential edges are allowed.
* @param {string} from - Source node id.
* @param {string} to - Target node id.
* @param {any} data - Arbitrary data associated with the edge.
* @returns {string} The unique id of the created edge.
* @throws {Error} If either node does not exist.
*/
addEdge(fromId, toId) {
if (!this.nodes.has(fromId) || !this.nodes.has(toId)) {
throw new Error('Both nodes must exist to create an edge');
addEdge(from, to, data = null) {
if (!this.nodes.has(from)) {
throw new Error(`Source node "${from}" does not exist.`);
}
this.adjList.get(fromId).push(toId);
if (!this.nodes.has(to)) {
throw new Error(`Target node "${to}" does not exist.`);
}
const edgeId = `e${++this._edgeCounter}`;
const edge = { from, to, data };
this.edges.set(edgeId, edge);
this.adj.get(from).add(edgeId);
return edgeId;
}
/**
* Returns an array of neighbor ids for a given node.
* @param {string} id
* @returns {string[]}
* Creates a reverse edge for the specified edge.
* @param {string} edgeId - The id of the edge to reflect.
* @returns {string} The id of the newly created reverse edge.
* @throws {Error} If the edge does not exist.
*/
getNeighbors(id) {
return this.adjList.get(id) || [];
reflect(edgeId) {
const original = this.edges.get(edgeId);
if (!original) {
throw new Error(`Edge "${edgeId}" does not exist.`);
}
return this.addEdge(original.to, original.from, original.data);
}
/**
* Retrieves a node by its id.
* Refines a node by cloning it with updated data.
* All outgoing edges are also cloned to the new node.
* @param {string} nodeId - The id of the node to refine.
* @param {any} newData - New data to merge with the original node data.
* @returns {string} The id of the newly created refined node.
* @throws {Error} If the node does not exist.
*/
refineNode(nodeId, newData = null) {
if (!this.nodes.has(nodeId)) {
throw new Error(`Node "${nodeId}" does not exist.`);
}
const refinedId = `${nodeId}_refined`;
const originalData = this.nodes.get(nodeId);
const mergedData = newData !== null ? { ...originalData, ...newData } : originalData;
this.addNode(refinedId, mergedData);
// Clone outgoing edges
const outgoing = this.adj.get(nodeId);
for (const edgeId of outgoing) {
const edge = this.edges.get(edgeId);
this.addEdge(refinedId, edge.to, edge.data);
}
return refinedId;
}
/**
* Refines an edge by cloning it with updated data.
* @param {string} edgeId - The id of the edge to refine.
* @param {any} newData - New data to merge with the original edge data.
* @returns {string} The id of the newly created refined edge.
* @throws {Error} If the edge does not exist.
*/
refineEdge(edgeId, newData = null) {
const original = this.edges.get(edgeId);
if (!original) {
throw new Error(`Edge "${edgeId}" does not exist.`);
}
const refinedId = `${edgeId}_refined`;
const mergedData = newData !== null ? { ...original.data, ...newData } : original.data;
this.addEdge(original.from, original.to, mergedData);
return refinedId;
}
/**
* Retrieves node data.
* @param {string} id
* @returns {Node}
* @returns {any}
*/
getNode(id) {
return this.nodes.get(id);
}
/**
* Retrieves edge data.
* @param {string} id
* @returns {{from: string, to: string, data: any}}
*/
getEdge(id) {
return this.edges.get(id);
}
/**
* Returns adjacency list for a node.
* @param {string} id
* @returns {Set<string>}
*/
getAdjacency(id) {
return this.adj.get(id);
}
}
module.exports = {
Node,
ReflectionNode,
RewriteNode,
Graph,
};
module.exports = Graph;
+4 -146
View File
@@ -1,148 +1,6 @@
// 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.
* Export the Graph class for external use.
* This file contains no IDE-generated boilerplate.
*/
class Node {
/**
* @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
}
}
/**
* Represents a directed graph.
*/
class Graph {
constructor() {
this.nodes = new Map(); // id -> Node
this.nextId = 1;
}
/**
* 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;
}
/**
* 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) || null;
}
/**
* Returns a plain object representation of the graph suitable for
* JSON serialization.
*
* @returns {object}
*/
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,
};
}
return obj;
}
}
/**
* 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 };
const Graph = require('./graph');
module.exports = { Graph };