Home Reference Source

src/graph/Graph.js

import Edge from './Edge';
import Vertex from './Vertex';
import DuplicateVertexError from './DuplicateVertexError';
import DuplicateEdgeError from './DuplicateEdgeError';

/**
 * Graph: represents a undirected graph data structure.
 * Usage: let graph = new Graph();
 *        graph.addVertex('A');
 *        graph.addVertex('B');
 *        graph.addEdge('A','B');
 */
class Graph {
  constructor(){
    this._vertices = [];
    this._edges = [];
  }

  /**
   * @return {Array} array of all Vertex instances in this graph.
   */
  getVertices(){
    return this._vertices;
  }

  /**
   * @param {value} value to find in the graph.
   * @return {Boolean} true if any vertex has this value else false
   */
  hasVertexWithValue(value){
    var vertexValues = this.getVertexValues();
    return !!~vertexValues.indexOf(value);
  }

  /**
   * @return {Array} array of values in all graph vertices.
   */
  getVertexValues(){
    return this._vertices.map((vertex) => {
      return vertex.getValue();
    });
  }

  /**
   * @param {value} value to set on the vertex
   * @throws {DuplicateVertexError} when adding vertex whose value exists in graph.
   * @return {Graph} current instance of graph.
   */
  addVertex(value, vertex_class=Vertex){
    if (this.hasVertexWithValue(value)){
      throw new DuplicateVertexError("Cannot add duplicate value '" + value.toString() + "' to graph.");
    } else {
      var vertex = new vertex_class(value);
      this._vertices.push(vertex);
    }
    return this;
  }

  /**
   * @param {values} array of values to add to the graph
   * @return {Graph} current instance of graph.
   */
  addVerticies(values){
    values.forEach((value) => {
      this.addVertex(value);
    });
    return this;
  }

  /**
   * @param {value} value
   * @return {Vertex} instance of Vertex in graph with value, else undefined
   */
  getVertexByValue(value){
    var vertices = this.getVertices();
    var matching = vertices.filter((vertex) => {
      return vertex.value === value;
    });

    if (matching.length > 0){
      return matching[0];
    }

    return undefined;
  }

  /**
   * @param {startVertex} Vertex (or value of Vertex) in graph to start
   * @param {endVertex} Vertex (or value of Vertex) in graph to end
   * @throws {DuplicateEdgeError} when adding edge whose value exists in graph.
   * @return {Graph} current instance of graph.
   */
   addEdge(startVertex, endVertex){
     if (!(startVertex instanceof Vertex)){
       startVertex = this.getVertexByValue(startVertex);
     }

     if (!(endVertex instanceof Vertex)){
       endVertex = this.getVertexByValue(endVertex);
     }

     if (this.hasEdge(startVertex.value, endVertex.value)){
       throw new DuplicateEdgeError("Cannot add duplicate edge between '" + startVertex.value + "' and '" + endVertex.value + "' to graph.");
     }

     var edge = new Edge(startVertex, endVertex);
     this._edges.push(edge);

     // since this undirected graph, edges must go both ways
     var reverseEdge = new Edge(endVertex, startVertex);
     this._edges.push(reverseEdge);

     return this;
   }

   /**
    * @return {Array} array of Edge instances present in Graph.
    */
   getEdges(){
     return this._edges;
   }

   /**
    * @param {startValue} start value of edge to locate
    * @param {endValue} end value of edge to locate
    * @return {Edge} returns Edge instance in Graph if exists, otherwise returns undefined
    */
   getEdgeByValues(startValue, endValue){
     let edges = this.getEdges();
     let matching = edges.filter((edge) => {
       return edge.startVertex.value === startValue && edge.endVertex.value === endValue;
     });

     if (matching.length > 0){
       return matching[0];
     }

     return undefined;
   }

   /**
    * @param {startVertex} start value of edge to locate
    * @param {endVertex} end value of edge to locate
    * @return {Boolean} true if any edge has these start and end values else false
    */
   hasEdge(startVertex, endVertex){
     return !!this.getEdgeByValues(startVertex, endVertex);
   }

   /**
    * @return {Array} array of arrays; each entry represents adjacency for row;
    *         each entry in each sub-array represents adjacency for column;
    *         if A[i][j] = 1, there is an edge from i to j.
    */
   getAdjacencyMatrix(){
     let matrix = [];
     let vertices = this.getVertices();
     // where N = number of vertices, initialize N X N zero matrix
     vertices.forEach((vertex) => {
      matrix.push(new Array(vertices.length).fill(0));
     });

     // if edge exists for A[i][j] (and A[j][i]) set cell to 1
     let edges = this.getEdges();
     let vertexValues = this.getVertexValues();
     edges.forEach((edge) => {
       let startValue = edge.startVertex.value;
       let endValue = edge.endVertex.value;
       let startIndex = vertexValues.indexOf(startValue);
       let endIndex = vertexValues.indexOf(endValue);
       matrix[startIndex][endIndex] = 1;
       matrix[endIndex][startIndex] = 1;
     });
     return matrix;
   }

   /**
    * @return {Array} given Vertex A, return all values from vertices connected to A by edges.
    */
   getEdgesByVertex(vertex){
     if (!(vertex instanceof Vertex)){
       vertex = this.getVertexByValue(vertex);
     }

     let edges = this.getEdges();
     let matching = edges.filter((edge) => {
       return edge.startVertex.value === vertex.value;
     });

     return matching.map( (edge) => {
       return edge.endVertex.value;
     });
   }

   /**
    * @return {Object} each Object key represents a vertex in graph;
    * value at each key is an array of vertex values found in edge destinations.
    */
    getAdjacencyList(){
      var list = {};
      let vertices = this.getVertices();
      vertices.forEach((vertex) => {
        list[vertex.value] = this.getEdgesByVertex(vertex);
      });
      return list;
    }
}

export default Graph;