src/pages/vaults/utils/path-finding.tsx

Summary

Maintainability
A
1 hr
Test Coverage
/**
 * Ref: https://github.com/DeFiCh/wallet/blob/main/mobile-app/app/screens/AppNavigator/screens/Dex/helpers/path-finding.tsx
 */

export interface GraphProps {
  pairId: string;
  a: string;
  b: string;
}

/**
 * To find all the adjacent vertices / neighbors of a node in a graph
 * @param startNode
 * @param graph
 */
export function getAdjacentNodes(
  startNode: string,
  graph: GraphProps[]
): string[] {
  const adjacentNodesSet = new Set();
  graph.forEach((vertices) => {
    if (vertices.a === startNode && vertices.b !== startNode) {
      adjacentNodesSet.add(vertices.b);
    } else if (vertices.b === startNode && vertices.a !== startNode) {
      adjacentNodesSet.add(vertices.a);
    }
  });

  return Array.from(adjacentNodesSet) as string[];
}

/**
 * This uses a modified Breadth First Search to store the first path found (by distance)
 * @param graph
 * @param start
 * @param target
 */
export function findPath(
  graph: GraphProps[],
  origin: string,
  target: string
): { visitedNodes: Set<string>; path: string[] } {
  let isPathFound = false;
  let nodesToVisit = [origin, ...getAdjacentNodes(origin, graph)];
  const visitedNodes = new Set<string>([]); // track visited nodes in a set
  const path: string[] = []; // store the first path found by token
  bfs({
    start: {
      value: origin,
      edges: getAdjacentNodes(origin, graph),
      currentDistance: 0,
    },
    target: target,
  });

  function bfs({
    start,
    target,
  }: {
    start: { value: string; edges: string[]; currentDistance: number };
    target: string;
  }): void {
    if (start.edges.length === 0 && start.value !== target) {
      // no possible path
      visitedNodes.add(start.value);
      return;
    }

    if (!isPathFound) {
      path[start.currentDistance] = start.value;
    }

    if (start.value === target) {
      isPathFound = true;
      visitedNodes.add(start.value);
      nodesToVisit = [];
      return;
    }
    visitedNodes.add(start.value);

    while (nodesToVisit.length > 0) {
      nodesToVisit.shift();
      const nextNodeToVisitEdges = start.edges;

      while (nextNodeToVisitEdges.length !== 0 && !isPathFound) {
        const startValue = nextNodeToVisitEdges[0];
        const innerStart = {
          value: startValue,
          edges: getAdjacentNodes(startValue, graph).filter(
            (node) => !visitedNodes.has(node)
          ),
          currentDistance: start.currentDistance + 1,
        };
        const startEdgesToVisit = innerStart.edges;

        nodesToVisit = [...nodesToVisit, ...startEdgesToVisit];
        bfs({
          start: innerStart,
          target: target,
        });
        nextNodeToVisitEdges.shift();
      }
    }
  }

  return {
    visitedNodes,
    path: isPathFound ? path : [],
  };
}

export function checkIfPair(
  pair: { a: string; b: string },
  a: string,
  b: string
): boolean {
  return (pair.a === a && pair.b === b) || (pair.b === a && pair.a === b);
}