Home Reference Source Test Repository

src/js/algorithm/traversal-algorithm.js

import Queue from '../util/queue';
import Node from '../data/node/node';
import Edge from '../data/edge/edge';
import AlgorithmResult from '../algorithm/algorithm-result';
import AbstractAlgorithm from '../algorithm/abstract-algorithm';
import StepBuilder from '../algorithm/step-builder';

/**
 * Class that contains code for running a traversal algorithm.
 * @class TraversalAlgorithm
 */
class TraversalAlgorithm extends AbstractAlgorithm {

  /**
   * Data structure that determines order of traversal.
   * @type {Queue}
   */
  next = new Queue();

  /**
   * Source node of the algorithm.
   * @type {[type]}
   */
  source = null;

  /**
   * Object that holds the algorithm input values. Used by the UI classes to view and store values.
   * @type {Object.<string, *>}
   */
  inputs = {
    source: null
  };

  /**
   * @typedef InputType
   * @type {object}
   * @property {string} type - String representing type of input.
   * @property {string} name - Name of the field.
   * @property {string} displayName - Name to be displayed.
   * @property {method} test - Function run to validate input.
   * @property {boolean} required - Whether or not the input is required.
   */

  /**
   * Object that defines the algorithm input types.
   * @type {Object.<string, InputType>}
   */
  inputTypes = [
    {
      type: 'node',
      name: 'source',
      displayName: 'Source',
      test: (node) => {
        return node instanceof Node;
      },
      required: true
    }
  ];

  /**
   * Array of field names to store for Node objects.
   * @type {Array.<string>}
   */
  nodeFields = [
    'color'
  ];

  /**
   * Array of field names to store for Edge objects.
   * @type {Array.<string>}
   */
  edgeFields = [
    'color'
  ];

  /**
   * Constructor for TraversalAlgorithm.
   * @param  {Graph} graph - The graph that the algorithm will be run on.
   * @constructs TraversalAlgorithm
   */
  constructor(graph) {
    super(graph);
    this.result = new AlgorithmResult();
    this.stepBuilder = new StepBuilder(this.nodeFields, this.edgeFields, this.result);
  }

  /**
   * Visit the specified node.
   * @param  {Node} node - The node to be visited.
   */
  visitNode(node) {
    // mark node as visited
    this.graphState.set(node, true);

    // add adjacent edges to the queue
    let edges = node.edges;
    for (let edge of edges) {
      if (this.hasVisited(edge) || this.next.has(edge)) {
        continue;
      }
      if (edge.isDirected && edge.startNode === node) {
        this.next.enqueue(edge);
      } else if (!edge.isDirected) {
        this.next.enqueue(edge);
      }
    }
  }

  /**
   * Visit the specified edge.
   * @param  {Edge} edge - The edge to be visited.
   */
  visitEdge(edge) {
    // mark edge as visited
    this.graphState.set(edge, true);

    // add adjacent node to the queue
    if (!this.hasVisited(edge.destNode) && !this.next.has(edge.destNode)) {
      this.next.enqueue(edge.destNode);
    } else if (!this.hasVisited(edge.startNode) && !edge.isDirected && !this.next.has(edge.startNode)) {
      this.next.enqueue(edge.startNode);
    }
  }

  /**
   * Check if a node or edge has been visited by the algorithm.
   * @param  {(Node|Edge)}  object - The node/edge to be checked.
   * @return {boolean} - Whether or not the node/edge has been visited.
   */
  hasVisited(object) {
    return this.graphState.has(object) && this.graphState.get(object);
  }

  /**
   * @override
   */
  step() {
    if (this.hasStarted && this.next.size === 0) {
      this.isComplete = true;
      return false;
    }

    let nextItem;

    if (this.hasStarted) {
      nextItem = this.next.dequeue();
    } else {
      this.hasStarted = true;
      nextItem = this.source;
    }

    if (nextItem instanceof Node) {
      this.visitNode(nextItem);
    } else if (nextItem instanceof Edge) {
      this.visitEdge(nextItem);
    } else {
      throw Error('Non-graph object in algorithm queue');
    }
    // represent the visual aspects of this step by creating a new step, adding a change for the current object, and completing the step
    let typeName;
    if (nextItem instanceof Node) {
      typeName = 'node';
    } else if (nextItem instanceof Edge) {
      typeName = 'edge';
    } else {
      typeName = nextItem.constructor.name;
    }
    this.stepBuilder.newStep(`Visiting ${typeName} ${nextItem.id}`);
    this.stepBuilder.addChange(nextItem, {
      color: nextItem.color
    }, {
      color: 'red'
    }, {
      color: 'green'
    });
    this.stepBuilder.completeStep();

    return true;
  }

  /**
   * Reset the algorithm so that it can be run again.
   */
  reset() {
    this.next.clear();
    this.isComplete = false;
    this.hasStarted = false;
    this.graphState = new WeakMap();
    this.result = new AlgorithmResult();
    this.stepBuilder = new StepBuilder(this.nodeFields, this.edgeFields, this.result);
  }

}

export { TraversalAlgorithm };
export default TraversalAlgorithm;