package berack96.lib.graph.impl;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import berack96.lib.graph.Edge;
import berack96.lib.graph.Graph;
import berack96.lib.graph.Vertex;
import berack96.lib.graph.visit.VisitStrategy;
import berack96.lib.graph.visit.impl.Dijkstra;
import berack96.lib.graph.visit.impl.Tarjan;
import berack96.lib.graph.visit.impl.VisitInfo;
/**
* 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
* @author Berack96
*/
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<>();
/**
* Map that contains the marker as key and a set of all the vertices that has it as the value.
* This map is build like this for performance in creating the marker for multiple vertices.
* If you flip the parameters (object and set) then has more performance over the single vertex.
*/
private final Map> markers = 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 = new HashMap<>();
@Override
public boolean isCyclic() {
return stronglyConnectedComponents().size() != numberOfVertices();
}
@Override
public boolean isDAG() {
return !isCyclic();
}
@Override
public Vertex getVertex(V vertex) throws NullPointerException, IllegalArgumentException {
checkNullAndExist(vertex);
return new Vertex<>(this, vertex);
}
@Override
public void addVertex(V vertex) throws NullPointerException {
checkNull(vertex);
edges.put(vertex, new HashMap<>());
graphChanged();
}
@Override
public boolean addVertexIfAbsent(V vertex) throws NullPointerException {
if (contains(vertex))
return false;
addVertex(vertex);
return true;
}
@Override
public void addAllVertices(Collection vertices) throws NullPointerException {
checkNull(vertices);
vertices.forEach(this::addVertexIfAbsent);
}
@Override
public void removeVertex(V vertex) throws NullPointerException {
if (contains(vertex)) {
edges.remove(vertex);
edges.forEach((v, map) -> map.remove(vertex));
markers.forEach((mark, set) -> set.remove(vertex));
graphChanged();
}
}
@Override
public void removeAllVertex() {
edges.clear();
markers.clear();
graphChanged();
}
@Override
public boolean contains(V vertex) throws NullPointerException {
checkNull(vertex);
return edges.containsKey(vertex);
}
@Override
public void mark(V vertex, Object mark) throws NullPointerException, IllegalArgumentException {
checkNullAndExist(vertex);
checkNull(mark);
Set set = markers.computeIfAbsent(mark, (v) -> new HashSet<>());
set.add(vertex);
}
@Override
public void unMark(V vertex, Object mark) throws NullPointerException, IllegalArgumentException {
checkNullAndExist(vertex);
checkNull(mark);
markers.get(mark).remove(vertex);
}
@Override
public void unMark(V vertex) throws NullPointerException, IllegalArgumentException {
checkNullAndExist(vertex);
markers.forEach( (mark, set) -> set.remove(vertex) );
}
@Override
public Collection getMarkedWith(Object mark) throws NullPointerException {
checkNull(mark);
return markers.computeIfAbsent(mark, (v) -> new HashSet<>());
}
@Override
public Collection getMarks(V vertex) throws NullPointerException, IllegalArgumentException {
checkNullAndExist(vertex);
Collection marks = new HashSet<>();
markers.forEach( (mark, set) -> {
if (set.contains(vertex))
marks.add(mark);
});
return marks;
}
@Override
public void unMarkAll(Object mark) {
checkNull(mark);
markers.remove(mark);
}
@Override
public void unMarkAll() {
markers.clear();
}
@Override
public W addEdge(V vertex1, V vertex2, W weight) throws NullPointerException, IllegalArgumentException {
checkNullAndExist(vertex1);
checkNullAndExist(vertex2);
checkNull(weight);
W old = edges.get(vertex1).put(vertex2, weight);
graphChanged();
return old;
}
@Override
public W addEdge(Edge edge) throws NullPointerException, IllegalArgumentException {
return addEdge(edge.getSource(), edge.getDestination(), edge.getWeight());
}
@Override
public W addEdgeAndVertices(V vertex1, V vertex2, W weight) throws NullPointerException {
addVertexIfAbsent(vertex1);
addVertexIfAbsent(vertex2);
return addEdge(vertex1, vertex2, weight);
}
@Override
public W addEdgeAndVertices(Edge edge) throws NullPointerException, IllegalArgumentException {
return addEdgeAndVertices(edge.getSource(), edge.getDestination(), edge.getWeight());
}
@Override
public void addAllEdges(Collection> 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);
edges.get(vertex1).remove(vertex2);
graphChanged();
}
@Override
public void removeAllInEdge(V vertex) throws NullPointerException, IllegalArgumentException {
checkNullAndExist(vertex);
edges.forEach((v, map) -> map.remove(vertex));
graphChanged();
}
@Override
public void removeAllOutEdge(V vertex) throws NullPointerException, IllegalArgumentException {
checkNullAndExist(vertex);
edges.put(vertex, new HashMap<>());
graphChanged();
}
@Override
public void removeAllEdge(V vertex) throws NullPointerException, IllegalArgumentException {
checkNullAndExist(vertex);
removeVertex(vertex);
addVertex(vertex);
}
@Override
public void removeAllEdge() {
edges.forEach((v, map) -> map.clear());
graphChanged();
}
@Override
public boolean containsEdge(V vertex1, V vertex2) throws NullPointerException {
return (contains(vertex1) && contains(vertex2)) && edges.get(vertex1).get(vertex2) != null;
}
@Override
public Collection vertices() {
return new HashSet<>(edges.keySet());
}
@Override
public Collection> edges() {
Set> allEdges = new HashSet<>();
edges.forEach((source, map) -> map.forEach((destination, weight) -> allEdges.add(new Edge<>(source, destination, weight))));
return allEdges;
}
@Override
public Collection> 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 Collection> getEdgesIn(V vertex) throws NullPointerException, IllegalArgumentException {
checkNullAndExist(vertex);
Collection> collection = new HashSet<>();
edges.forEach((source, edge) -> {
if (edge.get(vertex) != null)
collection.add(new Edge<>(source, vertex, edge.get(vertex)));
});
return collection;
}
@Override
public Collection> getEdgesOut(V vertex) throws NullPointerException, IllegalArgumentException {
checkNullAndExist(vertex);
Collection> collection = new HashSet<>();
edges.get(vertex).forEach((dest, weight) -> collection.add(new Edge<>(vertex, dest, weight)));
return collection;
}
@Override
public Collection getChildren(V vertex) throws NullPointerException, IllegalArgumentException {
checkNullAndExist(vertex);
return new HashSet<>(edges.get(vertex).keySet());
}
@Override
public Collection 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 VisitInfo visit(V source, VisitStrategy strategy, Consumer visit) throws NullPointerException, IllegalArgumentException {
return 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 Collection> 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);
}
}
return null;
};
strategy.visit(this, source, null);
sub.addAllVertices(vertices);
for (V vertex : vertices)
getEdgesOut(vertex).forEach((edge) -> {
try {
sub.addEdge(edge);
} catch (Exception ignored) {
}
});
return sub;
}
@Override
public Graph subGraph(final Object...marker) {
final Graph sub = new MapGraph<>();
final Set allVertices = new HashSet<>();
final Set allMarkers = new HashSet<>();
final boolean isEmpty = (marker == null || marker.length == 0);
if (!isEmpty)
for (Object mark: marker)
allMarkers.add(mark);
markers.forEach( (mark, set) -> {
if (isEmpty || allMarkers.contains(mark))
allVertices.addAll(set);
});
if (isEmpty) {
Collection toAdd = vertices();
toAdd.removeAll(allVertices);
allVertices.clear();
allVertices.addAll(toAdd);
}
sub.addAllVertices(allVertices);
for (V vertex : sub.vertices())
edges.get(vertex).forEach( (dest, weight) -> {
try {
sub.addEdge(vertex, dest, 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); /* Cached */
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()); /* Cached */
}
@Override
public Iterator iterator() {
return edges.keySet().iterator();
}
/**
* Simple function that reset all the caching variables if the graph changed
*/
private void graphChanged() {
tarjan = null;
dijkstra.clear();
}
/**
* Simple function that return the result of the Dijkstra visit, with the starting point as source.
* It also cache it, so multiple call will return always the same value unless the graph has changed.
* @param source the source of the visit
* @return the complete visit
*/
private Dijkstra getDijkstra(V source) {
if (dijkstra.get(source) == null) {
Dijkstra newDijkstra = new Dijkstra<>();
newDijkstra.visit(this, source, null);
dijkstra.put(source, newDijkstra);
}
return dijkstra.get(source);
}
/**
* Simple function that return the result of the Tarjan visit.
* It also cache it, so multiple call will return always the same value unless the graph has changed.
* @return the tarjan visit
*/
private Tarjan getTarjan() {
if (tarjan == null) {
tarjan = new Tarjan<>();
tarjan.visit(this, null, null);
}
return tarjan;
}
/**
* Test if the object passed is null.
* If it is throw an exception.
* @param object the object to test
*/
private void checkNull(Object object) {
if (object == null)
throw new NullPointerException(PARAM_NULL);
}
/**
* Check if the vertex passed is null and if exist in the graph.
* If not then throws eventual exception
* @param vertex the vertex to test
*/
private void checkNullAndExist(V vertex) {
checkNull(vertex);
if (!edges.containsKey(vertex))
throw new IllegalArgumentException(VERTEX_NOT_CONTAINED);
}
}