All Downloads are FREE. Search and download functionalities are using the official Maven repository.

edu.stanford.nlp.graph.DirectedMultiGraph Maven / Gradle / Ivy

Go to download

Stanford CoreNLP provides a set of natural language analysis tools which can take raw English language text input and give the base forms of words, their parts of speech, whether they are names of companies, people, etc., normalize dates, times, and numeric quantities, mark up the structure of sentences in terms of phrases and word dependencies, and indicate which noun phrases refer to the same entities. It provides the foundational building blocks for higher level text understanding applications.

There is a newer version: 4.5.7
Show newest version
package edu.stanford.nlp.graph;

import java.util.*;

import edu.stanford.nlp.util.CollectionUtils;
import edu.stanford.nlp.util.Generics;
import edu.stanford.nlp.util.MapFactory;

/**
 * Simple graph library; this is directed for now. This class focuses on time
 * efficiency rather than memory efficiency.
 *
 * @author sonalg
 * @author John Bauer
 *
 * @param 
 *          Type of vertices
 * @param 
 *          Type of edges.
 */

public class DirectedMultiGraph implements Graph /* Serializable */{

  final Map>> outgoingEdges;

  final Map>> incomingEdges;

  final MapFactory>> outerMapFactory;
  final MapFactory> innerMapFactory;

  public DirectedMultiGraph() {
    this(MapFactory.>>hashMapFactory(), MapFactory.>hashMapFactory());
  }

  public DirectedMultiGraph(MapFactory>> outerMapFactory, MapFactory> innerMapFactory) {
    this.outerMapFactory = outerMapFactory;
    this.innerMapFactory = innerMapFactory;
    this.outgoingEdges = outerMapFactory.newMap();
    this.incomingEdges = outerMapFactory.newMap();
  }

  /**
   * Creates a copy of the given graph. This will copy the entire data
   * structure (this may be slow!), but will not copy any of the edge
   * or vertex objects.
   *
   * @param graph The graph to copy into this object.
   */
  public DirectedMultiGraph(DirectedMultiGraph graph) {
    this(graph.outerMapFactory, graph.innerMapFactory);
    for (Map.Entry>> map : graph.outgoingEdges.entrySet()) {
      Map> edgesCopy = innerMapFactory.newMap();
      for (Map.Entry> entry : map.getValue().entrySet()) {
        edgesCopy.put(entry.getKey(), Generics.newArrayList(entry.getValue()));
      }
      this.outgoingEdges.put(map.getKey(), edgesCopy);
    }
    for (Map.Entry>> map : graph.incomingEdges.entrySet()) {
      Map> edgesCopy = innerMapFactory.newMap();
      for (Map.Entry> entry : map.getValue().entrySet()) {
        edgesCopy.put(entry.getKey(), Generics.newArrayList(entry.getValue()));
      }
      this.incomingEdges.put(map.getKey(), edgesCopy);
    }
  }

  /**
   * Be careful hashing these. They are mutable objects, and changing the object
   * will throw off the hash code, messing up your hash table
   */
  public int hashCode() {
    return outgoingEdges.hashCode();
  }

  public boolean equals(Object that) {
    if (that == this)
      return true;
    if (!(that instanceof DirectedMultiGraph))
      return false;
    return outgoingEdges.equals(((DirectedMultiGraph) that).outgoingEdges);
  }

  /**
   * For adding a zero degree vertex
   *
   * @param v
   */
  @Override
  public boolean addVertex(V v) {
    if (outgoingEdges.containsKey(v))
      return false;
    outgoingEdges.put(v, innerMapFactory.newMap());
    incomingEdges.put(v, innerMapFactory.newMap());
    return true;
  }

  private Map> getOutgoingEdgesMap(V v) {
    Map> map = outgoingEdges.get(v);
    if (map == null) {
      map = innerMapFactory.newMap();
      outgoingEdges.put(v, map);
      incomingEdges.put(v, innerMapFactory.newMap());
    }
    return map;
  }

  private Map> getIncomingEdgesMap(V v) {
    Map> map = incomingEdges.get(v);
    if (map == null) {
      outgoingEdges.put(v, innerMapFactory.newMap());
      map = innerMapFactory.newMap();
      incomingEdges.put(v, map);
    }
    return map;
  }

