diff --git a/src/berack96/sim/util/graph/Graph.java b/src/berack96/sim/util/graph/Graph.java index 029d016..dc1f4b3 100644 --- a/src/berack96/sim/util/graph/Graph.java +++ b/src/berack96/sim/util/graph/Graph.java @@ -228,6 +228,17 @@ public interface Graph extends Iterable { */ Set> edges(); + /** + * Retrieve all the edges from a particular vertex.
+ * Note: the edges that is returned are the edges that goes IN this vertex AND the edges that goes OUT of it. + * + * @param vertex a vertex of the graph + * @return a set of edges + * @throws NullPointerException if the vertex is null + * @throws IllegalArgumentException if the vertex is not contained in the graph + */ + Set> edgesOf(V vertex) throws NullPointerException, IllegalArgumentException; + /** * Get all the vertices that are children of the vertex passed as parameter.
* The vertices V(0-N) that are 'children' of a vertex V1, are all the vertices that have an edge diff --git a/src/berack96/sim/util/graph/MapGraph.java b/src/berack96/sim/util/graph/MapGraph.java new file mode 100644 index 0000000..69fe2ec --- /dev/null +++ b/src/berack96/sim/util/graph/MapGraph.java @@ -0,0 +1,384 @@ +package berack96.sim.util.graph; + +import berack96.sim.util.graph.visit.Dijkstra; +import berack96.sim.util.graph.visit.Tarjan; +import berack96.sim.util.graph.visit.VisitStrategy; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +/** + * Graph that uses HashMap for vertices and edges
+ * More specifically it utilizes a Map containing all the vertices mapped to all their edges
+ * Technically this version of the graph combine the fast adding/removing of the edges of the Matrix implementation, + * with the low memory and fast adding/removing of vertices of the Linked List implementation.
+ * This happen if the HashMap is not reallocated. So in the end each operation of adding or removing has O(n) + * + * @param the vertices + * @param the weight of the edges + */ +public class MapGraph implements Graph { + + /** + * Map that contains the edges from a vertex to another
+ * The first vertex is the vertex where start the edge, the second one is where the edge goes
+ * If an edge exist, then it's weight is returned + */ + private final Map> edges = new HashMap<>(); + + /** + * Need this variable for not calculating each time the SCC or the cyclic part if the graph doesn't change + */ + private Tarjan tarjan = null; + + /** + * Need this variable for not calculating each time the distance from a vertex to all his destinations if the graph doesn't change + */ + private Map> dijkstra = null; + + @Override + public boolean isCyclic() { + return stronglyConnectedComponents().size() != numberOfVertices(); + } + + @Override + public boolean isDAG() { + return !isCyclic(); + } + + @Override + public void addVertex(V vertex) throws NullPointerException { + checkNull(vertex); + graphChanged(); + edges.put(vertex, new HashMap<>()); + } + + @Override + public boolean addVertexIfAbsent(V vertex) throws NullPointerException { + if (contains(vertex)) + return false; + addVertex(vertex); + return true; + } + + @Override + public void addAllVertices(Set vertices) throws NullPointerException { + checkNull(vertices); + vertices.forEach(this::addVertexIfAbsent); + } + + @Override + public void removeVertex(V vertex) throws NullPointerException, IllegalArgumentException { + checkNullAndExist(vertex); + + graphChanged(); + edges.remove(vertex); + edges.forEach((v, map) -> map.remove(vertex)); + } + + @Override + public void removeAllVertex() { + graphChanged(); + edges.clear(); + } + + @Override + public boolean contains(V vertex) throws NullPointerException { + checkNull(vertex); + return edges.containsKey(vertex); + } + + @Override + public W addEdge(V vertex1, V vertex2, W weight) throws NullPointerException, IllegalArgumentException { + checkNullAndExist(vertex1); + checkNullAndExist(vertex2); + checkNull(weight); + + graphChanged(); + return edges.get(vertex1).put(vertex2, weight); + } + + @Override + public W addEdgeAndVertices(V vertex1, V vertex2, W weight) throws NullPointerException { + addVertexIfAbsent(vertex1); + addVertexIfAbsent(vertex2); + return addEdge(vertex1, vertex2, weight); + } + + @Override + public void addAllEdges(Set> edges) throws NullPointerException { + edges.forEach((edge) -> addEdgeAndVertices(edge.getSource(), edge.getDestination(), edge.getWeight())); + } + + @Override + public W getWeight(V vertex1, V vertex2) throws NullPointerException, IllegalArgumentException { + checkNullAndExist(vertex1); + checkNullAndExist(vertex2); + + return edges.get(vertex1).get(vertex2); + } + + @Override + public void removeEdge(V vertex1, V vertex2) throws NullPointerException, IllegalArgumentException { + checkNullAndExist(vertex1); + checkNullAndExist(vertex2); + + graphChanged(); + edges.get(vertex1).remove(vertex2); + } + + @Override + public void removeAllInEdge(V vertex) throws NullPointerException, IllegalArgumentException { + checkNullAndExist(vertex); + + graphChanged(); + edges.forEach((v, map) -> map.remove(vertex)); + } + + @Override + public void removeAllOutEdge(V vertex) throws NullPointerException, IllegalArgumentException { + checkNullAndExist(vertex); + + graphChanged(); + edges.put(vertex, new HashMap<>()); + } + + @Override + public void removeAllEdge(V vertex) throws NullPointerException, IllegalArgumentException { + removeVertex(vertex); + addVertex(vertex); + } + + @Override + public void removeAllEdge() { + graphChanged(); + edges.forEach((v, map) -> map.clear()); + } + + @Override + public boolean containsEdge(V vertex1, V vertex2) throws NullPointerException, IllegalArgumentException { + checkNullAndExist(vertex1); + checkNullAndExist(vertex2); + + return edges.get(vertex1).get(vertex2) != null; + } + + @Override + public Set vertices() { + return new HashSet<>(edges.keySet()); + } + + @Override + public Set> edges() { + Set> allEdges = new HashSet<>(); + edges.forEach((source, map) -> map.forEach((destination, weight) -> allEdges.add(new Edge<>(source, destination, weight)))); + return allEdges; + } + + @Override + public Set> edgesOf(V vertex) throws NullPointerException, IllegalArgumentException { + checkNullAndExist(vertex); + + Set> set = new HashSet<>(); + edges.forEach((source, map) -> map.forEach((destination, weight) -> { + if (destination.equals(vertex) || source.equals(vertex)) + set.add(new Edge<>(source, destination, weight)); + })); + return set; + } + + @Override + public Set getChildren(V vertex) throws NullPointerException, IllegalArgumentException { + checkNullAndExist(vertex); + + return new HashSet<>(edges.get(vertex).keySet()); + } + + @Override + public Map getChildrenAndWeight(V vertex) throws NullPointerException, IllegalArgumentException { + checkNullAndExist(vertex); + + return new HashMap<>(edges.get(vertex)); + } + + @Override + public Set getAncestors(V vertex) throws NullPointerException, IllegalArgumentException { + checkNullAndExist(vertex); + + Set set = new HashSet<>(); + edges.forEach((v, map) -> { + if (map.containsKey(vertex)) set.add(v); + }); + return set; + } + + @Override + public int degreeIn(V vertex) throws NullPointerException, IllegalArgumentException { + checkNullAndExist(vertex); + + AtomicInteger sum = new AtomicInteger(); + edges.forEach((v, map) -> { + if (map.containsKey(vertex)) + sum.getAndIncrement(); + }); + + return sum.get(); + } + + @Override + public int degreeOut(V vertex) throws NullPointerException, IllegalArgumentException { + checkNullAndExist(vertex); + + return edges.get(vertex).size(); + } + + @Override + public int degree(V vertex) throws NullPointerException, IllegalArgumentException { + return degreeIn(vertex) + degreeOut(vertex); + } + + @Override + public int numberOfVertices() { + return edges.size(); + } + + @Override + public int numberOfEdges() { + AtomicInteger sum = new AtomicInteger(0); + edges.forEach((v, map) -> sum.getAndAdd(map.size())); + + return sum.get(); + } + + @Override + public void visit(V source, VisitStrategy strategy, Consumer visit) throws NullPointerException, IllegalArgumentException { + strategy.visit(this, source, visit); + } + + @Override + public Graph transpose() { + Graph graph = new MapGraph<>(); + for (V vertex : edges.keySet()) + graph.addVertex(vertex); + + edges.forEach((source, map) -> map.forEach((destination, weight) -> graph.addEdge(destination, source, weight))); + + return graph; + } + + @Override + public List topologicalSort() throws UnsupportedOperationException { + if (!isDAG()) + throw new UnsupportedOperationException(NOT_DAG); + return getTarjan().getTopologicalSort(); + } + + @Override + public Set> stronglyConnectedComponents() { + return getTarjan().getSCC(); + } + + @Override + public Graph subGraph(V source, int depth) throws NullPointerException, IllegalArgumentException { + Graph sub = new MapGraph<>(); + Set vertices = new HashSet<>(); + + int finalDepth = depth > 0 ? depth : 0; + VisitStrategy strategy = (graph, sourceVertex, visit) -> { + int currentDepth = 0; + final LinkedList> toVisitChildren = new LinkedList<>(); + toVisitChildren.add(new AbstractMap.SimpleEntry<>(sourceVertex, 0)); + vertices.add(source); + + while (!toVisitChildren.isEmpty() && currentDepth + 1 <= finalDepth) { + final Map.Entry current = toVisitChildren.removeFirst(); + currentDepth = current.getValue() + 1; + final int finalCurrentDepth = currentDepth; + + for (V child : graph.getChildren(current.getKey())) + if (!vertices.contains(child)) { + toVisitChildren.addLast(new AbstractMap.SimpleEntry<>(child, finalCurrentDepth)); + vertices.add(child); + } + } + }; + + strategy.visit(this, source, null); + + sub.addAllVertices(vertices); + for (V vertex : vertices) + getChildrenAndWeight(vertex).forEach((child, weight) -> { + try { + sub.addEdge(vertex, child, weight); + } catch (Exception ignored) { + } + }); + + return sub; + } + + @Override + public List> distance(V source, V destination) throws NullPointerException, IllegalArgumentException, UnsupportedOperationException { + checkNullAndExist(source); + checkNullAndExist(destination); + + Dijkstra dijkstra = getDijkstra(source); + List> path = dijkstra.getLastDistance().get(destination); + if (path == null) + throw new UnsupportedOperationException(NOT_CONNECTED); + return new ArrayList<>(path); + } + + @Override + public Map>> distance(V source) throws NullPointerException, IllegalArgumentException { + checkNullAndExist(source); + return new HashMap<>(getDijkstra(source).getLastDistance()); + } + + @Override + public Iterator iterator() { + return edges.keySet().iterator(); + } + + + /** + * Simple function that set all the memory vars at null if the graph changed + */ + private void graphChanged() { + tarjan = null; + dijkstra = null; + } + + private Dijkstra getDijkstra(V source) { + if (dijkstra == null) + dijkstra = new HashMap<>(); + if (dijkstra.get(source) == null) { + Dijkstra newDijkstra = new Dijkstra<>(); + newDijkstra.visit(this, source, null); + dijkstra.put(source, newDijkstra); + } + + return dijkstra.get(source); + } + + private Tarjan getTarjan() { + if (tarjan == null) { + tarjan = new Tarjan<>(); + tarjan.visit(this, null, null); + } + + return tarjan; + } + + private void checkNull(Object object) { + if (object == null) + throw new NullPointerException(PARAM_NULL); + } + + private void checkNullAndExist(V vertex) { + checkNull(vertex); + if (!edges.containsKey(vertex)) + throw new IllegalArgumentException(VERTEX_NOT_CONTAINED); + } +} diff --git a/src/berack96/sim/util/graph/visit/Dijkstra.java b/src/berack96/sim/util/graph/visit/Dijkstra.java new file mode 100644 index 0000000..312b3e9 --- /dev/null +++ b/src/berack96/sim/util/graph/visit/Dijkstra.java @@ -0,0 +1,89 @@ +package berack96.sim.util.graph.visit; + +import berack96.sim.util.graph.Graph; + +import java.util.*; +import java.util.function.Consumer; + +public class Dijkstra implements VisitStrategy { + + private Map>> distance; + + /** + * Get the last calculated distance to all the possible destinations
+ * The map contains all the possible vertices that are reachable from the source set in the visit
+ * If there is no path between the destination and the source, then null is returned as accordingly to the map interface
+ * If the visit is not already been done, then the map is null. + * + * @return the last distance + */ + public Map>> getLastDistance() { + return distance; + } + + @Override + public void visit(Graph graph, V source, Consumer visit) throws NullPointerException, IllegalArgumentException { + Queue> queue = new PriorityQueue<>(); + Map dist = new HashMap<>(); + Map prev = new HashMap<>(); + + dist.put(source, 0); // Initialization + queue.add(new QueueEntry<>(source, 0)); + + while (!queue.isEmpty()) { // The main loop + QueueEntry u = queue.poll(); // Remove and return best vertex + graph.getChildrenAndWeight(u.entry).forEach((vertex, weight) -> { + int alt = dist.get(u.entry) + weight.intValue(); + Integer distCurrent = dist.get(vertex); + if (distCurrent == null || alt < distCurrent) { + dist.put(vertex, alt); + prev.put(vertex, u.entry); + + QueueEntry current = new QueueEntry<>(vertex, alt); + queue.remove(current); + queue.add(current); + } + }); + } + + /* Cleaning up the results */ + distance = new HashMap<>(); + for (V vertex : prev.keySet()) { + List> path = new LinkedList<>(); + V child = vertex; + V father = prev.get(child); + do { + Graph.Edge edge = new Graph.Edge<>(father, child, graph.getWeight(father, child)); + path.add(0, edge); + child = father; + father = prev.get(child); + } while (father != null); + + distance.put(vertex, new ArrayList<>(path)); + } + } + + private class QueueEntry implements Comparable { + final V entry; + final W weight; + + QueueEntry(V entry, W weight) { + this.entry = entry; + this.weight = weight; + } + + @Override + public boolean equals(Object obj) { + try { + return ((QueueEntry) obj).entry.equals(entry); + } catch (Exception e) { + return false; + } + } + + @Override + public int compareTo(QueueEntry queueEntry) { + return this.weight.intValue() - queueEntry.weight.intValue(); + } + } +} diff --git a/src/berack96/sim/util/graph/visit/Tarjan.java b/src/berack96/sim/util/graph/visit/Tarjan.java new file mode 100644 index 0000000..10e1346 --- /dev/null +++ b/src/berack96/sim/util/graph/visit/Tarjan.java @@ -0,0 +1,96 @@ +package berack96.sim.util.graph.visit; + +import berack96.sim.util.graph.Graph; + +import java.util.*; +import java.util.function.Consumer; + +public class Tarjan implements VisitStrategy { + + private Set> SCC = null; + private List topologicalSort = null; + + private Map indices = null; + private Map lowLink = null; + private Stack stack = null; + + /** + * Return the latest calculated strongly connected components of the graph. + * + * @return the latest SCC + */ + public Set> getSCC() { + return SCC; + } + + /** + * Return the latest calculated Topological sort of the graph.
+ * If the latest visited graph is not a DAG, it will return null. + * + * @return the topological order of the DAG + */ + public List getTopologicalSort() { + return topologicalSort; + } + + /** + * This particular visit strategy use only the graph, so the other parameters are useless. + * + * @param graph the graph to visit + * @param source the source of the visit + * @param visit the function to apply at each vertex when they are visited + * @throws NullPointerException if the graph is null + * @throws IllegalArgumentException doesn't throw this + */ + @Override + public void visit(Graph graph, V source, Consumer visit) throws NullPointerException, IllegalArgumentException { + SCC = new HashSet<>(); + topologicalSort = new LinkedList<>(); + + indices = new HashMap<>(); + lowLink = new HashMap<>(); + stack = new Stack<>(); + Integer index = 0; + + for (V vertex : graph) + if (!indices.containsKey(vertex)) + strongConnect(graph, vertex, index); + + topologicalSort = (graph.numberOfVertices() == SCC.size()) ? new ArrayList<>(topologicalSort) : null; + } + + private void strongConnect(Graph graph, V vertex, Integer index) { + // Set the depth index for v to the smallest unused index + indices.put(vertex, index); + lowLink.put(vertex, index); + index++; + stack.push(vertex); + + // Consider successors of v + for (V child : graph.getChildren(vertex)) { + if (!indices.containsKey(child)) { + strongConnect(graph, child, index); + lowLink.put(vertex, Math.min(lowLink.get(vertex), lowLink.get(child))); + } else if (stack.contains(child)) { + // Successor w is in stack S and hence in the current SCC + // If w is not on stack, then (v, w) is a cross-edge in the DFS tree and must be ignored + // Note: The next line may look odd - but is correct. + // It says w.index not w.lowlink; that is deliberate and from the original paper + lowLink.put(vertex, Math.min(lowLink.get(vertex), indices.get(child))); + } + } + + // If v is a root node, pop the stack and generate an SCC + if (lowLink.get(vertex).equals(indices.get(vertex))) { + Set newComponent = new HashSet<>(); + V temp; + do { + temp = stack.pop(); + topologicalSort.add(0, temp); + newComponent.add(temp); + } while (!temp.equals(vertex)); + + SCC.add(newComponent); + } + } +} diff --git a/test/berack96/test/sim/TestGraph.java b/test/berack96/test/sim/TestGraph.java index 182dd8e..4c9c5ee 100644 --- a/test/berack96/test/sim/TestGraph.java +++ b/test/berack96/test/sim/TestGraph.java @@ -1,6 +1,7 @@ package berack96.test.sim; import berack96.sim.util.graph.Graph; +import berack96.sim.util.graph.MapGraph; import berack96.sim.util.graph.visit.BFS; import berack96.sim.util.graph.visit.DFS; import berack96.sim.util.graph.visit.VisitStrategy; @@ -21,7 +22,7 @@ public class TestGraph { @Before public void before() { // Change here the instance for changing all the test for that particular class - graph = null; + graph = new MapGraph<>(); } @Test @@ -312,6 +313,14 @@ public class TestGraph { new Graph.Edge<>("4", "6", 6), new Graph.Edge<>("5", "3", 9), new Graph.Edge<>("5", "4", 5)); + + shouldThrow(nullException, () -> graph.edgesOf(null)); + shouldThrow(notException, () -> graph.edgesOf("rew")); + shouldContain(graph.edgesOf("5"), + new Graph.Edge<>("2", "5", 4), + new Graph.Edge<>("3", "5", 2), + new Graph.Edge<>("5", "3", 9), + new Graph.Edge<>("5", "4", 5)); } @Test