UNPKG

skymel-adk-js-beta

Version:

Skymel Agent Development Kit using Javascript - A JavaScript SDK for creating and managing intelligent agents

510 lines (473 loc) 23.9 kB
import {CommonValidators} from "./common_validators.js"; /** * A utility class containing common algorithms for graph manipulation and analysis. * This class contains only static methods and cannot be instantiated. The `graph` is a map of * `{nodeId:Set([immediateChildrenNodeIds])}` */ export class CommonGraphAlgorithms { constructor() { throw new Error('This is a class with static methods and cannot be instantiated.'); } static getUnionOfSets(set1, set2) { let output = new Set(set1); for (let x of set2) { output.add(x); } return output; } static removeDuplicateNodeIdsFromList(listOfNodeIds) { if (typeof listOfNodeIds === 'undefined' || listOfNodeIds === null || listOfNodeIds.length === 0) return []; if (typeof listOfNodeIds === 'string' || typeof listOfNodeIds === 'number') { return [listOfNodeIds]; } if (typeof listOfNodeIds === 'object' && Array.isArray(listOfNodeIds)) { return Array.from(new Set(listOfNodeIds)); } if (typeof listOfNodeIds === 'object' && Object.keys(listOfNodeIds).length > 0) { return Array.from(Object.keys(listOfNodeIds)); } if (typeof listOfNodeIds === 'object' && listOfNodeIds instanceof Set) { return Array.from(listOfNodeIds); } return []; } static isEmptyGraph(graph) { return typeof graph === 'undefined' || graph === null || Object.keys(graph).length === 0; } static isValidNodeId(nodeId) { return typeof nodeId === 'string' || typeof nodeId === 'number'; } static isNodeInGraph(graph, nodeId) { if (CommonGraphAlgorithms.isEmptyGraph(graph) || !CommonGraphAlgorithms.isValidNodeId(nodeId)) { return false; } return nodeId in graph; } static isParentNode(graph, potentialParentNodeId, potentialChildNodeId) { if (CommonGraphAlgorithms.isEmptyGraph(graph) || CommonGraphAlgorithms.isNodeInGraph(graph, potentialParentNodeId) === false || CommonGraphAlgorithms.isNodeInGraph(graph, potentialChildNodeId) === false) { return false; } if (graph[potentialParentNodeId] === null) { return false; } return graph[potentialParentNodeId].has(potentialChildNodeId); } static isChildNode(graph, potentialChildNodeId, potentialParentNodeId) { return CommonGraphAlgorithms.isParentNode(graph, potentialParentNodeId, potentialChildNodeId); } static getListOfNodesInGraph(graph) { if (CommonGraphAlgorithms.isEmptyGraph(graph)) { return []; } return Object.keys(graph); } static getListOfChildrenOfNode(graph, nodeId) { if (!CommonGraphAlgorithms.isNodeInGraph(graph, nodeId)) { return []; } if (graph[nodeId] === null) { return []; } return CommonGraphAlgorithms.removeDuplicateNodeIdsFromList(graph[nodeId]); } static getListOfParentsOfNode(graph, nodeId) { if (!CommonGraphAlgorithms.isNodeInGraph(graph, nodeId)) { return []; } let output = new Set(); for (let nodeId2 in graph) { if (graph[nodeId2] === null) { continue; } if (graph[nodeId2].has(nodeId)) { output.add(nodeId2); } } return Array.from(output); } static getListOfSiblingsOfNode(graph, nodeId) { if (!CommonGraphAlgorithms.isNodeInGraph(graph, nodeId)) { return []; } let parentNodeIds = CommonGraphAlgorithms.getListOfParentsOfNode(graph, nodeId); if (parentNodeIds.length === 0) { return []; } let output = new Set(); for (let i = 0; i < parentNodeIds.length; ++i) { let parentNodeId = parentNodeIds[i]; let childrenOfParentNode = new Set(CommonGraphAlgorithms.getListOfChildrenOfNode(graph, parentNodeId)); output = CommonGraphAlgorithms.getUnionOfSets(output, childrenOfParentNode); } output.delete(nodeId); return Array.from(output); } static getListOfRootNodes(graph) { if (CommonGraphAlgorithms.isEmptyGraph(graph)) { return []; } let output = []; for (let nodeId in graph) { if (CommonGraphAlgorithms.getListOfParentsOfNode(graph, nodeId).length === 0) { output.push(nodeId); } } return CommonGraphAlgorithms.removeDuplicateNodeIdsFromList(output); } static getListOfLeafNodes(graph) { if (CommonGraphAlgorithms.isEmptyGraph(graph)) { return []; } let output = []; for (let nodeId in graph) { if (CommonGraphAlgorithms.getListOfChildrenOfNode(graph, nodeId).length === 0) { output.push(nodeId); } } return CommonGraphAlgorithms.removeDuplicateNodeIdsFromList(output); } /** * An implementation of Depth-First Search the method continues until `stopSearchCriteria` evaluates to true. * @param graph The `graph` is a map of `{nodeId:Set([immediateChildrenNodeIds])}` * @param startNodeIds A list of node-ids to start the Depth-First Search. If null, we find the roots of the current * graph and use them as startNodeIds. * @param stopSearchCriteria it's a method that takes as inputs: `currentNodeId, currentNodeVisitationDepth, * visitedNodeIdToFirstVisitDepthMap, toVisitNodesAndVisitationDepthStackArray, currentVisitationPath`. The method must return true or * false. If true is returned, the entire search is stopped. * @param stopSearchingChildrenCriteria it's a method that takes as inputs: `currentNodeId, currentNodeVisitationDepth, * visitedNodeIdToFirstVisitDepthMap, toVisitNodesAndVisitationDepthStackArray, currentVisitationPath`. The method must return true or * false. If true is returned, the children of the `currentNodeId` are not added to the search stack. * @param finalResulCallback An `async` method to compute the final result on stop criteria being met. It's provided * the following inputs : `stopSearchNode, stopSearchNodeVisitationDepth, visitedNodeIdToFirstVisitDepthMap, * toVisitNodesAndVisitationDepthStackArray, currentVisitationPath` * @returns {Promise<void>} Returns the result of `finalResulCallback` if it is not null. Else it returns nothing. */ static async depthFirstSearch(graph, startNodeIds = null, stopSearchCriteria = null, stopSearchingChildrenCriteria = null, finalResulCallback = null) { if (CommonGraphAlgorithms.isEmptyGraph(graph)) { return; } if (startNodeIds === null) { startNodeIds = CommonGraphAlgorithms.getListOfRootNodes(graph); } if (startNodeIds.length === 0) { // No start/root nodes found. return; } let visitedNodeIdToFirstVisitDepthMap = {}; let toVisitNodesAndVisitationDepthStackArray = []; for (let i = 0; i < startNodeIds.length; ++i) { let currentNodeId = startNodeIds[i]; if (!CommonGraphAlgorithms.isNodeInGraph(graph, currentNodeId)) { continue; } toVisitNodesAndVisitationDepthStackArray.push([currentNodeId, 0]); } let currentVisitationPath = []; let currentVisitationPathDepths = []; while (toVisitNodesAndVisitationDepthStackArray.length > 0) { let currentEntry = toVisitNodesAndVisitationDepthStackArray.pop(); let currentNodeId = currentEntry[0]; let currentNodeVisitationDepth = currentEntry[1]; if (currentNodeVisitationDepth === 0) { currentVisitationPath = [currentNodeId]; currentVisitationPathDepths = [0]; } else { while (currentVisitationPathDepths[currentVisitationPathDepths.length - 1] >= currentNodeVisitationDepth) { currentVisitationPathDepths.pop(); currentVisitationPath.pop(); } currentVisitationPath.push(currentNodeId); currentVisitationPathDepths.push(currentNodeVisitationDepth); } if (stopSearchCriteria !== null && typeof stopSearchCriteria === 'function' && stopSearchCriteria(currentNodeId, currentNodeVisitationDepth, visitedNodeIdToFirstVisitDepthMap, toVisitNodesAndVisitationDepthStackArray, currentVisitationPath)) { if (finalResulCallback !== null) { return await finalResulCallback(currentNodeId, currentNodeVisitationDepth, visitedNodeIdToFirstVisitDepthMap, toVisitNodesAndVisitationDepthStackArray, currentVisitationPath); } return; } if (currentNodeId in visitedNodeIdToFirstVisitDepthMap) { continue; } visitedNodeIdToFirstVisitDepthMap[currentNodeId] = currentNodeVisitationDepth; if (stopSearchingChildrenCriteria !== null && typeof stopSearchingChildrenCriteria === 'function' && stopSearchingChildrenCriteria(currentNodeId, currentNodeVisitationDepth, visitedNodeIdToFirstVisitDepthMap, toVisitNodesAndVisitationDepthStackArray, currentVisitationPath)) { continue; } let childrenOfCurrentNode = CommonGraphAlgorithms.getListOfChildrenOfNode(graph, currentNodeId); for (let i = 0; i < childrenOfCurrentNode.length; ++i) { let childNodeId = childrenOfCurrentNode[i]; toVisitNodesAndVisitationDepthStackArray.push([childNodeId, currentNodeVisitationDepth + 1]); } } if (finalResulCallback !== null) { return await finalResulCallback(null, null, visitedNodeIdToFirstVisitDepthMap, toVisitNodesAndVisitationDepthStackArray, currentVisitationPath); } } /** * An implementation of Breadth-First Search the method continues until `stopSearchCriteria` evaluates to true. * @param graph The `graph` is a map of `{nodeId:Set([immediateChildrenNodeIds])}` * @param startNodeIds A list of node-ids to start the Breadth-First Search. If null, we find the roots of the current * graph and use them as startNodeIds. * @param stopSearchCriteria it's a method that takes as inputs: `currentNodeId, currentNodeVisitationDepth, * visitedNodeIdToFirstVisitDepthMap, toVisitNodesAndVisitationDepthQueueArray`. The method must return true or * false. If true is returned, the entire search is stopped. * @param stopSearchingChildrenCriteria it's a method that takes as inputs: `currentNodeId, currentNodeVisitationDepth, * visitedNodeIdToFirstVisitDepthMap, toVisitNodesAndVisitationDepthQueueArray`. The method must return true or * false. If true is returned, the children of the `currentNodeId` are not added to the search stack. * @param finalResulCallback An `async` method to compute the final result on stop criteria being met. It's provided * the following inputs : `stopSearchNode, stopSearchNodeVisitationDepth, visitedNodeIdToFirstVisitDepthMap, * toVisitNodesAndVisitationDepthQueueArray` * @returns {Promise<void>} Returns the result of `finalResulCallback` if it is not null. Else it returns nothing. */ static async breadthFirstSearch(graph, startNodeIds = null, stopSearchCriteria = null, stopSearchingChildrenCriteria = null, finalResulCallback = null) { if (CommonGraphAlgorithms.isEmptyGraph(graph)) { return; } if (startNodeIds === null) { startNodeIds = CommonGraphAlgorithms.getListOfRootNodes(graph); } if (startNodeIds.length === 0) { // No start/root nodes found. return; } let visitedNodeIdToFirstVisitDepthMap = {}; let toVisitNodesAndVisitationDepthQueueArray = []; for (let i = 0; i < startNodeIds.length; ++i) { let currentNodeId = startNodeIds[i]; if (!CommonGraphAlgorithms.isNodeInGraph(graph, currentNodeId)) { continue; } toVisitNodesAndVisitationDepthQueueArray.push([currentNodeId, 0]); } while (toVisitNodesAndVisitationDepthQueueArray.length > 0) { let currentEntry = toVisitNodesAndVisitationDepthQueueArray.shift(); let currentNodeId = currentEntry[0]; let currentNodeVisitationDepth = currentEntry[1]; if (stopSearchCriteria !== null && typeof stopSearchCriteria === 'function' && stopSearchCriteria(currentNodeId, currentNodeVisitationDepth, visitedNodeIdToFirstVisitDepthMap, toVisitNodesAndVisitationDepthQueueArray)) { if (finalResulCallback !== null) { return await finalResulCallback(currentNodeId, currentNodeVisitationDepth, visitedNodeIdToFirstVisitDepthMap, toVisitNodesAndVisitationDepthQueueArray); } return; } if (currentNodeId in visitedNodeIdToFirstVisitDepthMap) { continue; } visitedNodeIdToFirstVisitDepthMap[currentNodeId] = currentNodeVisitationDepth; if (stopSearchingChildrenCriteria !== null && typeof stopSearchingChildrenCriteria === 'function' && stopSearchingChildrenCriteria(currentNodeId, currentNodeVisitationDepth, visitedNodeIdToFirstVisitDepthMap, toVisitNodesAndVisitationDepthQueueArray)) { continue; } let childrenOfCurrentNode = CommonGraphAlgorithms.getListOfChildrenOfNode(graph, currentNodeId); for (let i = 0; i < childrenOfCurrentNode.length; ++i) { let childNodeId = childrenOfCurrentNode[i]; toVisitNodesAndVisitationDepthQueueArray.push([childNodeId, currentNodeVisitationDepth + 1]); } } if (finalResulCallback !== null) { return await finalResulCallback(null, null, visitedNodeIdToFirstVisitDepthMap, toVisitNodesAndVisitationDepthQueueArray); } } /** * Checks whether the graph contains cycles * @param graph * @returns {Promise<boolean>} */ static async containsCyclicGraph(graph) { if (CommonGraphAlgorithms.isEmptyGraph(graph)) { return false; } const stopSearchCriterion = function (currentNodeId, currentNodeVisitationDepth, visitedNodeIdToFirstVisitDepthMap, toVisitNodesAndVisitationDepthStackArray, currentVisitationPath) { if (!(currentNodeId in visitedNodeIdToFirstVisitDepthMap)) { return false; } for (let i = 0; i < currentVisitationPath.length - 1; ++i) { if (currentNodeId === currentVisitationPath[i]) { return true; } } return false; } const finalResultCallback = async function (stopSearchNode, stopSearchNodeVisitationDepth, visitedNodeIdToFirstVisitDepthMap, toVisitNodesAndVisitationDepthStackArray, currentVisitationPath) { if (!(stopSearchNode in visitedNodeIdToFirstVisitDepthMap)) { return false; } for (let i = 0; i < currentVisitationPath.length - 1; ++i) { if (stopSearchNode === currentVisitationPath[i]) { return true; } } return false; } return await CommonGraphAlgorithms.depthFirstSearch(graph, null, stopSearchCriterion, null, finalResultCallback); } static getListOfAllEdgesInGraph(graph) { if (CommonGraphAlgorithms.isEmptyGraph(graph)) { return []; } let output = []; for (let nodeId in graph) { let childrenOfCurrentNode = CommonGraphAlgorithms.getListOfChildrenOfNode(graph, nodeId); for (let i = 0; i < childrenOfCurrentNode.length; ++i) { output.push([nodeId, childrenOfCurrentNode[i]]); } } return output; } static hasInboundEdgesToNodeInEdgeList(edgeList, nodeId) { let foundIndex = edgeList.findIndex(x => x[1] === nodeId); return foundIndex !== -1; } static hasOutboundEdgesFromNodeInEdgeList(edgeList, nodeId) { let foundIndex = edgeList.findIndex(x => x[0] === nodeId); return foundIndex !== -1; } static async topologicalSort(graph) { if (CommonGraphAlgorithms.isEmptyGraph(graph)) { return null; } const containsCyclicGraph = await CommonGraphAlgorithms.containsCyclicGraph(graph); if (containsCyclicGraph) { return null; } let listOfAllEdgesInGraph = CommonGraphAlgorithms.getListOfAllEdgesInGraph(graph); let sortedNodes = []; let toVisitNodes = CommonGraphAlgorithms.getListOfRootNodes(graph); while (toVisitNodes.length > 0) { let currentNodeId = toVisitNodes.pop(); sortedNodes.push(currentNodeId); let childrenOfCurrentNode = CommonGraphAlgorithms.getListOfChildrenOfNode(graph, currentNodeId); for (let i = 0; i < childrenOfCurrentNode.length; ++i) { let childNodeId = childrenOfCurrentNode[i]; let indexOfChildNodeInGraph = listOfAllEdgesInGraph.findIndex(x => x[1] === childNodeId && x[0] === currentNodeId); if (indexOfChildNodeInGraph !== -1) { listOfAllEdgesInGraph.splice(indexOfChildNodeInGraph, /*deleteCount=*/1); } if (!CommonGraphAlgorithms.hasInboundEdgesToNodeInEdgeList(listOfAllEdgesInGraph, childNodeId)) { toVisitNodes.push(childNodeId); } } } return sortedNodes; } static async getAllNodesEncounteredWhileTraversingDirectedAcyclicGraph(graph, sourceNodeId, destinationNodeId, includeSourceNodeId = true, includeDestinationNodeId = true) { if (CommonGraphAlgorithms.isEmptyGraph(graph)) { return null; } const containsCyclicGraph = await CommonGraphAlgorithms.containsCyclicGraph(graph); if (containsCyclicGraph) { return null; } if (sourceNodeId === destinationNodeId && CommonGraphAlgorithms.isNodeInGraph(graph, sourceNodeId)) { if (includeSourceNodeId && includeDestinationNodeId) { return [sourceNodeId]; } return []; } let allEncounteredNodes = new Set(); let nodesEncounteredOnConnectedPaths = new Set(); let stopRepeatingDFS = false; let lastVisitationPath = []; const areArraysEqual = function (arr1, arr2) { if (arr1.length !== arr2.length) { return false; } for (let i = 0; i < arr1.length; ++i) { if (arr1[i] !== arr2[i]) { return false; } } return true; } const stopSearchCriteria = function (currentNodeId, currentNodeVisitationDepth, visitedNodeIdToFirstVisitDepthMap, toVisitNodesAndVisitationDepthStackArray) { return currentNodeId === destinationNodeId; } const stopSearchingChildrenCriteria = function (currentNodeId, currentNodeVisitationDepth, visitedNodeIdToFirstVisitDepthMap, toVisitNodesAndVisitationDepthQueueArray) { return allEncounteredNodes.has(currentNodeId); } const finalResulCallback = async function (stopSearchNode, stopSearchNodeVisitationDepth, visitedNodeIdToFirstVisitDepthMap, toVisitNodesAndVisitationDepthStackArray, currentVisitationPath) { if (stopSearchNode === null || areArraysEqual(currentVisitationPath, lastVisitationPath)) { stopRepeatingDFS = true; } if (stopSearchNode === destinationNodeId) { lastVisitationPath = currentVisitationPath; for (let x = 0; x < currentVisitationPath.length; ++x) { const currentOnPathNodeId = currentVisitationPath[x]; if (currentOnPathNodeId === sourceNodeId && !includeSourceNodeId) { continue; } if (currentOnPathNodeId === destinationNodeId && !includeDestinationNodeId) { continue; } nodesEncounteredOnConnectedPaths.add(currentOnPathNodeId); } } for (let nodeId in visitedNodeIdToFirstVisitDepthMap) { if (nodeId === destinationNodeId || nodeId === sourceNodeId) { continue; } allEncounteredNodes.add(nodeId); } } while (!stopRepeatingDFS) { await CommonGraphAlgorithms.depthFirstSearch(graph, [sourceNodeId], stopSearchCriteria, stopSearchingChildrenCriteria, finalResulCallback); } const result = [...nodesEncounteredOnConnectedPaths]; if (CommonValidators.isEmpty(result)) { return null; } return result.sort(); } static async getSubGraphNodeIds(graph, sourceNodeIds, destinationNodeIds, includeSourceNodeIds = true, includeDestinationNodeIds = true) { let subGraphNodes = new Set(); for (let i = 0; i < sourceNodeIds.length; ++i) { const sourceNodeId = sourceNodeIds[i]; for (let j = 0; j < destinationNodeIds.length; ++j) { const destinationNodeId = destinationNodeIds[j]; const result = await CommonGraphAlgorithms.getAllNodesEncounteredWhileTraversingDirectedAcyclicGraph(graph, sourceNodeId, destinationNodeId, includeSourceNodeIds, includeDestinationNodeIds); if (CommonValidators.isEmpty(result)) { continue; } result.forEach(nodeId => subGraphNodes.add(nodeId)); } } let result = [...subGraphNodes]; if (CommonValidators.isEmpty(result)) { return null; } return result.sort(); } static getAllDownStreamNodeIds(graph, sourceNodeIds, addSourceNodeIdsToReturnedList = true) { if (CommonValidators.isEmpty(graph) || CommonValidators.isEmpty(sourceNodeIds)) { return null; } let allDownStreamNodes = new Set(); let toExploreNodeIds = [...sourceNodeIds]; let alreadyExploredNodeIds = new Set(); let sourceNodeIdsSet = new Set(sourceNodeIds); while (toExploreNodeIds.length > 0) { const currentNodeId = toExploreNodeIds.shift(); if (alreadyExploredNodeIds.has(currentNodeId)) { continue; } alreadyExploredNodeIds.add(currentNodeId); const currentChildren = CommonGraphAlgorithms.getListOfChildrenOfNode(graph, currentNodeId); if (!CommonValidators.isEmpty(currentChildren)) { for (let i = 0; i < currentChildren.length; ++i) { if (alreadyExploredNodeIds.has(currentChildren[i])) { continue; } toExploreNodeIds.push(currentChildren[i]); } } if (sourceNodeIdsSet.has(currentNodeId) && !addSourceNodeIdsToReturnedList) { continue; } allDownStreamNodes.add(currentNodeId); } let result = [...allDownStreamNodes]; if (CommonValidators.isEmpty(result)) { return null; } return result.sort(); } }