edu.stanford.nlp.graph.DirectedMultiGraph.java Source code

Java tutorial

Introduction

Here is the source code for edu.stanford.nlp.graph.DirectedMultiGraph.java

Source

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 <V>
 *          Type of vertices
 * @param <E>
 *          Type of edges.
 */

public class DirectedMultiGraph<V, E> implements Graph<V, E> /* Serializable */ {

    final Map<V, Map<V, List<E>>> outgoingEdges;

    final Map<V, Map<V, List<E>>> incomingEdges;

    final MapFactory<V, Map<V, List<E>>> outerMapFactory;
    final MapFactory<V, List<E>> innerMapFactory;

    public DirectedMultiGraph() {
        this(MapFactory.<V, Map<V, List<E>>>hashMapFactory(), MapFactory.<V, List<E>>hashMapFactory());
    }

    public DirectedMultiGraph(MapFactory<V, Map<V, List<E>>> outerMapFactory,
            MapFactory<V, List<E>> 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<V, E> graph) {
        this(graph.outerMapFactory, graph.innerMapFactory);
        for (Map.Entry<V, Map<V, List<E>>> map : graph.outgoingEdges.entrySet()) {
            Map<V, List<E>> edgesCopy = innerMapFactory.newMap();
            for (Map.Entry<V, List<E>> entry : map.getValue().entrySet()) {
                edgesCopy.put(entry.getKey(), Generics.newArrayList(entry.getValue()));
            }
            this.outgoingEdges.put(map.getKey(), edgesCopy);
        }
        for (Map.Entry<V, Map<V, List<E>>> map : graph.incomingEdges.entrySet()) {
            Map<V, List<E>> edgesCopy = innerMapFactory.newMap();
            for (Map.Entry<V, List<E>> 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<V, List<E>> getOutgoingEdgesMap(V v) {
        Map<V, List<E>> map = outgoingEdges.get(v);
        if (map == null) {
            map = innerMapFactory.newMap();
            outgoingEdges.put(v, map);
            incomingEdges.put(v, innerMapFactory.newMap());
        }
        return map;
    }

    private Map<V, List<E>> getIncomingEdgesMap(V v) {
        Map<V, List<E>> 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<V, List<E>> outgoingMap = getOutgoingEdgesMap(source);
        Map<V, List<E>> incomingMap = getIncomingEdgesMap(dest);

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

        List<E> 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<V> 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<E> getOutgoingEdges(V v) {
        if (!outgoingEdges.containsKey(v)) { //noinspection unchecked
            return Collections.emptyList();
        }
        return CollectionUtils.flatten(outgoingEdges.get(v).values());
    }

    @Override
    public List<E> 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<V, Map<V, List<E>>> sourceEntry : outgoingEdges.entrySet()) {
            for (Map.Entry<V, List<E>> destEntry : sourceEntry.getValue().entrySet()) {
                count += destEntry.getValue().size();
            }
        }
        return count;
    }

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

    @Override
    public Set<V> getChildren(V vertex) {
        Map<V, List<E>> 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<V> getNeighbors(V v) {
        // TODO: pity we have to copy the sets... is there a combination set?
        Set<V> children = getChildren(v);
        Set<V> parents = getParents(v);

        if (children == null && parents == null)
            return null;
        Set<V> 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<V, List<E>> childrenMap = outgoingEdges.get(source);
        if (childrenMap == null || childrenMap.isEmpty())
            return false;
        List<E> 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<V> getAllVertices() {
        return Collections.unmodifiableSet(outgoingEdges.keySet());
    }

    @Override
    public List<E> getAllEdges() {
        List<E> edges = new ArrayList<>();
        for (Map<V, List<E>> e : outgoingEdges.values()) {
            for (List<E> 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<V> 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<E> getEdges(V source, V dest) {
        Map<V, List<E>> childrenMap = outgoingEdges.get(source);
        if (childrenMap == null) {
            return Collections.emptyList();
        }
        List<E> 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<V> getShortestPath(V node1, V node2) {
        if (!outgoingEdges.containsKey(node1) || !outgoingEdges.containsKey(node2)) {
            return null;
        }
        return getShortestPath(node1, node2, false);
    }

    public List<E> 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<V> 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<E> getShortestPathEdges(V node1, V node2, boolean directionSensitive) {
        return convertPath(getShortestPath(node1, node2, directionSensitive), directionSensitive);
    }

    public List<E> convertPath(List<V> nodes, boolean directionSensitive) {
        if (nodes == null)
            return null;

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

        List<E> path = new ArrayList<>();
        Iterator<V> nodeIterator = nodes.iterator();
        V previous = nodeIterator.next();
        while (nodeIterator.hasNext()) {
            V next = nodeIterator.next();
            E connection = null;
            List<E> 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<V, List<E>> incoming = incomingEdges.get(vertex);
        for (List<E> edges : incoming.values()) {
            result += edges.size();
        }
        return result;
    }

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

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

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

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

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

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

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

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

    public Iterable<E> 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<V, E> implements Iterator<E> {
        private final Map<V, Map<V, List<E>>> reverseEdges;
        /** when iterating over the whole graph, this iterates over nodes */
        private Iterator<Map.Entry<V, Map<V, List<E>>>> vertexIterator;
        /** for a given node, this iterates over its neighbors */
        private Iterator<Map.Entry<V, List<E>>> connectionIterator;
        /** given the neighbor of a node, this iterates over all its connections */
        private Iterator<E> edgeIterator;

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

        public EdgeIterator(DirectedMultiGraph<V, E> graph) {
            vertexIterator = graph.outgoingEdges.entrySet().iterator();
            reverseEdges = graph.incomingEdges;
        }

        public EdgeIterator(V startVertex, Map<V, Map<V, List<E>>> source, Map<V, Map<V, List<E>>> reverseEdges) {
            currentSource = startVertex;
            Map<V, List<E>> 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<V, List<E>> nextConnection = connectionIterator.next();
                edgeIterator = nextConnection.getValue().iterator();
                currentTarget = nextConnection.getKey();
                primeIterator();
            } else if (vertexIterator != null && vertexIterator.hasNext()) {
                Map.Entry<V, Map<V, List<E>>> 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.
     * <br>
     * 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<V> topologicalSort() {
        List<V> result = Generics.newArrayList();
        Set<V> temporary = outerMapFactory.newSet();
        Set<V> 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<V> temporary, Set<V> permanent, List<V> result) {
        temporary.add(vertex);
        Map<V, List<E>> 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<V, List<E>> toMap() {
        Map<V, List<E>> 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;

}