feat: solution for 'Повторный экзамен: Граф с рефлексией и доработкой'
This commit is contained in:
+57
-55
@@ -1,14 +1,45 @@
|
||||
import { Node } from './nodes.js';
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple directed graph implementation.
|
||||
*/
|
||||
export class Graph {
|
||||
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 || '';
|
||||
}
|
||||
}
|
||||
|
||||
class Graph {
|
||||
constructor() {
|
||||
/** @type {Map<string, Node>} */
|
||||
this.nodes = new Map();
|
||||
/** @type {Map<string, Set<string>>} */
|
||||
this.adjList = new Map();
|
||||
this.nodes = new Map(); // Map of id -> Node
|
||||
this.adjList = new Map(); // Map of id -> array of neighbor ids
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -16,76 +47,47 @@ export class Graph {
|
||||
* @param {Node} node
|
||||
*/
|
||||
addNode(node) {
|
||||
if (!(node instanceof Node)) {
|
||||
throw new Error('Only Node instances can be added');
|
||||
}
|
||||
if (this.nodes.has(node.id)) {
|
||||
throw new Error(`Node with id ${node.id} already exists`);
|
||||
}
|
||||
this.nodes.set(node.id, node);
|
||||
this.adjList.set(node.id, new Set());
|
||||
this.adjList.set(node.id, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a directed edge from source to target.
|
||||
* Adds a directed edge from one node to another.
|
||||
* @param {string} fromId
|
||||
* @param {string} toId
|
||||
*/
|
||||
addEdge(fromId, toId) {
|
||||
if (!this.nodes.has(fromId) || !this.nodes.has(toId)) {
|
||||
throw new Error('Both nodes must exist to add an edge');
|
||||
throw new Error('Both nodes must exist to create an edge');
|
||||
}
|
||||
this.adjList.get(fromId).add(toId);
|
||||
this.adjList.get(fromId).push(toId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a node and all associated edges.
|
||||
* Returns an array of neighbor ids for a given node.
|
||||
* @param {string} id
|
||||
* @returns {string[]}
|
||||
*/
|
||||
removeNode(id) {
|
||||
if (!this.nodes.has(id)) {
|
||||
return;
|
||||
}
|
||||
this.nodes.delete(id);
|
||||
this.adjList.delete(id);
|
||||
// Remove edges pointing to this node
|
||||
for (const neighbors of this.adjList.values()) {
|
||||
neighbors.delete(id);
|
||||
}
|
||||
getNeighbors(id) {
|
||||
return this.adjList.get(id) || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a node by id.
|
||||
* Retrieves a node by its id.
|
||||
* @param {string} id
|
||||
* @returns {Node | undefined}
|
||||
* @returns {Node}
|
||||
*/
|
||||
getNode(id) {
|
||||
return this.nodes.get(id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Depth-first traversal starting from startId.
|
||||
* @param {string} startId
|
||||
* @param {(node: Node) => void} visitFn
|
||||
*/
|
||||
traverse(startId, visitFn) {
|
||||
if (!this.nodes.has(startId)) {
|
||||
throw new Error(`Start node ${startId} does not exist`);
|
||||
}
|
||||
const visited = new Set();
|
||||
const stack = [startId];
|
||||
while (stack.length) {
|
||||
const currentId = stack.pop();
|
||||
if (visited.has(currentId)) continue;
|
||||
visited.add(currentId);
|
||||
const node = this.nodes.get(currentId);
|
||||
visitFn(node);
|
||||
const neighbors = this.adjList.get(currentId);
|
||||
for (const neighborId of neighbors) {
|
||||
if (!visited.has(neighborId)) {
|
||||
stack.push(neighborId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
module.exports = {
|
||||
Node,
|
||||
ReflectionNode,
|
||||
RewriteNode,
|
||||
Graph,
|
||||
};
|
||||
+123
-111
@@ -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 };
|
||||
Reference in New Issue
Block a user