  /**
   * adds vertices (if not already in the graph) and the edge between them
   *
   * @param source
   * @param dest
   * @param data
   */
  @Override
  public void add(V source, V dest, E data) {
    Map> outgoingMap = getOutgoingEdgesMap(source);
    Map> incomingMap = getIncomingEdgesMap(dest);

    List outgoingList = outgoingMap.get(dest);
    if (outgoingList == null) {
      outgoingList = new ArrayList<>();
      outgoingMap.put(dest, outgoingList);
    }

    List incomingList = incomingMap.get(source);
    if (incomingList == null) {
      incomingList = new ArrayList<>();
      incomingMap.put(source, incomingList);
    }

    outgoingList.add(data);
    incomingList.add(data);
  }

  @Override
  public boolean removeEdges(V source, V dest) {
    if (!outgoingEdges.containsKey(source)) {
      return false;
    }
    if (!incomingEdges.containsKey(dest)) {
      return false;
    }
    if (!outgoingEdges.get(source).containsKey(dest)) {
      return false;
    }
    outgoingEdges.get(source).remove(dest);
    incomingEdges.get(dest).remove(source);
    return true;
  }

  @Override
  public boolean removeEdge(V source, V dest, E data) {
    if (!outgoingEdges.containsKey(source)) {
      return false;
    }
    if (!incomingEdges.containsKey(dest)) {
      return false;
    }
    if (!outgoingEdges.get(source).containsKey(dest)) {
      return false;
    }
    boolean foundOut = outgoingEdges.containsKey(source) && outgoingEdges.get(source).containsKey(dest) &&
        outgoingEdges.get(source).get(dest).remove(data);
    boolean foundIn = incomingEdges.containsKey(dest) && incomingEdges.get(dest).containsKey(source) &&
        incomingEdges.get(dest).get(source).remove(data);
    if (foundOut && !foundIn) {
      throw new AssertionError("Edge found in outgoing but not incoming");
    }
    if (foundIn && !foundOut) {
      throw new AssertionError("Edge found in incoming but not outgoing");
    }
    // TODO: cut down the number of .get calls
    if (outgoingEdges.containsKey(source) && (!outgoingEdges.get(source).containsKey(dest) || outgoingEdges.get(source).get(dest).size() == 0)) {
      outgoingEdges.get(source).remove(dest);
    }
    if (incomingEdges.containsKey(dest) && (!incomingEdges.get(dest).containsKey(source) || incomingEdges.get(dest).get(source).size() == 0)) {
      incomingEdges.get(dest).remove(source);
    }
    return foundOut;
  }

  /**
   * remove a vertex (and its edges) from the graph.
   *
   * @param vertex
   * @return true if successfully removes the node
   */
  @Override
  public boolean removeVertex(V vertex) {
    if (!outgoingEdges.containsKey(vertex)) {
      return false;
    }
    for (V other : outgoingEdges.get(vertex).keySet()) {
      incomingEdges.get(other).remove(vertex);
    }
    for (V other : incomingEdges.get(vertex).keySet()) {
      outgoingEdges.get(other).remove(vertex);
    }
    outgoingEdges.remove(vertex);
    incomingEdges.remove(vertex);
    return true;
  }

  @Override
  public boolean removeVertices(Collection vertices) {
    boolean changed = false;
    for (V v : vertices) {
      if (removeVertex(v)) {
        changed = true;
      }
    }
    return changed;
  }

  @Override
  public int getNumVertices() {
    return outgoingEdges.size();
  }

  @Override
  public List getOutgoingEdges(V v) {
    if (!outgoingEdges.containsKey(v)) { //noinspection unchecked
      return Collections.emptyList();
    }
    return CollectionUtils.flatten(outgoingEdges.get(v).values());
  }

  @Override
  public List getIncomingEdges(V v) {
    if (!incomingEdges.containsKey(v)) { //noinspection unchecked
      return Collections.emptyList();
    }
    return CollectionUtils.flatten(incomingEdges.get(v).values());
  }

