Function creating an iterator of edges for the given type.

@param  {Graph}    graph - Target Graph instance.
@param  {string}   type  - Type of edges to retrieve.
@return {Iterator}
795
function createEdgeIterator(graph, type) {
  if (graph.size === 0)
    return Iterator.empty();

  let iterator;

  if (type === 'mixed') {
    iterator = graph._edges.values();

    return new Iterator(function next() {
      const step = iterator.next();

      if (step.done)
        return step;

      const data = step.value;

      const value = [
        data.key,
        data.attributes,
        data.source.key,
        data.target.key,
        data.source.attributes,
        data.target.attributes
      ];

      return {value, done: false};
    });
  }

  iterator = graph._edges.values();

  return new Iterator(function next() {
    const step = iterator.next();

    if (step.done)
      return step;

    const data = step.value;

    if ((data instanceof UndirectedEdgeData) === (type === 'undirected')) {
      const value = [
        data.key,
        data.attributes,
        data.source.key,
        data.target.key,
        data.source.attributes,
        data.target.attributes
      ];

      return {value, done: false};
    }

    return next();
  });
}
943717
@SuppressWarnings({ "unchecked", "rawtypes" })
	public <NV> Graph<K, NV, EV> mapVertices(final MapFunction<Vertex<K, VV>, NV> mapper) {

		TypeInformation<K> keyType = ((TupleTypeInfo<?>) vertices.getType()).getTypeAt(0);

		TypeInformation<NV> valueType;

		if (mapper instanceof ResultTypeQueryable) {
			valueType = ((ResultTypeQueryable) mapper).getProducedType();
		} else {
			valueType = TypeExtractor.createTypeInfo(MapFunction.class, mapper.getClass(), 1, vertices.getType(), null);
		}

		TypeInformation<Vertex<K, NV>> returnType = (TypeInformation<Vertex<K, NV>>) new TupleTypeInfo(
				Vertex.class, keyType, valueType);

		return mapVertices(mapper, returnType);
	}
853142
def _create_union_types_specification(schema_graph, graphql_types, hidden_classes, base_name):
    """"""
    # When edges point to vertices of type base_name, and base_name is both non-abstract and
    # has subclasses, we need to represent the edge endpoint type with a union type based on
    # base_name and its subclasses. This function calculates what types that union should include.
    def types_spec():
        """Return a list of GraphQL types that this class' corresponding union type includes."""
        return [
            graphql_types[x]
            for x in sorted(list(schema_graph.get_subclass_set(base_name)))
            if x not in hidden_classes
        ]

    return types_spec
361841
def get_node_edges(self,node,type="both"):
    
    if type == "both":
      return [self.__edges[x] for x in self.__edges if node.id in self.__edges[x].get_node_ids()]
    elif type == "outgoing":
      return [self.__edges[x] for x in self.__edges if node.id == self.__edges[x].get_node_ids()[0]]
    elif type == "incoming":
      return [self.__edges[x] for x in self.__edges if node.id == self.__edges[x].get_node_ids()[1]]
    sys.stderr.write("ERROR: type is not a type "+type+"\n")
    sys.exit()
1227166
public static <T extends GraphQLType> List<T> sortGraphQLTypes(Collection<T> types) {
        List<T> sorted = new ArrayList<>(types);
        sorted.sort(graphQLTypeComparator());
        return sorted;
    }
679066
function(type) {
			var result = [];
			var crawler = new gui.Crawler();
			if (type) {
				crawler.ascend(this.element, {
					handleSpirit: function(spirit) {
						if (spirit instanceof type) {
							result.push(spirit);
						}
					}
				});
			} else {
				crawler.ascend(this.element, {
					handleElement: function(el) {
						result.push(el);
					}
				});
			}
			return result;
		}
994365
function graph(g) {
    init(g);

    var xs = [upperLeft(g), upperRight(g), lowerRight(g), lowerLeft(g)];

    g.nodes().forEach(function(u) {
      var xArray = [];
      for (var i = 0; i < xs.length; ++i) {
        xArray.push(xs[i][u]);
      }
      xArray.sort();
      g.node(u).x = (xArray[1] + xArray[2]) / 2;
    });
  }
210011
function Node(t, type) {
        // EDIT: "inherit" from Array in an x-browser way.
        var _this = [];
        for (var n in Node.prototype)
            _this[n] = Node.prototype[n];

        _this.constructor = Node;

        var token = t.token();
        if (token) {
            _this.type = type || token.type;
            _this.value = token.value;
            _this.lineno = token.lineno;
            _this.start = token.start;
            _this.end = token.end;
        } else {
            _this.type = type;
            _this.lineno = t.lineno;
        }
        _this.tokenizer = t;

        for (var i = 2; i < arguments.length; i++)
            _this.push(arguments[i]);

        return _this;
    }
