edu.stanford.nlp.graph.DirectedMultiGraph Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of stanford-corenlp Show documentation
Show all versions of stanford-corenlp Show documentation
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.
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;
}