  @Override
  public int getNumEdges() {
    int count = 0;
    for (Map.Entry>> sourceEntry : outgoingEdges.entrySet()) {
      for (Map.Entry> destEntry : sourceEntry.getValue().entrySet()) {
        count += destEntry.getValue().size();
      }
    }
    return count;
  }

  @Override
  public Set getParents(V vertex) {
    Map> parentMap = incomingEdges.get(vertex);
    if (parentMap == null)
      return null;
    return Collections.unmodifiableSet(parentMap.keySet());
  }

  @Override
  public Set getChildren(V vertex) {
    Map> childMap = outgoingEdges.get(vertex);
    if (childMap == null)
      return null;
    return Collections.unmodifiableSet(childMap.keySet());
  }

  /**
   * Gets both parents and children nodes
   *
   * @param v
   */
  @Override
  public Set getNeighbors(V v) {
    // TODO: pity we have to copy the sets... is there a combination set?
    Set children = getChildren(v);
    Set parents = getParents(v);

    if (children == null && parents == null)
      return null;
    Set neighbors = innerMapFactory.newSet();
    neighbors.addAll(children);
    neighbors.addAll(parents);
    return neighbors;
  }

  /**
   * clears the graph, removes all edges and nodes
   */
  @Override
  public void clear() {
    incomingEdges.clear();
    outgoingEdges.clear();
  }

  @Override
  public boolean containsVertex(V v) {
    return outgoingEdges.containsKey(v);
  }

  /**
   * only checks if there is an edge from source to dest. To check if it is
   * connected in either direction, use isNeighbor
   *
   * @param source
   * @param dest
   */
  @Override
  public boolean isEdge(V source, V dest) {
    Map> childrenMap = outgoingEdges.get(source);
    if (childrenMap == null || childrenMap.isEmpty())
      return false;
    List edges = childrenMap.get(dest);
    if (edges == null || edges.isEmpty())
      return false;
    return edges.size() > 0;
  }

  @Override
  public boolean isNeighbor(V source, V dest) {
    return isEdge(source, dest) || isEdge(dest, source);
  }

  @Override
  public Set getAllVertices() {
    return Collections.unmodifiableSet(outgoingEdges.keySet());
  }

  @Override
  public List getAllEdges() {
    List edges = new ArrayList<>();
    for (Map> e : outgoingEdges.values()) {
      for (List ee : e.values()) {
        edges.addAll(ee);
      }
    }
    return edges;
  }

  /**
   * False if there are any vertices in the graph, true otherwise. Does not care
   * about the number of edges.
   */
  @Override
  public boolean isEmpty() {
    return outgoingEdges.isEmpty();
  }

  /**
   * Deletes nodes with zero incoming and zero outgoing edges
   */
  @Override
  public void removeZeroDegreeNodes() {
    List toDelete = new ArrayList<>();
    for (V vertex : outgoingEdges.keySet()) {
      if (outgoingEdges.get(vertex).isEmpty() && incomingEdges.get(vertex).isEmpty()) {
        toDelete.add(vertex);
      }
    }
    for (V vertex : toDelete) {
      outgoingEdges.remove(vertex);
      incomingEdges.remove(vertex);
    }
  }

  @Override
  public List getEdges(V source, V dest) {
    Map> childrenMap = outgoingEdges.get(source);
    if (childrenMap == null) {
      return Collections.emptyList();
    }
    List edges = childrenMap.get(dest);
    if (edges == null) {
      return Collections.emptyList();
    }
    return Collections.unmodifiableList(edges);
  }

  /**
   * direction insensitive (the paths can go "up" or through the parents)
   */
  public List getShortestPath(V node1, V node2) {
    if (!outgoingEdges.containsKey(node1) || !outgoingEdges.containsKey(node2)) {
      return null;
    }
    return getShortestPath(node1, node2, false);
  }

  public List getShortestPathEdges(V node1, V node2) {
    return convertPath(getShortestPath(node1, node2), false);
  }