1117595
function attachForEachEdge(Class, description) {
  const {
    name,
    type,
    direction
  } = description;

  const forEachName = 'forEach' + name[0].toUpperCase() + name.slice(1, -1);

  /**
   * Function iterating over the graph's relevant edges by applying the given
   * callback.
   *
   * Arity 1: Iterate over all the relevant edges.
   * @param  {function} callback - Callback to use.
   *
   * Arity 2: Iterate over all of a node's relevant edges.
   * @param  {any}      node     - Target node.
   * @param  {function} callback - Callback to use.
   *
   * Arity 3: Iterate over the relevant edges across the given path.
   * @param  {any}      source   - Source node.
   * @param  {any}      target   - Target node.
   * @param  {function} callback - Callback to use.
   *
   * @return {undefined}
   *
   * @throws {Error} - Will throw if there are too many arguments.
   */
  Class.prototype[forEachName] = function(source, target, callback) {

    // Early termination
    if (type !== 'mixed' && this.type !== 'mixed' && type !== this.type)
      return;

    if (arguments.length === 1) {
      callback = source;
      return forEachEdge(this, type, callback);
    }

    if (arguments.length === 2) {
      source = '' + source;
      callback = target;

      const nodeData = this._nodes.get(source);

      if (typeof nodeData === 'undefined')
        throw new NotFoundGraphError(`Graph.${forEachName}: could not find the "${source}" node in the graph.`);

      // Iterating over a node's edges
      return forEachEdgeForNode(
        type === 'mixed' ? this.type : type,
        direction,
        nodeData,
        callback
      );
    }

    if (arguments.length === 3) {
      source = '' + source;
      target = '' + target;

      const sourceData = this._nodes.get(source);

      if (!sourceData)
        throw new NotFoundGraphError(`Graph.${forEachName}:  could not find the "${source}" source node in the graph.`);

      if (!this._nodes.has(target))
        throw new NotFoundGraphError(`Graph.${forEachName}:  could not find the "${target}" target node in the graph.`);

      // Iterating over the edges between source & target
      return forEachEdgeForPath(type, direction, sourceData, target, callback);
    }

    throw new InvalidArgumentsGraphError(`Graph.${forEachName}: too many arguments (expecting 1, 2 or 3 and got ${arguments.length}).`);
  };
}
179169
function createGraph(options) {
  // Graph structure is maintained as dictionary of nodes
  // and array of links. Each node has 'links' property which
  // hold all links related to that node. And general links
  // array is used to speed up all links enumeration. This is inefficient
  // in terms of memory, but simplifies coding.
  options = options || {};
  if ('uniqueLinkId' in options) {
    console.warn(
      'ngraph.graph: Starting from version 0.14 `uniqueLinkId` is deprecated.\n' +
      'Use `multigraph` option instead\n',
      '\n',
      'Note: there is also change in default behavior: From now on each graph\n'+
      'is considered to be not a multigraph by default (each edge is unique).'
    );

    options.multigraph = options.uniqueLinkId;
  }

  // Dear reader, the non-multigraphs do not guarantee that there is only
  // one link for a given pair of node. When this option is set to false
  // we can save some memory and CPU (18% faster for non-multigraph);
  if (options.multigraph === undefined) options.multigraph = false;

  var nodes = typeof Object.create === 'function' ? Object.create(null) : {},
    links = [],
    // Hash of multi-edges. Used to track ids of edges between same nodes
    multiEdges = {},
    nodesCount = 0,
    suspendEvents = 0,

    forEachNode = createNodeIterator(),
    createLink = options.multigraph ? createUniqueLink : createSingleLink,

    // Our graph API provides means to listen to graph changes. Users can subscribe
    // to be notified about changes in the graph by using `on` method. However
    // in some cases they don't use it. To avoid unnecessary memory consumption
    // we will not record graph changes until we have at least one subscriber.
    // Code below supports this optimization.
    //
    // Accumulates all changes made during graph updates.
    // Each change element contains:
    //  changeType - one of the strings: 'add', 'remove' or 'update';
    //  node - if change is related to node this property is set to changed graph's node;
    //  link - if change is related to link this property is set to changed graph's link;
    changes = [],
    recordLinkChange = noop,
    recordNodeChange = noop,
    enterModification = noop,
    exitModification = noop;

  // this is our public API:
  var graphPart = {
    /**
     * Adds node to the graph. If node with given id already exists in the graph
     * its data is extended with whatever comes in 'data' argument.
     *
     * @param nodeId the node's identifier. A string or number is preferred.
     * @param [data] additional data for the node being added. If node already
     *   exists its data object is augmented with the new one.
     *
     * @return {node} The newly added node or node with given id if it already exists.
     */
    addNode: addNode,

    /**
     * Adds a link to the graph. The function always create a new
     * link between two nodes. If one of the nodes does not exists
     * a new node is created.
     *
     * @param fromId link start node id;
     * @param toId link end node id;
     * @param [data] additional data to be set on the new link;
     *
     * @return {link} The newly created link
     */
    addLink: addLink,

    /**
     * Removes link from the graph. If link does not exist does nothing.
     *
     * @param link - object returned by addLink() or getLinks() methods.
     *
     * @returns true if link was removed; false otherwise.
     */
    removeLink: removeLink,

    /**
     * Removes node with given id from the graph. If node does not exist in the graph
     * does nothing.
     *
     * @param nodeId node's identifier passed to addNode() function.
     *
     * @returns true if node was removed; false otherwise.
     */
    removeNode: removeNode,

    /**
     * Gets node with given identifier. If node does not exist undefined value is returned.
     *
     * @param nodeId requested node identifier;
     *
     * @return {node} in with requested identifier or undefined if no such node exists.
     */
    getNode: getNode,

    /**
     * Gets number of nodes in this graph.
     *
     * @return number of nodes in the graph.
     */
    getNodesCount: function () {
      return nodesCount;
    },

    /**
     * Gets total number of links in the graph.
     */
    getLinksCount: function () {
      return links.length;
    },

    /**
     * Gets all links (inbound and outbound) from the node with given id.
     * If node with given id is not found null is returned.
     *
     * @param nodeId requested node identifier.
     *
     * @return Array of links from and to requested node if such node exists;
     *   otherwise null is returned.
     */
    getLinks: getLinks,

    /**
     * Invokes callback on each node of the graph.
     *
     * @param {Function(node)} callback Function to be invoked. The function
     *   is passed one argument: visited node.
     */
    forEachNode: forEachNode,

    /**
     * Invokes callback on every linked (adjacent) node to the given one.
     *
     * @param nodeId Identifier of the requested node.
     * @param {Function(node, link)} callback Function to be called on all linked nodes.
     *   The function is passed two parameters: adjacent node and link object itself.
     * @param oriented if true graph treated as oriented.
     */
    forEachLinkedNode: forEachLinkedNode,

    /**
     * Enumerates all links in the graph
     *
     * @param {Function(link)} callback Function to be called on all links in the graph.
     *   The function is passed one parameter: graph's link object.
     *
     * Link object contains at least the following fields:
     *  fromId - node id where link starts;
     *  toId - node id where link ends,
     *  data - additional data passed to graph.addLink() method.
     */
    forEachLink: forEachLink,

    /**
     * Suspend all notifications about graph changes until
     * endUpdate is called.
     */
    beginUpdate: enterModification,

    /**
     * Resumes all notifications about graph changes and fires
     * graph 'changed' event in case there are any pending changes.
     */
    endUpdate: exitModification,

    /**
     * Removes all nodes and links from the graph.
     */
    clear: clear,

    /**
     * Detects whether there is a link between two nodes.
     * Operation complexity is O(n) where n - number of links of a node.
     * NOTE: this function is synonim for getLink()
     *
     * @returns link if there is one. null otherwise.
     */
    hasLink: getLink,

    /**
     * Detects whether there is a node with given id
     * 
     * Operation complexity is O(1)
     * NOTE: this function is synonim for getNode()
     *
     * @returns node if there is one; Falsy value otherwise.
     */
    hasNode: getNode,

    /**
     * Gets an edge between two nodes.
     * Operation complexity is O(n) where n - number of links of a node.
     *
     * @param {string} fromId link start identifier
     * @param {string} toId link end identifier
     *
     * @returns link if there is one. null otherwise.
     */
    getLink: getLink
  };

  // this will add `on()` and `fire()` methods.
  eventify(graphPart);

  monitorSubscribers();

  return graphPart;

  function monitorSubscribers() {
    var realOn = graphPart.on;

    // replace real `on` with our temporary on, which will trigger change
    // modification monitoring:
    graphPart.on = on;

    function on() {
      // now it's time to start tracking stuff:
      graphPart.beginUpdate = enterModification = enterModificationReal;
      graphPart.endUpdate = exitModification = exitModificationReal;
      recordLinkChange = recordLinkChangeReal;
      recordNodeChange = recordNodeChangeReal;

      // this will replace current `on` method with real pub/sub from `eventify`.
      graphPart.on = realOn;
      // delegate to real `on` handler:
      return realOn.apply(graphPart, arguments);
    }
  }

  function recordLinkChangeReal(link, changeType) {
    changes.push({
      link: link,
      changeType: changeType
    });
  }

  function recordNodeChangeReal(node, changeType) {
    changes.push({
      node: node,
      changeType: changeType
    });
  }

  function addNode(nodeId, data) {
    if (nodeId === undefined) {
      throw new Error('Invalid node identifier');
    }

    enterModification();

    var node = getNode(nodeId);
    if (!node) {
      node = new Node(nodeId, data);
      nodesCount++;
      recordNodeChange(node, 'add');
    } else {
      node.data = data;
      recordNodeChange(node, 'update');
    }

    nodes[nodeId] = node;

    exitModification();
    return node;
  }

  function getNode(nodeId) {
    return nodes[nodeId];
  }

  function removeNode(nodeId) {
    var node = getNode(nodeId);
    if (!node) {
      return false;
    }

    enterModification();

    var prevLinks = node.links;
    if (prevLinks) {
      node.links = null;
      for(var i = 0; i < prevLinks.length; ++i) {
        removeLink(prevLinks[i]);
      }
    }

    delete nodes[nodeId];
    nodesCount--;

    recordNodeChange(node, 'remove');

    exitModification();

    return true;
  }


  function addLink(fromId, toId, data) {
    enterModification();

    var fromNode = getNode(fromId) || addNode(fromId);
    var toNode = getNode(toId) || addNode(toId);

    var link = createLink(fromId, toId, data);

    links.push(link);

    // TODO: this is not cool. On large graphs potentially would consume more memory.
    addLinkToNode(fromNode, link);
    if (fromId !== toId) {
      // make sure we are not duplicating links for self-loops
      addLinkToNode(toNode, link);
    }

    recordLinkChange(link, 'add');

    exitModification();

    return link;
  }

  function createSingleLink(fromId, toId, data) {
    var linkId = makeLinkId(fromId, toId);
    return new Link(fromId, toId, data, linkId);
  }

  function createUniqueLink(fromId, toId, data) {
    // TODO: Get rid of this method.
    var linkId = makeLinkId(fromId, toId);
    var isMultiEdge = multiEdges.hasOwnProperty(linkId);
    if (isMultiEdge || getLink(fromId, toId)) {
      if (!isMultiEdge) {
        multiEdges[linkId] = 0;
      }
      var suffix = '@' + (++multiEdges[linkId]);
      linkId = makeLinkId(fromId + suffix, toId + suffix);
    }

    return new Link(fromId, toId, data, linkId);
  }

  function getLinks(nodeId) {
    var node = getNode(nodeId);
    return node ? node.links : null;
  }

  function removeLink(link) {
    if (!link) {
      return false;
    }
    var idx = indexOfElementInArray(link, links);
    if (idx < 0) {
      return false;
    }

    enterModification();

    links.splice(idx, 1);

    var fromNode = getNode(link.fromId);
    var toNode = getNode(link.toId);

    if (fromNode) {
      idx = indexOfElementInArray(link, fromNode.links);
      if (idx >= 0) {
        fromNode.links.splice(idx, 1);
      }
    }

    if (toNode) {
      idx = indexOfElementInArray(link, toNode.links);
      if (idx >= 0) {
        toNode.links.splice(idx, 1);
      }
    }

    recordLinkChange(link, 'remove');

    exitModification();

    return true;
  }

  function getLink(fromNodeId, toNodeId) {
    // TODO: Use sorted links to speed this up
    var node = getNode(fromNodeId),
      i;
    if (!node || !node.links) {
      return null;
    }

    for (i = 0; i < node.links.length; ++i) {
      var link = node.links[i];
      if (link.fromId === fromNodeId && link.toId === toNodeId) {
        return link;
      }
    }

    return null; // no link.
  }

  function clear() {
    enterModification();
    forEachNode(function(node) {
      removeNode(node.id);
    });
    exitModification();
  }

  function forEachLink(callback) {
    var i, length;
    if (typeof callback === 'function') {
      for (i = 0, length = links.length; i < length; ++i) {
        callback(links[i]);
      }
    }
  }

  function forEachLinkedNode(nodeId, callback, oriented) {
    var node = getNode(nodeId);

    if (node && node.links && typeof callback === 'function') {
      if (oriented) {
        return forEachOrientedLink(node.links, nodeId, callback);
      } else {
        return forEachNonOrientedLink(node.links, nodeId, callback);
      }
    }
  }

  function forEachNonOrientedLink(links, nodeId, callback) {
    var quitFast;
    for (var i = 0; i < links.length; ++i) {
      var link = links[i];
      var linkedNodeId = link.fromId === nodeId ? link.toId : link.fromId;

      quitFast = callback(nodes[linkedNodeId], link);
      if (quitFast) {
        return true; // Client does not need more iterations. Break now.
      }
    }
  }

  function forEachOrientedLink(links, nodeId, callback) {
    var quitFast;
    for (var i = 0; i < links.length; ++i) {
      var link = links[i];
      if (link.fromId === nodeId) {
        quitFast = callback(nodes[link.toId], link);
        if (quitFast) {
          return true; // Client does not need more iterations. Break now.
        }
      }
    }
  }

  // we will not fire anything until users of this library explicitly call `on()`
  // method.
  function noop() {}

  // Enter, Exit modification allows bulk graph updates without firing events.
  function enterModificationReal() {
    suspendEvents += 1;
  }

  function exitModificationReal() {
    suspendEvents -= 1;
    if (suspendEvents === 0 && changes.length > 0) {
      graphPart.fire('changed', changes);
      changes.length = 0;
    }
  }

  function createNodeIterator() {
    // Object.keys iterator is 1.3x faster than `for in` loop.
    // See `https://github.com/anvaka/ngraph.graph/tree/bench-for-in-vs-obj-keys`
    // branch for perf test
    return Object.keys ? objectKeysIterator : forInIterator;
  }

  function objectKeysIterator(callback) {
    if (typeof callback !== 'function') {
      return;
    }

    var keys = Object.keys(nodes);
    for (var i = 0; i < keys.length; ++i) {
      if (callback(nodes[keys[i]])) {
        return true; // client doesn't want to proceed. Return.
      }
    }
  }

  function forInIterator(callback) {
    if (typeof callback !== 'function') {
      return;
    }
    var node;

    for (node in nodes) {
      if (callback(nodes[node])) {
        return true; // client doesn't want to proceed. Return.
      }
    }
  }
}
1017613
def graph_type(self, graph):
        """"""
        graph = self.pack(graph)
        return self.sql('graph_type', graph).fetchone()[0]
