From 745b1715942249305b0b6f0155bc4f5fdb7aa345 Mon Sep 17 00:00:00 2001 From: Giacomo Date: Mon, 8 Oct 2018 02:29:34 +0200 Subject: [PATCH] Init - init with the first interface and tests - added some basic class for the tests --- .gitignore | Bin 0 -> 4620 bytes README.md | Bin 0 -> 756 bytes src/berack96/sim/util/graph/Graph.java | 463 +++++++++ src/berack96/sim/util/graph/visit/BFS.java | 52 + src/berack96/sim/util/graph/visit/DFS.java | 58 ++ .../sim/util/graph/visit/VisitStrategy.java | 193 ++++ test/berack96/test/sim/TestGraph.java | 907 ++++++++++++++++++ 7 files changed, 1673 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 src/berack96/sim/util/graph/Graph.java create mode 100644 src/berack96/sim/util/graph/visit/BFS.java create mode 100644 src/berack96/sim/util/graph/visit/DFS.java create mode 100644 src/berack96/sim/util/graph/visit/VisitStrategy.java create mode 100644 test/berack96/test/sim/TestGraph.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..c2b7620a8d676dbd11ee7603d326654cab62645d GIT binary patch literal 4620 zcmb7|T~8ZF6o%)zQvbuMNEJ86@_AVHjf`VjWqV&!(w)u*&9oB}@i~jVs?{kh1 zJB!zl%Cgy+Io}`endRSqwo;KgX_ju&INhXDt3y34(yms6beX2McaZkdzMg*7`GNKp zqM7J(De70+J5I}Vn(ot)ag1%Rn=aBk^+Y?9tZUnQqn)|w8%YA3U2%0pIe4Jyq<4}s zlNQe3=JhAJj)Nt?;59Z4JN7x^u5m3qdPg`^G@p|S0JpT12$ zr61D|`uv<;r(Y5lhL!I{gN^8&>uD(7GvhC`gTH6`K;70ZksCkHq6rlFAJ>m5L$t>Q{HXU0P=sD@@so2fm&o8q*IXCjS5X|MaaUMpq#B$~ZOJD;wGk-C&TUMn)g zS~S* z^~hNL5AuML)bzE^Mq1G~c?Z3+%G}bPTSdJV=FwP^-9IqCP?R_ULx=Vo{fG^Lz}I?$ zi#o(lII8YxYgMH8^4(GMTIY0Vp?I9>w~9cAUyOG5})ErE6yM3p^(OSb+Tt)9Mkq6Mb&^fn}$f@tlEb zixr-QPO2R$I+jkK>b#Wxg{+84^^~XmT6G3mhV(<-HI8)ySeJnH6Z4YVr3OP^s}|QB z0YYj=esI$X%QBn7^VIA8@huan)h+Y22+Uo!V0ALXmqSf&9wPy^|#hj$vL%ke#6+5FSj*Eskr;VjfHdl zGBMq9dMTdmF!@mUdjXVQRtcZ#5806`>m7ApKI6etNsR4iWeP3H|NQHc5S;**4X~imIn`2Tw=&O3YNtaJcIw7yG^%;bp zH>xkXJpkAdi@Q46H2rtr5Qu&-3_|0*Gku761C!X<5^qw%oXLnMZ5k6T9?~X z)<7ldG5WHpmvXwVMO#&|cj$T*`7N!vyXml)H!XFe|Ch!sFCQ%GTl_#1{zJkq|5b?T N-Om1Rz|5hl{sY^=@%aD% literal 0 HcmV?d00001 diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..df8b6922a3be3ba60145f9873671faf62cbba436 GIT binary patch literal 756 zcmZuv?P|j?47}fky@R2@*Oypfzcv_S56~t~YiOJ-ABFAV*WF3+NMV@7vUED>WZ!8Ayfp(9MR!?i--tn(UM@)~j;AUj=e$($(;vP_uEpboA zp$xGktC*Q9<|{Mhi-s4ktWvMBs646>a5An6XYP4n72k{tkrTaF^B#DXC*YmF7Ek<7 z)Vs4))V>lc*0UkAv3OQZd@zlXDiY%t_O<()(-vBRyrpAxI&rPL_i3rxBe5Tg0!^^7 zE{n<&vo!O>Nhp6{^k`N^OZO8NH9BW5;YZ?iQnX=?)UKx~MtN&P2&HVFuj~7y;z;IT zwRO{FGXL#G^WG|zn^~(<2YMBiRq{zTJbS<1jqcDVAe=Jp--=X;MV*co_pX(#*W?pQ Y)^%ahJ93tmc{M-XUF76?;#4Q%7j7bcY5)KL literal 0 HcmV?d00001 diff --git a/src/berack96/sim/util/graph/Graph.java b/src/berack96/sim/util/graph/Graph.java new file mode 100644 index 0000000..029d016 --- /dev/null +++ b/src/berack96/sim/util/graph/Graph.java @@ -0,0 +1,463 @@ +package berack96.sim.util.graph; + +import berack96.sim.util.graph.visit.VisitStrategy; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + +/** + * An interface for the graphs.
+ * This interface is used for the graphs with Directed edges.
+ * A directed edge between V1 and V2 is an edge that has V1 as source and V2 as destination.
+ * + * @param The Object that represent a vertex + * @param The Object that represent the edge (more specifically the weight of the edge) + * @author Berack96 + */ +public interface Graph extends Iterable { + + String NOT_DAG = "The graph is not a DAG"; + String NOT_CONNECTED = "The source vertex doesn't have a path that reach the destination"; + String PARAM_NULL = "The parameter must not be null"; + String VERTEX_NOT_CONTAINED = "The vertex must be contained in the graph"; + + /** + * Tells if the graph has some cycle.
+ * A cycle is detected if visiting the graph G starting from V1 (that is any of the vertex of G), + * the visit can return to V1 in any point. + * + * @return true if has cycle, false otherwise + */ + boolean isCyclic(); + + /** + * Tells if the graph has the property of DAG (Directed Acyclic Graph).
+ * A graph is a DAG only if absent of any cycle. ( see {@link #isCyclic()} ) + * + * @return true if is a DAG, false otherwise + */ + boolean isDAG(); + + /** + * Add the vertex to the graph. If it's already in the graph it will be replaced.
+ * Of course the vertex added will have no edge to any other vertex nor form any other vertex. + * + * @param vertex the vertex to add + * @throws NullPointerException if the vertex is null + */ + void addVertex(V vertex) throws NullPointerException; + + /** + * Add the specified vertex to the graph only if the graph doesn't contains it.
+ * The graph contains a vertex only if the method {@link #contains(V)} returns true. + * + * @param vertex the vertex to add + * @return true if the vertex is added, false if the graph contains the vertex and therefore the new one is not added + * @throws NullPointerException if the vertex is null + */ + boolean addVertexIfAbsent(V vertex) throws NullPointerException; + + /** + * Add all the vertices contained in the set to the graph.
+ * If a vertex is contained in the set and in the graph is ignored and it will not be replaced.
+ * Null vertices will be ignored and they will not be added to the graph. + * + * @param vertices a set containing the vertices + * @throws NullPointerException if the set is null + */ + void addAllVertices(Set vertices) throws NullPointerException; + + /** + * Remove the selected vertex from the graph.
+ * After this method's call the vertex will be no longer present in the graph, and nether all his edges. + * + * @param vertex the vertex to remove + * @throws NullPointerException if the vertex is null + * @throws IllegalArgumentException if the vertex is not contained in the graph + */ + void removeVertex(V vertex) throws NullPointerException, IllegalArgumentException; + + /** + * Remove all the vertex contained in the graph.
+ * After this method's call the graph will be empty; no vertices nor edges. + */ + void removeAllVertex(); + + /** + * Check if the vertex passed is contained in the graph or not.
+ * The vertex V1 is contained in the graph G, if and only if:
+ * exist V2 in G such that V2.equals(V1) + * + * @param vertex the vertex to check + * @return true if the vertex is contained, false otherwise + * @throws NullPointerException if the vertex is null + */ + boolean contains(V vertex) throws NullPointerException; + + /** + * Add an edge between the two vertex.
+ * The edge will be created from the vertex V1 and the vertex V2
+ * This method will overwrite any existing edge between the two vertex.
+ * If there was a previous edge then it is returned + * + * @param vertex1 a vertex of the graph + * @param vertex2 a vertex of the graph + * @param weight the weight of the edge + * @return null or the previous value of the edge if there was already one + * @throws NullPointerException if one of the parameter is null + * @throws IllegalArgumentException if one of the vertex is not contained in the graph + */ + W addEdge(V vertex1, V vertex2, W weight) throws NullPointerException, IllegalArgumentException; + + /** + * This particular function add an edge to the graph.
+ * If one of the two, or both vertices aren't contained in the graph, then the vertices will be added.
+ * The edge will be created from the vertex V1 and the vertex V2
+ * This method will overwrite any existing edge between the two vertex.
+ * If there was a previous edge then it is returned + * + * @param vertex1 a vertex of the graph + * @param vertex2 a vertex of the graph + * @param weight the weight of the edge + * @return null or the previous value of the edge if there was already one + * @throws NullPointerException if one of the parameter is null + */ + W addEdgeAndVertices(V vertex1, V vertex2, W weight) throws NullPointerException; + + /** + * Add all the edges of the set in the graph.
+ * If one of the two, or both vertices aren't contained in the graph, then the vertices will be added.
+ * Any null edges will be ignored.
+ * This method will overwrite any existing edge between the two vertex. + * + * @param edges the edges to add + * @throws NullPointerException if the set is null + */ + void addAllEdges(Set> edges) throws NullPointerException; + + /** + * Get the weight of the selected edge.
+ * If the edge doesn't exist, then null is returned + * + * @param vertex1 a vertex of the graph + * @param vertex2 a vertex of the graph + * @return the weight previously set, or null if the edge doesn't exist + * @throws NullPointerException if one of the parameters is null + * @throws IllegalArgumentException if one of the vertex is not contained in the graph + */ + W getWeight(V vertex1, V vertex2) throws NullPointerException, IllegalArgumentException; + + /** + * Remove the edge between the two vertex.
+ * If the edge doesn't exist, then this call does nothing.
+ * After this method's call it will be no longer possible to travel from V1 to V2, nether from V2 to V1. + * + * @param vertex1 a vertex of the graph + * @param vertex2 a vertex of the graph + * @throws NullPointerException if one of the parameters is null + * @throws IllegalArgumentException if one of the vertex is not contained in the graph + */ + void removeEdge(V vertex1, V vertex2) throws NullPointerException, IllegalArgumentException; + + /** + * Remove all the edges that goes in the vertex.
+ * After this method's call it will be no longer possible travel to this vertex. + * + * @param vertex a vertex of the graph + * @throws NullPointerException if one of the parameters is null + * @throws IllegalArgumentException if one of the vertex is not contained in the graph + */ + void removeAllInEdge(V vertex) throws NullPointerException, IllegalArgumentException; + + /** + * Remove all the edges that start from this vertex.
+ * After this method's call it will be no longer possible travel to any vertex from this one. + * + * @param vertex a vertex of the graph + * @throws NullPointerException if one of the parameters is null + * @throws IllegalArgumentException if one of the vertex is not contained in the graph + */ + void removeAllOutEdge(V vertex) throws NullPointerException, IllegalArgumentException; + + /** + * Remove all edges form a particular vertex of the graph.
+ * After this method's call the selected vertex will have 0 edges.
+ * It will be no longer possible to reach this vertex from any other vertex, and vice versa. + * + * @param vertex a vertex of the graph + * @throws NullPointerException if the vertex is null + * @throws IllegalArgumentException if the vertex is not contained in the graph + */ + void removeAllEdge(V vertex) throws NullPointerException, IllegalArgumentException; + + /** + * Remove all the edges of the graph.
+ * After this method's call the graph will have only vertices, and no edge. + */ + void removeAllEdge(); + + /** + * Check if the edge between the two vertex passed is contained in the graph or not.
+ * An edge between V1 and V2 is contained in the graph if and only if i can travel from V1 to V2. + * + * @param vertex1 a vertex of the graph + * @param vertex2 a vertex of the graph + * @return true if the edge is contained, false otherwise + * @throws NullPointerException if one of the parameters is null + * @throws IllegalArgumentException if one of the vertex is not contained in the graph + */ + boolean containsEdge(V vertex1, V vertex2) throws NullPointerException, IllegalArgumentException; + + /** + * Get all the vertices in the graph.
+ * If the graph doesn't contains vertices, it'll return an empty set.
+ * Note that this set is completely different than the set used for the vertices, so any modification of this set will not change the graph. + * + * @return a set that include all the vertices + */ + Set vertices(); + + /** + * Get all the edges in the graph.
+ * If the graph doesn't contains edges, it'll return an empty set.
+ * Note that this set is completely different than the set used for the edges, so any modification of this set will not change the graph. + * + * @return a set that include all the edges + */ + Set> edges(); + + /** + * 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 + * where V1 is the source of that edge. + * + * @param vertex the source vertex + * @return a set of vertices + * @throws NullPointerException if the vertex is null + * @throws IllegalArgumentException if the vertex is not contained in the graph + */ + Set getChildren(V vertex) throws NullPointerException, IllegalArgumentException; + + /** + * This method will get all the child of the vertex selected.
+ * The map created will be a {@link java.util.LinkedHashMap LinkedHashMap}
+ * The order in which the vertex are iterated in the map will be from the vertex with the lowest weight to the one with the highest. + * + * @param vertex a vertex of the graph + * @return a map of all the child and their respective weight + * @throws NullPointerException if the vertex is null + * @throws IllegalArgumentException if the vertex is not contained in the graph + */ + Map getChildrenAndWeight(V vertex) throws NullPointerException, IllegalArgumentException; + + /** + * Get all the vertices that have the vertex passed as their child.
+ * Basically is the opposite of {@link #getChildren(Object)} + * + * @param vertex a vertex of the graph + * @return a set of ancestors of the vertex + * @throws NullPointerException if one of the parameters is null + * @throws IllegalArgumentException if one of the vertex is not contained in the graph + */ + Set getAncestors(V vertex) throws NullPointerException, IllegalArgumentException; + + /** + * Tells the degree of all the edges that goes to this vertex.
+ * Basically, it'll count how many edge towards himself it have. + * + * @param vertex a vertex of the graph + * @return the in degree of the vertex + * @throws NullPointerException if the vertex is null + * @throws IllegalArgumentException if the vertex is not contained in the graph + */ + int degreeIn(V vertex) throws NullPointerException, IllegalArgumentException; + + /** + * Tells the degree of all the edges that goes form this vertex to others.
+ * Basically, it'll count how many edge towards any other vertex it have. + * + * @param vertex a vertex of the graph + * @return the out degree of the vertex + * @throws NullPointerException if the vertex is null + * @throws IllegalArgumentException if the vertex is not contained in the graph + */ + int degreeOut(V vertex) throws NullPointerException, IllegalArgumentException; + + /** + * Tells the degree of a vertex.
+ * The degree of a vertex is the quantity of edges that have.
+ * Basically, it'll count how many edge it have. + * + * @param vertex a vertex of the graph + * @return the degree of the vertex + * @throws NullPointerException if the vertex is null + * @throws IllegalArgumentException if the vertex is not contained in the graph + */ + int degree(V vertex) throws NullPointerException, IllegalArgumentException; + + /** + * Tells how many vertices are in the graph. + * + * @return the number of vertices + */ + int numberOfVertices(); + + /** + * Tells how many edges are in the graph. + * + * @return the number of edges + */ + int numberOfEdges(); + + /** + * Visit the graph accordingly to the strategy that is passed.
+ * This method visit the graph from the source to all the vertex that are reachable form the source.
+ * Some strategy can accept a source vertex null, because they visit all the graph anyway. + * + * @param source the source vertex of the visit + * @param strategy the algorithm for visiting the graph + * @param visit the function to apply at each vertex + * @throws NullPointerException if one of the parameter is null (except the consumer) + * @throws IllegalArgumentException if the vertex is not in the graph + */ + void visit(V source, VisitStrategy strategy, Consumer visit) throws NullPointerException, IllegalArgumentException; + + /** + * This method will create a new Graph that is the transposed version of the original.
+ * At the end of this method the new graph will have all the edges inverted in orientation.
+ * Example: if the graph G contains (V1, V2, V3) as vertex, and (V1->V2, V3->V2) as edges, the transpose graph G' will contain (V1, V2, V3) as vertex, and (V2->V1, V2->V3) as edges. + * + * @return a transposed graph of this instance + */ + Graph transpose(); + + /** + * If the current graph is a DAG, it returns a topological sort of this graph.
+ * A topological ordering of a graph is a linear ordering of its vertices such that for every directed edge (V1, V2) from vertex V1 to vertex V2, V2 comes before V1 in the ordering. + * + * @return an array containing the topological order of the vertices + * @throws UnsupportedOperationException if the graph is not a DAG (see {@link #isDAG()}) + */ + List topologicalSort() throws UnsupportedOperationException; + + /** + * The strongly connected components or diconnected components of an arbitrary directed graph form a partition into subgraphs that are themselves strongly connected. + * + * @return a set containing the strongly connected components + */ + Set> stronglyConnectedComponents(); + + /** + * Get a sub-graph of the current one based on the maximum depth that is given.
+ * If the depth is 1 then only the source and it's children will be in the sub-graph.
+ * If the depth is 2 then only the source, it's children and it's children of it's children will be in the sub-graph.
+ * And so on.
+ * Of course the sub-graph will contain the edges that link the vertices, but only the one selected. + * + * @param source the source vertex + * @param depth the maximum depth (must be a positive number, if >=0 a graph containing only the source is returned) + * @return a sub-graph of the original + * @throws NullPointerException if the vertex is null + * @throws IllegalArgumentException if the vertex is null + */ + Graph subGraph(V source, int depth) throws NullPointerException, IllegalArgumentException; + + /** + * Get the minimum path from the source vertex to the destination vertex.
+ * If the source vertex can't reach the destination, then an exception is thrown. + * + * @param source the vertex where to start + * @param destination the destination chosen + * @return an ordered list of edges from source to destination that represent the minimum path between the two vertices + * @throws NullPointerException if one of the parameter is null (except the consumer) + * @throws IllegalArgumentException if the vertex is not in the graph + * @throws UnsupportedOperationException if from the source it's not possible to reach the destination + */ + List> distance(V source, V destination) throws NullPointerException, IllegalArgumentException, UnsupportedOperationException; + + /** + * Get the minimum path from the source vertex to all the possible reachable vertices. + * + * @param source the vertex where to start + * @return a map containing all the possible reachable vertices from the source and the minimum path to reach them + * @throws NullPointerException if one of the parameter is null (except the consumer) + * @throws IllegalArgumentException if the vertex is not in the graph + */ + Map>> distance(V source) throws NullPointerException, IllegalArgumentException; + + // TODO maybe -> STATIC saveOnFile(orString) INSTANCE loadFromFile(orString), but need JSON parser + // TODO maybe, but i don't think so... STATIC DISTANCE V* -> V* + + /** + * Class used for retrieving the edges of the graph. + * + * @param the vertices + * @param the weight of the edge + */ + class Edge { + private final V source; + private final V destination; + private final W weight; + + /** + * Create an final version of this object + * + * @param source the source of the edge + * @param destination the destination of the edge + * @param weight the weight od the edge + */ + public Edge(V source, V destination, W weight) { + this.source = source; + this.destination = destination; + this.weight = weight; + } + + /** + * The vertex where the edge goes + * + * @return the vertex + */ + public V getDestination() { + return destination; + } + + /** + * The vertex where the edge starts from + * + * @return the vertex + */ + public V getSource() { + return source; + } + + /** + * The weight of the edge + * + * @return the weight + */ + public W getWeight() { + return weight; + } + + @Override + public String toString() { + return "[" + source + " -> " + destination + ", " + weight + "]"; + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public boolean equals(Object obj) { + try { + return obj.getClass().equals(getClass()) && obj.toString().equals(toString()); + } catch (Exception e) { + return false; + } + } + } +} diff --git a/src/berack96/sim/util/graph/visit/BFS.java b/src/berack96/sim/util/graph/visit/BFS.java new file mode 100644 index 0000000..9eef55a --- /dev/null +++ b/src/berack96/sim/util/graph/visit/BFS.java @@ -0,0 +1,52 @@ +package berack96.sim.util.graph.visit; + +import berack96.sim.util.graph.Graph; + +import java.util.LinkedList; +import java.util.function.Consumer; + +/** + * Breadth-first search
+ * The algorithm starts at the root node and explores all of the neighbor nodes at the present depth prior to moving on to the nodes at the next depth level. + * + * @param the vertex of the graph + * @param the weight of the graph + */ +public class BFS implements VisitStrategy { + + private VisitInfo lastVisit = null; + + /** + * Retrieve the info of the last visit + * + * @return an info of the visit + */ + public VisitInfo getLastVisit() { + return lastVisit; + } + + @Override + public void visit(Graph graph, V source, Consumer visit) throws NullPointerException, IllegalArgumentException { + lastVisit = new VisitInfo<>(source); + final LinkedList toVisitChildren = new LinkedList<>(); + + toVisitChildren.push(source); + if (visit != null) + visit.accept(source); + lastVisit.setVisited(source); + + while (!toVisitChildren.isEmpty()) { + V current = toVisitChildren.removeFirst(); + + for (V child : graph.getChildren(current)) + if (!lastVisit.isDiscovered(child)) { + toVisitChildren.addLast(child); + + lastVisit.setVisited(child); + lastVisit.setParent(current, child); + if (visit != null) + visit.accept(child); + } + } + } +} diff --git a/src/berack96/sim/util/graph/visit/DFS.java b/src/berack96/sim/util/graph/visit/DFS.java new file mode 100644 index 0000000..9f34209 --- /dev/null +++ b/src/berack96/sim/util/graph/visit/DFS.java @@ -0,0 +1,58 @@ +package berack96.sim.util.graph.visit; + +import berack96.sim.util.graph.Graph; + +import java.util.Iterator; +import java.util.Stack; +import java.util.function.Consumer; + +/** + * Depth-first search
+ * The algorithm starts at the root node and explores as far as possible along each branch before backtracking. + * + * @param the vertex of the graph + * @param the weight of the graph + */ +public class DFS implements VisitStrategy { + + private VisitInfo lastVisit = null; + + /** + * Retrieve the info of the last visit + * + * @return an info of the visit + */ + public VisitInfo getLastVisit() { + return lastVisit; + } + + @Override + public void visit(Graph graph, V source, Consumer visit) throws NullPointerException, IllegalArgumentException { + lastVisit = new VisitInfo<>(source); + final Stack toVisit = new Stack<>(); + + toVisit.push(source); + + while (!toVisit.isEmpty()) { + V current = toVisit.peek(); + boolean hasChildToVisit = false; + Iterator iter = graph.getChildren(current).iterator(); + + while (iter.hasNext() && !hasChildToVisit) { + V child = iter.next(); + if (!lastVisit.isDiscovered(child)) { + hasChildToVisit = true; + toVisit.push(child); + lastVisit.setParent(current, child); + } + } + + if (!hasChildToVisit) { + toVisit.pop(); + lastVisit.setVisited(current); + if (visit != null) + visit.accept(current); + } + } + } +} diff --git a/src/berack96/sim/util/graph/visit/VisitStrategy.java b/src/berack96/sim/util/graph/visit/VisitStrategy.java new file mode 100644 index 0000000..1d08008 --- /dev/null +++ b/src/berack96/sim/util/graph/visit/VisitStrategy.java @@ -0,0 +1,193 @@ +package berack96.sim.util.graph.visit; + +import berack96.sim.util.graph.Graph; + +import java.util.Hashtable; +import java.util.Map; +import java.util.function.Consumer; + +/** + * This class is used for define some strategy for the visit of a graph. + * + * @param The Object that represent a vertex + * @param The Object that represent the edge (more specifically the weight of the edge) + * @author Berack96 + */ +public interface VisitStrategy { + + /** + * With this the graph will be visited accordingly to the strategy of the visit.
+ * Some strategy can accept a source vertex null, because they visit all the graph anyway.
+ * If you want to stop the visit of the graph, you just have to throw any exception in the visit function, but be sure to catch it + * + * @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 one of the arguments is null (only the consumers can be null) + * @throws IllegalArgumentException if the source vertex is not in the graph + * @throws UnsupportedOperationException in the case that the visit algorithm cannot be applied to the graph + */ + void visit(Graph graph, V source, Consumer visit) throws NullPointerException, IllegalArgumentException, UnsupportedOperationException; + + /** + * The class used for getting the info of the visit.
+ * It could be used with the algorithm of the visit for set some useful data. + * + * @param the vertex of the visit + * @author Berack96 + */ + class VisitInfo { + private final Map discovered; + private final Map visited; + private final Map parent; + private final V source; + private long time; + + /** + * Need a source for initialize the basic values + * + * @param source the source of the visit + * @throws NullPointerException if the source is null + */ + public VisitInfo(V source) { + if (source == null) + throw new NullPointerException(); + + discovered = new Hashtable<>(); + visited = new Hashtable<>(); + parent = new Hashtable<>(); + + this.time = 0; + this.source = source; + setDiscovered(source); + } + + /** + * The time of the vertex when it is discovered in the visit.
+ * For "discovered" i mean when the node is first found by the visit algorithm. It may depends form {@link VisitStrategy}
+ * The time starts at 0 and for each vertex discovered it is increased by one. If a vertex is visited it also increase the time
+ * + * @param vertex the vertex needed + * @return the time of it's discovery + * @throws IllegalArgumentException if the vertex is not discovered + * @throws NullPointerException if the vertex is null + */ + public long getTimeDiscover(V vertex) throws IllegalArgumentException, NullPointerException { + Long time = discovered.get(vertex); + if (time == null) + throw new IllegalArgumentException(); + return time; + } + + /** + * The time when the vertex is visited by the algorithm
+ * For "visited" i mean when the node is finally visited by the visit algorithm. It may depends form {@link VisitStrategy}
+ * The time starts at 0 and for each vertex discovered or visited is increased by one
+ * + * @param vertex the vertex needed + * @return the time of it's visit + * @throws IllegalArgumentException if the vertex is not visited + * @throws NullPointerException if the vertex is null + */ + public long getTimeVisit(V vertex) throws IllegalArgumentException, NullPointerException { + Long time = visited.get(vertex); + if (time == null) + throw new IllegalArgumentException(); + return time; + } + + /** + * Tells if a vertex is discovered or not + * + * @param vertex the vertex chosen + * @return true if is discovered + */ + public boolean isDiscovered(V vertex) throws NullPointerException { + try { + return discovered.containsKey(vertex); + } catch (NullPointerException e) { + return false; + } + } + + /** + * Tells if the vertex is visited or not + * + * @param vertex the vertex chosen + * @return true if is visited + */ + public boolean isVisited(V vertex) throws NullPointerException { + try { + return visited.containsKey(vertex); + } catch (NullPointerException e) { + return false; + } + } + + /** + * Set a vertex as "visited". After this call the vertex is set as discovered (if not already) and visited.
+ * Next this call it will be possible to get the time of visit of that vertex
+ * Does nothing if the vertex is already been visited. + * + * @param vertex the vertex that has been visited + */ + public synchronized void setVisited(V vertex) { + setDiscovered(vertex); + if (!visited.containsKey(vertex)) + visited.put(vertex, time++); + } + + /** + * Set a vertex as "discovered". After this call the vertex is set as discovered and it will be possible to get the time of it's discovery
+ * Does nothing if the vertex is already been discovered. + * + * @param vertex the vertex that has been discovered + */ + public synchronized void setDiscovered(V vertex) { + if (!discovered.containsKey(vertex)) + discovered.put(vertex, time++); + } + + /** + * Set the parent of a particular vertex
+ * The parent of a vertex is the one that has discovered it
+ * If the target vertex is not already discovered, then {@link #setDiscovered(Object)} is called
+ * + * @param parent the vertex that is the parent + * @param child the vertex discovered + * @throws IllegalArgumentException if the parent is not already discovered + */ + public synchronized void setParent(V parent, V child) throws IllegalArgumentException { + if (!isDiscovered(parent)) + throw new IllegalArgumentException(parent.toString()); + + setDiscovered(child); + this.parent.putIfAbsent(child, parent); + } + + /** + * Get the source of the visit. + * + * @return the source vertex where it's started the visit + */ + public V getSource() { + return source; + } + + /** + * Get the parent of a particular vertex.
+ * The parent of a vertex is the one that has discovered it
+ * If the vertex has no parent (it has not been set by the visit algorithm or it's the source) then null is returned. + * + * @param vertex the child vertex + * @return the parent of the child + * @throws IllegalArgumentException if the vertex has not been discovered yet + */ + public V getParentOf(V vertex) throws IllegalArgumentException { + if (isDiscovered(vertex)) + return parent.get(vertex); + + throw new IllegalArgumentException(); + } + } +} diff --git a/test/berack96/test/sim/TestGraph.java b/test/berack96/test/sim/TestGraph.java new file mode 100644 index 0000000..182dd8e --- /dev/null +++ b/test/berack96/test/sim/TestGraph.java @@ -0,0 +1,907 @@ +package berack96.test.sim; + +import berack96.sim.util.graph.Graph; +import berack96.sim.util.graph.visit.BFS; +import berack96.sim.util.graph.visit.DFS; +import berack96.sim.util.graph.visit.VisitStrategy; +import org.junit.Before; +import org.junit.Test; + +import java.util.*; + +import static org.junit.Assert.*; + +public class TestGraph { + + private Graph graph; + + private final Exception nullException = new NullPointerException(Graph.PARAM_NULL); + private final Exception notException = new IllegalArgumentException(Graph.VERTEX_NOT_CONTAINED); + + @Before + public void before() { + // Change here the instance for changing all the test for that particular class + graph = null; + } + + @Test + public void basicVertex() { + assertEquals(0, graph.numberOfVertices()); + + graph.addVertex("1"); + graph.addVertex("2"); + shouldThrow(nullException, () -> graph.addVertex(null)); + + assertTrue(graph.contains("1")); + assertFalse(graph.contains("0")); + assertTrue(graph.contains("2")); + assertFalse(graph.contains("3")); + assertEquals(2, graph.numberOfVertices()); + + graph.removeVertex("1"); + assertFalse(graph.contains("1")); + assertTrue(graph.contains("2")); + assertEquals(1, graph.numberOfVertices()); + + graph.addVertex("3"); + assertTrue(graph.contains("3")); + shouldThrow(nullException, () -> graph.contains(null)); + shouldThrow(nullException, () -> graph.addVertexIfAbsent(null)); + + assertTrue(graph.addVertexIfAbsent("4")); + assertFalse(graph.addVertexIfAbsent("4")); + assertFalse(graph.addVertexIfAbsent("2")); + + assertEquals(3, graph.numberOfVertices()); + shouldContain(graph.vertices(), "2", "3", "4"); + + graph.removeAllVertex(); + shouldContain(graph.vertices()); + + Set vertices = new HashSet<>(Arrays.asList("1", "5", "24", "2", "3")); + graph.addAllVertices(vertices); + shouldContain(graph.vertices(), vertices.toArray()); + graph.removeVertex("1"); + graph.removeVertex("24"); + shouldContain(graph.vertices(), "5", "2", "3"); + graph.addAllVertices(vertices); + shouldContain(graph.vertices(), vertices.toArray()); + + shouldThrow(nullException, () -> graph.addAllVertices(null)); + } + + @Test + public void basicEdge() { + /* + * This graph should be like this + * + * 1 -> 2 + * | | + * v v + * 3 <-> 5 -> 4 + */ + graph.addVertexIfAbsent("1"); + graph.addVertexIfAbsent("2"); + graph.addVertexIfAbsent("3"); + graph.addVertexIfAbsent("4"); + graph.addVertexIfAbsent("5"); + + shouldThrow(nullException, () -> graph.addEdge(null, "2", 1)); + shouldThrow(nullException, () -> graph.addEdge(null, null, 1)); + shouldThrow(nullException, () -> graph.addEdge("1", null, 1)); + shouldThrow(nullException, () -> graph.containsEdge(null, "2")); + shouldThrow(nullException, () -> graph.containsEdge(null, null)); + shouldThrow(nullException, () -> graph.containsEdge("1", null)); + shouldThrow(nullException, () -> graph.removeEdge(null, "2")); + shouldThrow(nullException, () -> graph.removeEdge(null, null)); + shouldThrow(nullException, () -> graph.removeEdge("1", null)); + shouldThrow(nullException, () -> graph.removeAllEdge(null)); + shouldThrow(nullException, () -> graph.removeAllOutEdge(null)); + shouldThrow(nullException, () -> graph.removeAllInEdge(null)); + + shouldThrow(notException, () -> graph.addEdge("0", "2", 1)); + shouldThrow(notException, () -> graph.addEdge("2", "8", 1)); + shouldThrow(notException, () -> graph.addEdge("9", "6", 1)); + shouldThrow(notException, () -> graph.containsEdge("01", "4")); + shouldThrow(notException, () -> graph.containsEdge("3", "8132")); + shouldThrow(notException, () -> graph.containsEdge("9423", "516")); + shouldThrow(notException, () -> graph.removeEdge("012", "2")); + shouldThrow(notException, () -> graph.removeEdge("2", "28")); + shouldThrow(notException, () -> graph.removeEdge("4329", "62")); + shouldThrow(notException, () -> graph.removeAllEdge("0")); + shouldThrow(notException, () -> graph.removeAllInEdge("011")); + shouldThrow(notException, () -> graph.removeAllOutEdge("9")); + + assertEquals(0, graph.numberOfEdges()); + + assertNull(graph.addEdge("1", "2", 1)); + assertNull(graph.addEdge("1", "3", 1)); + assertNull(graph.addEdge("2", "5", 4)); + assertNull(graph.addEdge("3", "5", 2)); + assertNull(graph.addEdge("5", "3", 2)); + assertNull(graph.addEdge("5", "4", 3)); + + assertEquals(6, graph.numberOfEdges()); + + // All this calls should do nothing + graph.removeEdge("1", "5"); + graph.removeEdge("1", "4"); + graph.removeEdge("2", "3"); + graph.removeEdge("3", "1"); + graph.removeEdge("4", "5"); + + assertEquals(6, graph.numberOfEdges()); + + assertEquals(new Integer(1), graph.getWeight("1", "2")); + assertEquals(new Integer(1), graph.getWeight("1", "3")); + assertEquals(new Integer(4), graph.getWeight("2", "5")); + assertEquals(new Integer(2), graph.getWeight("3", "5")); + assertEquals(new Integer(2), graph.getWeight("5", "3")); + assertEquals(new Integer(3), graph.getWeight("5", "4")); + + assertNull(graph.getWeight("1", "4")); + + assertEquals(new Integer(1), graph.addEdge("1", "2", 102)); + assertEquals(new Integer(102), graph.addEdge("1", "2", 3)); + assertEquals(new Integer(3), graph.addEdge("1", "2", 1)); + + assertEquals(6, graph.numberOfEdges()); + assertTrue(graph.containsEdge("1", "2")); + assertFalse(graph.containsEdge("4", "3")); + assertFalse(graph.containsEdge("2", "1")); + assertFalse(graph.containsEdge("1", "4")); + assertTrue(graph.containsEdge("1", "3")); + assertTrue(graph.containsEdge("3", "5")); + assertTrue(graph.containsEdge("2", "5")); + + graph.removeEdge("2", "5"); + assertFalse(graph.containsEdge("2", "5")); + assertEquals(5, graph.numberOfEdges()); + + graph.removeEdge("1", "2"); + assertFalse(graph.containsEdge("1", "2")); + assertTrue(graph.containsEdge("1", "3")); + assertEquals(4, graph.numberOfEdges()); + graph.addEdge("1", "2", 2); + + graph.removeAllOutEdge("1"); + assertFalse(graph.containsEdge("1", "2")); + assertFalse(graph.containsEdge("1", "3")); + assertEquals(3, graph.numberOfEdges()); + graph.addEdge("1", "2", 2); + graph.addEdge("1", "3", 2); + assertEquals(5, graph.numberOfEdges()); + + graph.removeAllInEdge("3"); + assertFalse(graph.containsEdge("5", "3")); + assertFalse(graph.containsEdge("1", "3")); + assertTrue(graph.containsEdge("3", "5")); + assertEquals(3, graph.numberOfEdges()); + graph.addEdge("1", "3", 2); + graph.addEdge("5", "3", 2); + + graph.removeAllEdge("3"); + assertFalse(graph.containsEdge("5", "3")); + assertFalse(graph.containsEdge("1", "3")); + assertFalse(graph.containsEdge("3", "5")); + assertEquals(2, graph.numberOfEdges()); + + graph.removeAllEdge(); + assertFalse(graph.containsEdge("1", "2")); + assertFalse(graph.containsEdge("1", "3")); + assertFalse(graph.containsEdge("2", "5")); + assertFalse(graph.containsEdge("3", "5")); + assertFalse(graph.containsEdge("5", "3")); + assertFalse(graph.containsEdge("5", "4")); + assertEquals(0, graph.numberOfEdges()); + + shouldThrow(notException, () -> graph.containsEdge("2", "323")); + graph.addEdgeAndVertices("2", "323", 3); + assertTrue(graph.containsEdge("2", "323")); + shouldThrow(notException, () -> graph.containsEdge("2aa", "323")); + graph.addEdgeAndVertices("2aa", "323", 3); + assertTrue(graph.containsEdge("2aa", "323")); + shouldThrow(notException, () -> graph.containsEdge("2bbb", "323bbb")); + graph.addEdgeAndVertices("2bbb", "323bbb", 3); + assertTrue(graph.containsEdge("2bbb", "323bbb")); + shouldThrow(nullException, () -> graph.addEdgeAndVertices(null, "1", 1)); + shouldThrow(nullException, () -> graph.addEdgeAndVertices(null, null, 1)); + shouldThrow(nullException, () -> graph.addEdgeAndVertices("2", null, 1)); + + graph.removeAllVertex(); + graph.addVertex("aaa"); + graph.addVertex("1"); + graph.addVertex("2"); + + shouldContain(graph.vertices(), "1", "2", "aaa"); + shouldContain(graph.edges()); + + Set> edges = new HashSet<>(); + edges.add(new Graph.Edge<>("aaa", "bbb", 3)); + edges.add(new Graph.Edge<>("bbb", "ccc", 4)); + edges.add(new Graph.Edge<>("ccc", "aaa", 5)); + edges.add(new Graph.Edge<>("1", "2", 2)); + graph.addAllEdges(edges); + + shouldContain(graph.vertices(), "1", "2", "aaa", "bbb", "ccc"); + shouldContain(graph.edges(), + new Graph.Edge<>("aaa", "bbb", 3), + new Graph.Edge<>("bbb", "ccc", 4), + new Graph.Edge<>("ccc", "aaa", 5), + new Graph.Edge<>("1", "2", 2)); + } + + @Test + public void advancedEdge() { + /* + * This graph should be like this + * + * 1 -> 2 -> 6 + * ^ + * | | | + * v v + * 3 <-> 5 -> 4 + */ + + graph.addVertexIfAbsent("1"); + graph.addVertexIfAbsent("2"); + graph.addVertexIfAbsent("3"); + graph.addVertexIfAbsent("4"); + graph.addVertexIfAbsent("5"); + graph.addVertexIfAbsent("6"); + + shouldContain(graph.edges()); + + graph.addEdge("1", "2", 1); + graph.addEdge("1", "3", 1); + graph.addEdge("2", "5", 4); + graph.addEdge("2", "6", 5); + graph.addEdge("3", "5", 2); + graph.addEdge("4", "6", 6); + graph.addEdge("5", "3", 9); + graph.addEdge("5", "4", 5); + + shouldContain(graph.getChildren("1"), "2", "3"); + shouldContain(graph.getChildren("2"), "5", "6"); + shouldContain(graph.getChildren("3"), "5"); + shouldContain(graph.getChildren("4"), "6"); + shouldContain(graph.getChildren("5"), "3", "4"); + shouldContain(graph.getChildren("6")); + + shouldContain(graph.getAncestors("1")); + shouldContain(graph.getAncestors("2"), "1"); + shouldContain(graph.getAncestors("3"), "1", "5"); + shouldContain(graph.getAncestors("4"), "5"); + shouldContain(graph.getAncestors("5"), "2", "3"); + shouldContain(graph.getAncestors("6"), "2", "4"); + + shouldContain(graph.getChildrenAndWeight("1").entrySet(), new AbstractMap.SimpleEntry<>("2", 1), new AbstractMap.SimpleEntry<>("3", 1)); + shouldContain(graph.getChildrenAndWeight("2").entrySet(), new AbstractMap.SimpleEntry<>("5", 4), new AbstractMap.SimpleEntry<>("6", 5)); + shouldContain(graph.getChildrenAndWeight("3").entrySet(), new AbstractMap.SimpleEntry<>("5", 2)); + shouldContain(graph.getChildrenAndWeight("4").entrySet(), new AbstractMap.SimpleEntry<>("6", 6)); + shouldContain(graph.getChildrenAndWeight("5").entrySet(), new AbstractMap.SimpleEntry<>("3", 9), new AbstractMap.SimpleEntry<>("4", 5)); + shouldContain(graph.getChildrenAndWeight("6").entrySet()); + + assertEquals(0, graph.degreeIn("1")); + assertEquals(1, graph.degreeIn("2")); + assertEquals(2, graph.degreeIn("3")); + assertEquals(1, graph.degreeIn("4")); + assertEquals(2, graph.degreeIn("5")); + assertEquals(2, graph.degreeIn("6")); + + assertEquals(2, graph.degreeOut("1")); + assertEquals(2, graph.degreeOut("2")); + assertEquals(1, graph.degreeOut("3")); + assertEquals(1, graph.degreeOut("4")); + assertEquals(2, graph.degreeOut("5")); + assertEquals(0, graph.degreeOut("6")); + + assertEquals(2, graph.degree("1")); + assertEquals(3, graph.degree("2")); + assertEquals(3, graph.degree("3")); + assertEquals(2, graph.degree("4")); + assertEquals(4, graph.degree("5")); + assertEquals(2, graph.degree("6")); + + shouldContain(graph.edges(), + new Graph.Edge<>("1", "2", 1), + new Graph.Edge<>("1", "3", 1), + new Graph.Edge<>("2", "5", 4), + new Graph.Edge<>("2", "6", 5), + new Graph.Edge<>("3", "5", 2), + new Graph.Edge<>("4", "6", 6), + new Graph.Edge<>("5", "3", 9), + new Graph.Edge<>("5", "4", 5)); + } + + @Test + public void preBasicVisit() { + VisitStrategy.VisitInfo info = new VisitStrategy.VisitInfo<>(0); + assertTrue(info.isDiscovered(0)); + assertFalse(info.isVisited(0)); + assertEquals(0, info.getTimeDiscover(0)); + assertEquals(new Integer(0), info.getSource()); + assertNull(info.getParentOf(0)); + + assertFalse(info.isVisited(null)); + assertFalse(info.isDiscovered(null)); + + shouldThrow(new IllegalArgumentException(), () -> info.getTimeVisit(0)); + shouldThrow(new IllegalArgumentException(), () -> info.getTimeDiscover(1)); + shouldThrow(new IllegalArgumentException(), () -> info.getParentOf(2)); + shouldThrow(new IllegalArgumentException(), () -> info.getParentOf(null)); + + shouldThrow(new NullPointerException(), () -> info.getTimeDiscover(null)); + shouldThrow(new NullPointerException(), () -> info.getTimeVisit(null)); + } + + @Test + public void basicVisit() { + /* + * This graph should be like this + * + * 1 -> 2 <- 6 7 + * ^ ^ + * | | | | + * v v v + * 3 <- 5 -> 4 8 + */ + + graph.addVertexIfAbsent("1"); + graph.addVertexIfAbsent("2"); + graph.addVertexIfAbsent("3"); + graph.addVertexIfAbsent("4"); + graph.addVertexIfAbsent("5"); + graph.addVertexIfAbsent("6"); + graph.addVertexIfAbsent("7"); + graph.addVertexIfAbsent("8"); + + graph.addEdge("1", "2", 1); + graph.addEdge("1", "3", 1); + graph.addEdge("2", "5", 4); + graph.addEdge("4", "6", 5); + graph.addEdge("5", "3", 6); + graph.addEdge("5", "4", 3); + graph.addEdge("6", "2", 2); + graph.addEdge("7", "8", 8); + graph.addEdge("8", "7", 8); + + Exception nullP = new NullPointerException(); + shouldThrow(nullP, () -> graph.visit(null, new DFS<>(), null)); + shouldThrow(nullP, () -> graph.visit(null, null, null)); + shouldThrow(nullP, () -> graph.visit("1", null, null)); + + shouldThrow(notException, () -> graph.visit("1010", new DFS<>(), null)); + + DFS dfs = new DFS<>(); + graph.visit("1", dfs, null); + VisitStrategy.VisitInfo visitDFS = dfs.getLastVisit(); + assertEquals(0, visitDFS.getTimeDiscover("1")); + assertEquals(1, visitDFS.getTimeDiscover("2")); + assertEquals(2, visitDFS.getTimeDiscover("5")); + assertEquals(3, visitDFS.getTimeDiscover("3")); + assertEquals(4, visitDFS.getTimeVisit("3")); + assertEquals(5, visitDFS.getTimeDiscover("4")); + assertEquals(6, visitDFS.getTimeDiscover("6")); + assertEquals(7, visitDFS.getTimeVisit("6")); + assertEquals(8, visitDFS.getTimeVisit("4")); + assertEquals(9, visitDFS.getTimeVisit("5")); + assertEquals(10, visitDFS.getTimeVisit("2")); + assertEquals(11, visitDFS.getTimeVisit("1")); + assertFalse(visitDFS.isDiscovered("7")); + assertFalse(visitDFS.isDiscovered("8")); + + BFS bfs = new BFS<>(); + graph.visit("1", bfs, null); + VisitStrategy.VisitInfo visitBFS = bfs.getLastVisit(); + assertEquals(0, visitBFS.getTimeDiscover("1")); + assertEquals(1, visitBFS.getTimeVisit("1")); + assertEquals(2, visitBFS.getTimeDiscover("2")); + assertEquals(3, visitBFS.getTimeVisit("2")); + assertEquals(4, visitBFS.getTimeDiscover("3")); + assertEquals(5, visitBFS.getTimeVisit("3")); + assertEquals(6, visitBFS.getTimeDiscover("5")); + assertEquals(7, visitBFS.getTimeVisit("5")); + assertEquals(8, visitBFS.getTimeDiscover("4")); + assertEquals(9, visitBFS.getTimeVisit("4")); + assertEquals(10, visitBFS.getTimeDiscover("6")); + assertEquals(11, visitBFS.getTimeVisit("6")); + assertFalse(visitBFS.isDiscovered("7")); + assertFalse(visitBFS.isDiscovered("8")); + } + + @Test + public void iterable() { + /* + * This graph should be like this + * + * 1 -> 2 <- 6 7 + * ^ ^ + * | | | | + * v v v + * 3 <- 5 -> 4 8 + */ + + graph.addVertexIfAbsent("1"); + graph.addVertexIfAbsent("2"); + graph.addVertexIfAbsent("3"); + graph.addVertexIfAbsent("4"); + graph.addVertexIfAbsent("5"); + graph.addVertexIfAbsent("6"); + graph.addVertexIfAbsent("7"); + graph.addVertexIfAbsent("8"); + + graph.addEdge("1", "2", 1); + graph.addEdge("1", "3", 1); + graph.addEdge("2", "5", 4); + graph.addEdge("4", "6", 5); + graph.addEdge("5", "3", 6); + graph.addEdge("5", "4", 3); + graph.addEdge("6", "2", 2); + graph.addEdge("7", "8", 8); + graph.addEdge("8", "7", 8); + + Set vertices = new HashSet<>(); + for (String vertex : graph) + vertices.add(vertex); + shouldContain(vertices, "1", "2", "3", "4", "5", "6", "7", "8"); + + vertices.clear(); + graph.forEach(vertices::add); + shouldContain(vertices, "1", "2", "3", "4", "5", "6", "7", "8"); + + vertices.clear(); + Iterator iter = graph.iterator(); + while (iter.hasNext()) + vertices.add(iter.next()); + + shouldContain(vertices, "1", "2", "3", "4", "5", "6", "7", "8"); + } + + @Test + public void scc() { + /* + * This graph should be like this + * + * 1 -> 2 -> 6 + * ^ + * | | | + * v v + * 3 <-> 5 -> 4 + */ + + graph.addVertexIfAbsent("1"); + graph.addVertexIfAbsent("2"); + graph.addVertexIfAbsent("3"); + graph.addVertexIfAbsent("4"); + graph.addVertexIfAbsent("5"); + graph.addVertexIfAbsent("6"); + + graph.addEdge("1", "2", 1); + graph.addEdge("1", "3", 1); + graph.addEdge("2", "5", 4); + graph.addEdge("2", "6", 5); + graph.addEdge("3", "5", 2); + graph.addEdge("4", "6", 6); + graph.addEdge("5", "3", 9); + graph.addEdge("5", "4", 5); + + shouldContain(graph.stronglyConnectedComponents(), new HashSet<>(Collections.singletonList("6")), new HashSet<>(Arrays.asList("3", "5")), new HashSet<>(Collections.singletonList("4")), new HashSet<>(Collections.singletonList("1")), new HashSet<>(Collections.singletonList("2"))); + + /* + * This graph should be like this + * + * 1 -> 2 <- 6 7 + * ^ ^ + * | | | | + * v v v + * 3 <- 5 -> 4 8 + */ + before(); + graph.addVertexIfAbsent("1"); + graph.addVertexIfAbsent("2"); + graph.addVertexIfAbsent("3"); + graph.addVertexIfAbsent("4"); + graph.addVertexIfAbsent("5"); + graph.addVertexIfAbsent("6"); + graph.addVertexIfAbsent("7"); + graph.addVertexIfAbsent("8"); + + graph.addEdge("1", "2", 1); + graph.addEdge("1", "3", 1); + graph.addEdge("2", "5", 4); + graph.addEdge("4", "6", 5); + graph.addEdge("5", "3", 6); + graph.addEdge("5", "4", 3); + graph.addEdge("6", "2", 2); + graph.addEdge("7", "8", 8); + graph.addEdge("8", "7", 8); + + shouldContain(graph.stronglyConnectedComponents(), new HashSet<>(Arrays.asList("7", "8")), new HashSet<>(Arrays.asList("2", "5", "4", "6")), new HashSet<>(Collections.singletonList("3")), new HashSet<>(Collections.singletonList("1"))); + } + + @Test + public void cyclic() { + /* + * This graph should be like this + * + * 1 -> 2 -> 6 + * ^ + * | | | + * v v + * 3 -> 5 -> 4 + */ + + assertFalse(graph.isCyclic()); + assertTrue(graph.isDAG()); + + graph.addVertexIfAbsent("1"); + graph.addVertexIfAbsent("2"); + graph.addVertexIfAbsent("3"); + graph.addVertexIfAbsent("4"); + graph.addVertexIfAbsent("5"); + graph.addVertexIfAbsent("6"); + + assertFalse(graph.isCyclic()); + assertTrue(graph.isDAG()); + + graph.addEdge("1", "2", 1); + graph.addEdge("1", "3", 1); + graph.addEdge("2", "5", 4); + graph.addEdge("2", "6", 5); + graph.addEdge("3", "5", 2); + graph.addEdge("4", "6", 6); + graph.addEdge("5", "4", 5); + + assertFalse(graph.isCyclic()); + assertTrue(graph.isDAG()); + + /* + * This graph should be like this + * + * 1 -> 2 <- 6 + * ^ + * | | | + * v v + * 3 <- 5 -> 4 + */ + before(); + graph.addVertexIfAbsent("1"); + graph.addVertexIfAbsent("2"); + graph.addVertexIfAbsent("3"); + graph.addVertexIfAbsent("4"); + graph.addVertexIfAbsent("5"); + graph.addVertexIfAbsent("6"); + + assertFalse(graph.isCyclic()); + assertTrue(graph.isDAG()); + + + graph.addEdge("1", "2", 1); + assertFalse(graph.isCyclic()); + assertTrue(graph.isDAG()); + graph.addEdge("1", "3", 1); + assertFalse(graph.isCyclic()); + assertTrue(graph.isDAG()); + graph.addEdge("2", "5", 4); + assertFalse(graph.isCyclic()); + assertTrue(graph.isDAG()); + graph.addEdge("4", "6", 5); + assertFalse(graph.isCyclic()); + assertTrue(graph.isDAG()); + graph.addEdge("5", "3", 6); + assertFalse(graph.isCyclic()); + assertTrue(graph.isDAG()); + graph.addEdge("5", "4", 3); + assertFalse(graph.isCyclic()); + assertTrue(graph.isDAG()); + graph.addEdge("6", "2", 2); + assertTrue(graph.isCyclic()); + assertFalse(graph.isDAG()); + } + + @Test + public void transpose() { + /* + * This graph should be like this + * + * 1 -> 2 <- 6 7 + * ^ + * | | | | + * v v v + * 3 <- 5 -> 4 8 + */ + + graph.addVertexIfAbsent("1"); + graph.addVertexIfAbsent("2"); + graph.addVertexIfAbsent("3"); + graph.addVertexIfAbsent("4"); + graph.addVertexIfAbsent("5"); + graph.addVertexIfAbsent("6"); + graph.addVertexIfAbsent("7"); + graph.addVertexIfAbsent("8"); + + graph.addEdge("1", "2", 1); + graph.addEdge("1", "3", 1); + graph.addEdge("2", "5", 4); + graph.addEdge("4", "6", 5); + graph.addEdge("5", "3", 6); + graph.addEdge("5", "4", 3); + graph.addEdge("6", "2", 2); + graph.addEdge("7", "8", 8); + + Graph transposed = graph.transpose(); + + DFS dfs = new DFS<>(); + transposed.visit("6", dfs, null); + VisitStrategy.VisitInfo visitDFS = dfs.getLastVisit(); + assertEquals(0, visitDFS.getTimeDiscover("6")); + assertEquals(1, visitDFS.getTimeDiscover("4")); + assertEquals(2, visitDFS.getTimeDiscover("5")); + assertEquals(3, visitDFS.getTimeDiscover("2")); + assertEquals(4, visitDFS.getTimeDiscover("1")); + assertEquals(5, visitDFS.getTimeVisit("1")); + assertEquals(6, visitDFS.getTimeVisit("2")); + assertEquals(7, visitDFS.getTimeVisit("5")); + assertEquals(8, visitDFS.getTimeVisit("4")); + assertEquals(9, visitDFS.getTimeVisit("6")); + assertFalse(visitDFS.isDiscovered("3")); + assertFalse(visitDFS.isDiscovered("7")); + assertFalse(visitDFS.isDiscovered("8")); + + transposed.visit("8", dfs, null); + visitDFS = dfs.getLastVisit(); + assertEquals(0, visitDFS.getTimeDiscover("8")); + assertEquals(1, visitDFS.getTimeDiscover("7")); + assertEquals(2, visitDFS.getTimeVisit("7")); + assertEquals(3, visitDFS.getTimeVisit("8")); + assertFalse(visitDFS.isDiscovered("1")); + assertFalse(visitDFS.isDiscovered("2")); + assertFalse(visitDFS.isDiscovered("3")); + assertFalse(visitDFS.isDiscovered("4")); + assertFalse(visitDFS.isDiscovered("5")); + assertFalse(visitDFS.isDiscovered("6")); + } + + @Test + public void topologicalSort() { + /* + * This graph should be like this + * + * 1 -> 2 -> 6 + * ^ + * | | | + * v v + * 3 -> 5 -> 4 + */ + + graph.addVertexIfAbsent("1"); + graph.addVertexIfAbsent("2"); + graph.addVertexIfAbsent("3"); + graph.addVertexIfAbsent("4"); + graph.addVertexIfAbsent("5"); + graph.addVertexIfAbsent("6"); + + graph.addEdge("1", "2", 1); + graph.addEdge("1", "3", 1); + graph.addEdge("2", "5", 4); + graph.addEdge("2", "6", 5); + graph.addEdge("3", "5", 2); + graph.addEdge("4", "6", 6); + graph.addEdge("5", "4", 5); + + shouldContainInOrder(graph.topologicalSort(), "1", "3", "2", "5", "4", "6"); + + } + + @Test + public void distanceVV() { + /* + * This graph should be like this + * + * 1 -> 2 <- 6 7 + * ^ + * | | | | + * v v v + * 3 <- 5 -> 4 8 + */ + + graph.addVertexIfAbsent("1"); + graph.addVertexIfAbsent("2"); + graph.addVertexIfAbsent("3"); + graph.addVertexIfAbsent("4"); + graph.addVertexIfAbsent("5"); + graph.addVertexIfAbsent("6"); + graph.addVertexIfAbsent("7"); + graph.addVertexIfAbsent("8"); + + graph.addEdge("1", "2", 1); + graph.addEdge("1", "3", 10); + graph.addEdge("2", "5", 4); + graph.addEdge("4", "6", 5); + graph.addEdge("5", "3", 3); + graph.addEdge("5", "4", 3); + graph.addEdge("6", "2", 2); + graph.addEdge("7", "8", 8); + + List> distance = graph.distance("1", "6"); + int sum = distance.stream().mapToInt(Graph.Edge::getWeight).sum(); + assertEquals(13, sum); + shouldContainInOrder(distance, + new Graph.Edge<>("1", "2", 1), + new Graph.Edge<>("2", "5", 4), + new Graph.Edge<>("5", "4", 3), + new Graph.Edge<>("4", "6", 5)); + distance = graph.distance("1", "3"); + sum = distance.stream().mapToInt(Graph.Edge::getWeight).sum(); + assertEquals(8, sum); + shouldContainInOrder(distance, + new Graph.Edge<>("1", "2", 1), + new Graph.Edge<>("2", "5", 4), + new Graph.Edge<>("5", "3", 3)); + + shouldContainInOrder(graph.distance("7", "8"), new Graph.Edge<>("7", "8", 8)); + + shouldThrow(nullException, () -> graph.distance(null, "1")); + shouldThrow(nullException, () -> graph.distance(null, null)); + shouldThrow(nullException, () -> graph.distance("1", null)); + shouldThrow(notException, () -> graph.distance("34", "1")); + shouldThrow(notException, () -> graph.distance("2", "36")); + shouldThrow(notException, () -> graph.distance("689", "374")); + shouldThrow(new UnsupportedOperationException(Graph.NOT_CONNECTED), () -> graph.distance("1", "7")); + shouldThrow(new UnsupportedOperationException(Graph.NOT_CONNECTED), () -> graph.distance("3", "2")); + } + + @Test + public void distanceVtoAll() { + /* + * This graph should be like this + * + * 1 -> 2 <- 6 7 + * ^ + * | | | | + * v v v + * 3 <- 5 -> 4 -> 8 + */ + + graph.addVertexIfAbsent("1"); + graph.addVertexIfAbsent("2"); + graph.addVertexIfAbsent("3"); + graph.addVertexIfAbsent("4"); + graph.addVertexIfAbsent("5"); + graph.addVertexIfAbsent("6"); + graph.addVertexIfAbsent("7"); + graph.addVertexIfAbsent("8"); + + graph.addEdge("1", "2", 1); + graph.addEdge("1", "3", 10); + graph.addEdge("2", "5", 4); + graph.addEdge("4", "6", 5); + graph.addEdge("4", "8", 2); + graph.addEdge("5", "3", 3); + graph.addEdge("5", "4", 3); + graph.addEdge("6", "2", 2); + graph.addEdge("7", "8", 8); + + Map>> distance = graph.distance("1"); + assertNull(distance.get("1")); + shouldContainInOrder(distance.get("2"), + new Graph.Edge<>("1", "2", 1)); + shouldContainInOrder(distance.get("3"), + new Graph.Edge<>("1", "2", 1), + new Graph.Edge<>("2", "5", 4), + new Graph.Edge<>("5", "3", 3)); + shouldContain(distance.get("4"), + new Graph.Edge<>("1", "2", 1), + new Graph.Edge<>("2", "5", 4), + new Graph.Edge<>("5", "4", 3)); + shouldContain(distance.get("5"), + new Graph.Edge<>("1", "2", 1), + new Graph.Edge<>("2", "5", 4)); + shouldContain(distance.get("6"), + new Graph.Edge<>("1", "2", 1), + new Graph.Edge<>("2", "5", 4), + new Graph.Edge<>("5", "4", 3), + new Graph.Edge<>("4", "6", 5)); + assertNull(distance.get("7")); + shouldContain(distance.get("8"), + new Graph.Edge<>("1", "2", 1), + new Graph.Edge<>("2", "5", 4), + new Graph.Edge<>("5", "4", 3), + new Graph.Edge<>("4", "8", 2)); + } + + @Test + public void subGraph() { + /* + * This graph should be like this + * + * 1 -> 2 <- 6 + * ^ + * | | | + * v v + * 3 <- 5 -> 4 + */ + + graph.addVertexIfAbsent("1"); + graph.addVertexIfAbsent("2"); + graph.addVertexIfAbsent("3"); + graph.addVertexIfAbsent("4"); + graph.addVertexIfAbsent("5"); + graph.addVertexIfAbsent("6"); + + graph.addEdge("1", "2", 1); + graph.addEdge("1", "3", 1); + graph.addEdge("2", "5", 4); + graph.addEdge("4", "6", 6); + graph.addEdge("5", "3", 2); + graph.addEdge("5", "4", 5); + graph.addEdge("6", "2", 2); + + Graph sub = graph.subGraph("1", -541); + shouldContain(sub.vertices(), "1"); + shouldContain(sub.edges()); + + sub = graph.subGraph("1", 0); + shouldContain(sub.vertices(), "1"); + shouldContain(sub.edges()); + + sub = graph.subGraph("1", 1); + shouldContain(sub.vertices(), "1", "2", "3"); + shouldContain(sub.edges(), + new Graph.Edge<>("1", "2", 1), + new Graph.Edge<>("1", "3", 1)); + + sub = graph.subGraph("1", 3); + shouldContain(sub.vertices(), "1", "2", "3", "5", "4"); + shouldContain(sub.edges(), + new Graph.Edge<>("1", "2", 1), + new Graph.Edge<>("1", "3", 1), + new Graph.Edge<>("2", "5", 4), + new Graph.Edge<>("5", "3", 2), + new Graph.Edge<>("5", "4", 5)); + + sub = graph.subGraph("6", 2); + shouldContain(sub.vertices(), "6", "2", "5"); + shouldContain(sub.edges(), + new Graph.Edge<>("2", "5", 4), + new Graph.Edge<>("6", "2", 2)); + + sub = graph.subGraph("1", 77689); + shouldContain(sub.vertices(), "1", "2", "3", "5", "4", "6"); + shouldContain(sub.edges(), + new Graph.Edge<>("1", "2", 1), + new Graph.Edge<>("1", "3", 1), + new Graph.Edge<>("2", "5", 4), + new Graph.Edge<>("4", "6", 6), + new Graph.Edge<>("5", "3", 2), + new Graph.Edge<>("5", "4", 5), + new Graph.Edge<>("6", "2", 2)); + } + + // TODO test saveFile + + private void shouldContain(Collection actual, Object... expected) { + assertEquals("They have not the same number of elements\nActual: " + actual, expected.length, actual.size()); + + for (Object obj : expected) + assertTrue("Not containing: [" + obj + "]\nBut has: " + actual, actual.contains(obj)); + } + + private void shouldContainInOrder(List actual, Object... expected) { + assertEquals("They have not the same number of elements\nActual: " + actual, expected.length, actual.size()); + + for (int i = 0; i < actual.size(); i++) + assertEquals("Index: " + i, expected[i], actual.get(i)); + } + + private void shouldThrow(Exception expected, Runnable runnable) { + try { + runnable.run(); + fail("It has't thrown: " + expected.getClass().getSimpleName()); + } catch (Exception actual) { + assertEquals(expected.getClass(), actual.getClass()); + assertEquals(expected.getMessage(), actual.getMessage()); + } + } +}