  /**
   * can specify the direction sensitivity
   *
   * @param node1
   * @param node2
   * @param directionSensitive
   *          - whether the path can go through the parents
   * @return the list of nodes you get through to get there
   */
  public List getShortestPath(V node1, V node2, boolean directionSensitive) {
    if (!outgoingEdges.containsKey(node1) || !outgoingEdges.containsKey(node2)) {
      return null;
    }
    return DijkstraShortestPath.getShortestPath(this, node1, node2, directionSensitive);
  }

  public List getShortestPathEdges(V node1, V node2, boolean directionSensitive) {
    return convertPath(getShortestPath(node1, node2, directionSensitive), directionSensitive);
  }

  public List convertPath(List nodes, boolean directionSensitive) {
    if (nodes == null)
      return null;

    if (nodes.size() <= 1)
      return Collections.emptyList();

    List path = new ArrayList<>();
    Iterator nodeIterator = nodes.iterator();
    V previous = nodeIterator.next();
    while (nodeIterator.hasNext()) {
      V next = nodeIterator.next();
      E connection = null;
      List edges = getEdges(previous, next);
      if (edges.size() == 0 && !directionSensitive) {
        edges = getEdges(next, previous);
      }
      if (edges.size() > 0) {
        connection = edges.get(0);
      } else {
        throw new IllegalArgumentException("Path given with missing " + "edge connection");
      }
      path.add(connection);
      previous = next;
    }
    return path;
  }

  @Override
  public int getInDegree(V vertex) {
    if (!containsVertex(vertex)) {
      return 0;
    }
    int result = 0;
    Map> incoming = incomingEdges.get(vertex);
    for (List edges : incoming.values()) {
      result += edges.size();
    }
    return result;
  }

  @Override
  public int getOutDegree(V vertex) {
    int result = 0;
    Map> outgoing = outgoingEdges.get(vertex);
    if (outgoing == null) {
      return 0;
    }
    for (List edges : outgoing.values()) {
      result += edges.size();
    }
    return result;
  }

  @Override
  public List> getConnectedComponents() {
    return ConnectedComponents.getConnectedComponents(this);
  }

  /**
   * Deletes all duplicate edges.
   */
  public void deleteDuplicateEdges() {
    for (V vertex : getAllVertices()) {
      for (V vertex2 : outgoingEdges.get(vertex).keySet()) {
        List data = outgoingEdges.get(vertex).get(vertex2);
        Set deduplicatedData = new TreeSet<>(data);
        data.clear();
        data.addAll(deduplicatedData);
      }
      for (V vertex2 : incomingEdges.get(vertex).keySet()) {
        List data = incomingEdges.get(vertex).get(vertex2);
        Set deduplicatedData = new TreeSet<>(data);
        data.clear();
        data.addAll(deduplicatedData);
      }
    }
  }

  
  public Iterator incomingEdgeIterator(final V vertex) {
    return new EdgeIterator<>(vertex, incomingEdges, outgoingEdges);
  }

  public Iterable incomingEdgeIterable(final V vertex) {
    return () -> new EdgeIterator<>(vertex, incomingEdges, outgoingEdges);
  }

  public Iterator outgoingEdgeIterator(final V vertex) {
    return new EdgeIterator<>(vertex, outgoingEdges, incomingEdges);
  }

  public Iterable outgoingEdgeIterable(final V vertex) {
    return () -> new EdgeIterator<>(vertex, outgoingEdges, incomingEdges);
  }

  public Iterator edgeIterator() {
    return new EdgeIterator<>(this);
  }

  public Iterable edgeIterable() {
    return () -> new EdgeIterator<>(DirectedMultiGraph.this);
  }
  

  /**
   * This class handles either iterating over a single vertex's
   * connections or over all connections in a graph.
   */
  static class EdgeIterator implements Iterator {
    private final Map>> reverseEdges;
    /** when iterating over the whole graph, this iterates over nodes */
    private Iterator>>> vertexIterator;
    /** for a given node, this iterates over its neighbors */
    private Iterator>> connectionIterator;
    /** given the neighbor of a node, this iterates over all its connections */
    private Iterator edgeIterator;

    private V currentSource = null;
    private V currentTarget = null;
    private E currentEdge = null;
    private boolean hasNext = true;