537166
function loadGraphSON(graph, filename, callback) {
        var FileInputStream = Tinkerpop.autoImport('FileInputStream');
        var stream = new FileInputStream(filename);
        return Tinkerpop.GraphSONReader.buildP()
            .then(function (builder) {
            return _newGraphSONMapper()
                .then(function (mapper) { return builder.mapperP(mapper); });
        })
            .then(function (builder) { return builder.createP(); })
            .then(function (reader) { return reader.readGraphP(stream, graph); })
            .then(function () { return graph; })
            .nodeify(callback);
    }
638648
public <NV> Graph<K, VV, NV> mapEdges(final MapFunction<Edge<K, EV>, NV> mapper, TypeInformation<Edge<K, NV>> returnType) {
		DataSet<Edge<K, NV>> mappedEdges = edges.map(
			new MapFunction<Edge<K, EV>, Edge<K, NV>>() {
				private Edge<K, NV> output = new Edge<>();

				public Edge<K, NV> map(Edge<K, EV> value) throws Exception {
					output.f0 = value.f0;
					output.f1 = value.f1;
					output.f2 = mapper.map(value);
					return output;
				}
			})
			.returns(returnType)
			.withForwardedFields("f0; f1")
				.name("Map edges");

		return new Graph<>(this.vertices, mappedEdges, this.context);
	}
861715
function _getLinks(node) {
  var links = [];

  node.ifForEach('inEdgeIds', function (inEdge) {
    var edge = _data.edges.get(inEdge);
    edge.setProperties({
      sourceId: edge.get('inputVertexName'),
      targetId: edge.get('outputVertexName')
    });
    links.push(edge);
  });

  if(node.type === types.INPUT) {
    links.push(Ember.Object.create({
      sourceId: node.get('id'),
      targetId: node.get('vertex.id')
    }));
  }
  else if(node.type === types.OUTPUT) {
    links.push(Ember.Object.create({
      sourceId: node.get('vertex.id'),
      targetId: node.get('id')
    }));
  }

  return links;
}
480316
def implicit_graph
      result = ImplicitGraph.new { |g|
        g.vertex_iterator { |b| self.each_vertex(&b) }
        g.adjacent_iterator { |v, b| self.each_adjacent(v, &b) }
        g.directed = self.directed?
      }
      yield result if block_given? # let client overwrite defaults
      result
    end
492726
function(type) {
			var result = gui.Array.from(this.spirit.element.children);
			if (type) {
				result = result.filter(function(elm) {
					return elm.spirit && elm.spirit instanceof type;
				}).map(function(elm) {
					return elm.spirit;
				});
			}
			return result;
		}
904684
function Digraph(pkGloballyUniqueId)
{
    var self = this;

    var pkGuid              = pkGloballyUniqueId;
    var vertexArray         = [];       // Note: these three arrays are kept in parallel for ease of access as well as to increase search speed
    var vertexIdArray       = [];
    var vertexStateArray    = [];

    var startVertexIdArray  = [];       // Note: these two arrays are how directed edges are stored
    var endVertexIdArray    = [];


    /**
     * Is there a vertex in the digraph with that ID?
     *
     * @param {string} vertexId - ID of a vertex
     * @returns {boolean}
     */
    this.hasVertexId = function (vertexId)
    {
        return vertexIdArray.indexOf(vertexId) > -1;
    };

    /**
     * Creates a new vertex in the digraph
     *
     * @param {string} id - ID of the new vertex
     * @param {Object} data - Optional data object associated with the vertex
     * @returns {Vertex}
     */
    this.addVertex = function (id, data)
    {
        if(vertexIdArray.indexOf(id) > -1)
        {
            throw new Error("Duplicate Vertex ID", "Vertex with the ID " + id + " already exists!");
        }
        else
        {
            var newVertex = new Vertex(pkGuid, id, "NOT_READY", data);
            newVertex.parent = self;
            vertexIdArray.push(id);
            vertexStateArray.push("NOT_READY");
            vertexArray.push(newVertex);
            return newVertex;
        }
    };

    /**
     * Adds a directed edge to the digraph from startVertexId to endVertexId
     *
     * @param {string} startVertexId - Where the edge starts
     * @param {string} endVertexId - Where the edge ends
     */
    this.addEdge = function (startVertexId, endVertexId)
    {
        if(vertexIdArray.indexOf(startVertexId) == -1)
        {
            throw new Error("Start Vertex Not Found", "Start vertex (with the ID " + startVertexId + ") not found!");
        }
        else if(vertexIdArray.indexOf(endVertexId) == -1)
        {
            throw new Error("End Vertex Not Found", "End vertex (with the ID " + endVertexId + ") not found!");
        }
        else if(startVertexId == endVertexId)
        {
            throw new Error("Loops Not Allowed", "The start vertex and the end vertex are the same (vertex ID " + startVertexId + ")!");
        }
        else
        {
            var numEdges = startVertexIdArray.length;
            var duplicateEdgeFound = false;

            for(var i = 0; i < numEdges; i++)
            {
                if(startVertexIdArray[i] == startVertexId && endVertexIdArray[i] == endVertexId)
                {
                    duplicateEdgeFound = true;
                    break;
                }
            }

            if(duplicateEdgeFound)
            {
                throw new Error("Edge Already Exists", "Edge (" + startVertexId + ", " + endVertexId + ") already exists!");
            }
            else
            {
                startVertexIdArray.push(startVertexId);
                endVertexIdArray.push(endVertexId);
            }
        }
    };

    /**
     * Removes the vertex with the given vertexId
     *
     * @param {string} vertexId - ID of the vertex to remove
     */
    this.removeVertex = function (vertexId)
    {
        if(vertexIdArray.index(vertexId) == -1)
        {
            throw new Error("Vertex Not Found", "Vertex (with the ID " + vertexId + ") not found!");
        }
        else
        {
            // First, remove it from the vertex arrays
            var vertexIndex = vertexIdArray.indexOf(vertexId);
            vertexIdArray.splice(vertexIndex, 1);
            vertexStateArray.splice(vertexIndex, 1);
            vertexArray.splice(vertexIndex, 1);

            // Now remove the edges
            var numEdges = startVertexIdArray.length;
            for(var i = numEdges-1; i >= 0; i--)
            {
                if(startVertexIdArray[i] == vertexId || endVertexIdArray[i] == vertexId)
                {
                    startVertexIdArray.splice(i, 1);
                    endVertexIdArray.splice(i, 1);
                }
            }
        }
    };

    /**
     * Removes the edge going from startVertexId to endVertexId
     *
     * @param {string} startVertexId - Where the edge starts
     * @param {string} endVertexId - Where the edge ends
     */
    this.removeEdge = function (startVertexId, endVertexId)
    {
        var numEdges = startVertexIdArray.length;
        var edgeIndex = -1;

        // NOTE: this next part assumes that no duplicate edges exist
        for(var i = 0; i < numEdges; i++)
        {
            if(startVertexIdArray[i] == startVertexId && endVertexIdArray[i] == endVertexId)
            {
                edgeIndex = i;
                break;
            }
        }

        if(edgeIndex == -1)
        {
            throw new Error("Edge Doesn't Exist", "Edge (" + startVertexId + ", " + endVertexId + ") does not exist!");
        }
        else
        {
            startVertexIdArray.splice(edgeIndex, 1);
            endVertexIdArray.splice(edgeIndex, 1);
        }
    };

    /**
     * Gets all child vertices (objects) of the vertex with the given parentVertexId
     *
     * @param {string} parentVertexId - ID of the parent vertex
     * @returns {Array}
     */
    this.getChildVertices = function (parentVertexId)
    {
        var numEdges = startVertexIdArray.length;
        var childVertices = [];

        for(var i = 0; i < numEdges; i++)
        {
            if(startVertexIdArray[i] == parentVertexId)
            {
                childVertices.push(vertexArray[vertexIdArray.indexOf(endVertexIdArray[i])]);
            }
        }

        return childVertices;
    };

    /**
     * Gets the IDs of all child vertices of parentVertexId
     *
     * @param {string} parentVertexId - ID of the parent vertex
     * @returns {Array}
     */
    this.getChildVertexIds = function (parentVertexId)
    {
        var numEdges = startVertexIdArray.length;
        var childVertexIds = [];

        for(var i = 0; i < numEdges; i++)
        {
            if(startVertexIdArray[i] == parentVertexId)
            {
                childVertexIds.push(vertexArray[vertexIdArray.indexOf(endVertexIdArray[i])].id);
            }
        }

        return childVertexIds;
    };

    /**
     * Gets all parent vertices (objects) of the vertex with the given childVertexId
     *
     * @param {string} childVertexId - ID of the child vertex
     * @returns {Array}
     */
    this.getParentVertices = function (childVertexId)
    {
        var numEdges = startVertexIdArray.length;
        var parentVertices = [];

        for(var i = 0; i < numEdges; i++)
        {
            if(endVertexIdArray[i] == childVertexId)
            {
                parentVertices.push(vertexArray[vertexIdArray.indexOf(startVertexIdArray[i])]);
            }
        }

        return parentVertices;
    };

    /**
     * Gets the IDs of all parents of the specified vertex
     *
     * @param {string} childVertexId - ID of the child vertex
     * @returns {Array}
     */
    this.getParentVertexIds = function (childVertexId)
    {
        var numEdges = startVertexIdArray.length;
        var parentVertexIds = [];

        for(var i = 0; i < numEdges; i++)
        {
            if(endVertexIdArray[i] == childVertexId)
            {
                parentVertexIds.push(vertexArray[vertexIdArray.indexOf(startVertexIdArray[i])].id);
            }
        }

        return parentVertexIds;
    };

    /**
     * How many edges are coming into this vertex?
     *
     * @param {string} vertexId - The vertex ID
     * @returns {number}
     */
    this.indegree = function (vertexId)
    {
        var numEdges = startVertexIdArray.length;
        var count = 0;

        for(var i = 0; i < numEdges; i++)
        {
            if(endVertexIdArray[i] == vertexId)
            {
                count++;
            }
        }
        return count;
    };

    /**
     * How many edges are leaving from this vertex?
     *
     * @param {string} vertexId - The vertex ID
     * @returns {number}
     */
    this.outdegree = function (vertexId)
    {
        var numEdges = startVertexIdArray.length;
        var count = 0;

        for(var i = 0; i < numEdges; i++)
        {
            if(startVertexIdArray[i] == vertexId)
            {
                count++;
            }
        }
        return count;
    };

    /**
     * Returns list of vertices (objects) with indegree == 0
     *
     * @returns {Array}
     */
    this.getVerticesWithIndegree0 = function ()
    {
        var numVertices = vertexArray.length;
        var verticesWithIndegree0 = [];

        for(var i = 0; i < numVertices; i++)
        {
            if(endVertexIdArray.indexOf(vertexArray[i].id) == -1)
            {
                verticesWithIndegree0.push(vertexArray[i]);
            }
        }

        return verticesWithIndegree0;
    };

    /**
     * Gets vertex (object) with specified ID
     *
     * @param {string} vertexId - The vertex ID
     * @returns {*}
     */
    this.getVertex = function (vertexId)
    {
        var vertexIndex = vertexIdArray.indexOf(vertexId);

        if(vertexIndex == -1)
        {
            throw new Error("Vertex Not Found", "Vertex (with the ID " + vertexId + ") not found!");
        }
        else
        {
            return vertexArray[vertexIndex];
        }
    };

    /**
     * Returns list of the IDs of all the vertices
     *
     * @returns {Array}
     */
    this.getVertexIds = function ()
    {
        var vertexIds = [];
        var numVertices = vertexArray.length;

        for(var i = 0; i < numVertices; i++)
        {
            vertexIds.push(vertexIdArray[i]);
        }

        return vertexIds;
    };

    /**
     * How many vertices are in the digraph?
     *
     * @returns {Number}
     */
    this.vertexCount = function ()
    {
        return vertexArray.length;
    };

    /**
     * How many edges are in the digraph?
     *
     * @returns {Number}
     */
    this.edgeCount = function ()
    {
        return startVertexIdArray.length;
    };

    /**
     * Performs a topological sort on the vertices of the digraph, implemented using Kahn's algorithm <br />
     * See Wikipedia for details: {@link http://en.wikipedia.org/wiki/Topological_sort}
     *
     * @returns {Array}
     */
    this.topologicalSort = function ()
    {
        // Based upon Kahn's algorithm from
        // http://en.wikipedia.org/wiki/Topological_sort

        // Make a copy of the edge set, since Kahn's algorithm will destroy that info!
        // We'll restore the info at the end
        var edgeInfo = this.getEdgeInfo();

        var L = [];
        var S = this.getVerticesWithIndegree0();

        while(S.length > 0)
        {
            var n = S.pop();
            L.push(n);
            var childNodes = this.getChildVertices(n.id);
            var numChildren = childNodes.length;
            for(var i = 0; i < numChildren; i++)
            {
                var m = childNodes[i];
                this.removeEdge(n.id, m.id);

                if(this.indegree(m.id) == 0)
                {
                    S.push(m);
                }
            }
        }
        if(this.edgeCount() > 0)
        {
            this.setEdgeInfo(edgeInfo);
            return [];
        }
        else
        {
            this.setEdgeInfo(edgeInfo);
            return L;
        }
    };

    /**
     * Returns a clone of the start and end vertex arrays - used as part of the topological sort, since it is destructive
     *
     * @returns {{startVertexIdArray: *, endVertexIdArray: *}}
     */
    this.getEdgeInfo = function ()
    {
        var edgeInfo = {
            "startVertexIdArray": clone(startVertexIdArray),
            "endVertexIdArray": clone(endVertexIdArray)
        };

        return edgeInfo;
    };

    /**
     * Sets the start and end vertex arrays - used as part of the topological sort, since it is destructive
     *
     * @param edgeInfo
     */
    this.setEdgeInfo = function (edgeInfo)
    {
        startVertexIdArray = clone(edgeInfo.startVertexIdArray);
        endVertexIdArray = clone(edgeInfo.endVertexIdArray);
    };

    /**
     * Creates a deep copy of a JS object
     * Code from {@link http://stackoverflow.com/questions/728360/most-elegant-way-to-clone-a-javascript-object}
     *
     * @private
     * @param {Object} obj - Opject to clone
     * @returns {*}
     */
    function clone(obj)
    {
        // Based upon http://stackoverflow.com/questions/728360/most-elegant-way-to-clone-a-javascript-object
        if(obj == null || typeof obj != "object")
        {
            return obj;
        }
        else
        {
            var copy = obj.constructor();
            for(var attr in obj)
            {
                if(obj.hasOwnProperty(attr))
                {
                    copy[attr] = obj[attr];
                }
            }
            return copy;
        }
    }

    /**
     * Returns the digraph's vertices and edges, formatted all pretty like
     *
     * @returns {string}
     */
    this.asString = function ()
    {
        var numEdges = startVertexIdArray.length;
        var edgeArray = [];

        for(var i = 0; i < numEdges; i++)
        {
            edgeArray.push({"start":startVertexIdArray[i], "end":endVertexIdArray[i]});
        }

        var str = "";

        str += "{" + "\n";
        str += "     \"vertices\": " + JSON.stringify(vertexArray) + "," + "\n";
        str += "     \"edges\": " + JSON.stringify(edgeArray) + "\n";
        str += "}";

        return str;
    };
}