    public EdgeIterator(DirectedMultiGraph graph) {
      vertexIterator = graph.outgoingEdges.entrySet().iterator();
      reverseEdges = graph.incomingEdges;
    }

    public EdgeIterator(V startVertex, Map>> source,
                        Map>> reverseEdges) {
      currentSource = startVertex;
      Map> neighbors = source.get(startVertex);
      if (neighbors != null) {
        vertexIterator = null;
        connectionIterator = neighbors.entrySet().iterator();
      }
      this.reverseEdges = reverseEdges;
    }

    @Override
    public boolean hasNext() {
      primeIterator();
      return hasNext;
    }

    @Override
    public E next() {
      if (!hasNext()) {
        throw new NoSuchElementException("Graph edge iterator exhausted.");
      }
      currentEdge = edgeIterator.next();
      return currentEdge;
    }

    private void primeIterator() {
      if (edgeIterator != null && edgeIterator.hasNext()) {
        hasNext = true;  // technically, we shouldn't need to put this here, but let's be safe
      } else if (connectionIterator != null && connectionIterator.hasNext()) {
        Map.Entry> nextConnection = connectionIterator.next();
        edgeIterator = nextConnection.getValue().iterator();
        currentTarget = nextConnection.getKey();
        primeIterator();
      } else if (vertexIterator != null && vertexIterator.hasNext()) {
        Map.Entry>> nextVertex = vertexIterator.next();
        connectionIterator = nextVertex.getValue().entrySet().iterator();
        currentSource = nextVertex.getKey();
        primeIterator();
      } else {
        hasNext = false;
      }
    }

    @Override
    public void remove() {
      if (currentEdge != null) {
        reverseEdges.get(currentTarget).get(currentSource).remove(currentEdge);
        edgeIterator.remove();

        if (reverseEdges.get(currentTarget).get(currentSource) != null &&
            reverseEdges.get(currentTarget).get(currentSource).size() == 0) {
          connectionIterator.remove();
          reverseEdges.get(currentTarget).remove(currentSource);
          // TODO: may not be necessary to set this to null
          edgeIterator = null;
        }
      }
    }
  }

  /**
   * Topological sort of the graph.
   * 
* This method uses the depth-first search implementation of * topological sort. * Topological sorting only works if the graph is acyclic. * * @return A sorted list of the vertices * @throws IllegalStateException if this graph is not a DAG */ public List topologicalSort() { List result = Generics.newArrayList(); Set temporary = outerMapFactory.newSet(); Set permanent = outerMapFactory.newSet(); for (V vertex : getAllVertices()) { if (!temporary.contains(vertex)) { topologicalSortHelper(vertex, temporary, permanent, result); } } Collections.reverse(result); return result; } private void topologicalSortHelper(V vertex, Set temporary, Set permanent, List result) { temporary.add(vertex); Map> neighborMap = outgoingEdges.get(vertex); if (neighborMap != null) { for (V neighbor : neighborMap.keySet()) { if (permanent.contains(neighbor)) { continue; } if (temporary.contains(neighbor)) { throw new IllegalStateException("This graph has cycles. Topological sort not possible: " + this.toString()); } topologicalSortHelper(neighbor, temporary, permanent, result); } } result.add(vertex); permanent.add(vertex); } /** * Cast this multi-graph as a map from vertices, to the outgoing data along edges out of those vertices. * * @return A map representation of the graph. */ public Map> toMap() { Map> map = innerMapFactory.newMap(); for (V vertex : getAllVertices()) { map.put(vertex, getOutgoingEdges(vertex)); } return map; } @Override public String toString() { StringBuilder s = new StringBuilder(); s.append("{\n"); s.append("Vertices:\n"); for (V vertex : outgoingEdges.keySet()) { s.append(" ").append(vertex).append('\n'); } s.append("Edges:\n"); for (V source : outgoingEdges.keySet()) { for (V dest : outgoingEdges.get(source).keySet()) { for (E edge : outgoingEdges.get(source).get(dest)) { s.append(" ").append(source).append(" -> ").append(dest).append(" : ").append(edge).append('\n'); } } } s.append('}'); return s.toString(); } private static final long serialVersionUID = 609823567298345145L; }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy