Switch to Maven project.

This commit is contained in:
Mikaël Capelle
2020-02-23 16:30:06 +01:00
parent fd503d148e
commit 5bb454a3b2
139 changed files with 2487 additions and 2198 deletions

View File

@@ -0,0 +1,87 @@
package org.insa.graphs.algorithm;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
/**
* Base class for algorithm classes.
*
* @param <Observer> Observer type for the algorithm.
*/
public abstract class AbstractAlgorithm<Observer> {
// Input data for the algorithm
protected final AbstractInputData data;
// List of observers for the algorithm
protected final ArrayList<Observer> observers;
/**
* Create a new algorithm with an empty list of observers.
*
* @param data Input data for the algorithm.
*/
protected AbstractAlgorithm(AbstractInputData data) {
this.data = data;
this.observers = new ArrayList<Observer>();
}
/**
* Create a new algorithm with the given list of observers.
*
* @param data Input data for the algorithm.
* @param observers Initial list of observers for the algorithm.
*/
protected AbstractAlgorithm(AbstractInputData data, ArrayList<Observer> observers) {
this.data = data;
this.observers = observers;
}
/**
* Add an observer to this algorithm.
*
* @param observer Observer to add to this algorithm.
*/
public void addObserver(Observer observer) {
observers.add(observer);
}
/**
* @return The list of observers for this algorithm.
*/
public ArrayList<Observer> getObservers() {
return observers;
}
/**
* @return Input for this algorithm.
*/
public AbstractInputData getInputData() {
return data;
}
/**
* Run the algorithm and return the solution.
*
* This methods internally time the call to doRun() and update the result of the
* call with the computed solving time.
*
* @return The solution found by the algorithm (may not be a feasible solution).
*/
public AbstractSolution run() {
Instant start = Instant.now();
AbstractSolution solution = this.doRun();
solution.setSolvingTime(Duration.between(start, Instant.now()));
return solution;
}
/**
* Abstract method that should be implemented by child class.
*
* @return The solution found, must not be null (use an infeasible or unknown
* status if necessary).
*/
protected abstract AbstractSolution doRun();
}

View File

@@ -0,0 +1,97 @@
package org.insa.graphs.algorithm;
import org.insa.graphs.model.Arc;
import org.insa.graphs.model.Graph;
import org.insa.graphs.model.GraphStatistics;
/**
* Base class for algorithm input data classes. This class contains the basic
* data that are required by most graph algorithms, i.e. a graph, a mode (time /
* length) and a filter for the arc.
*
*/
public abstract class AbstractInputData {
/**
* Enum specifying the top mode of the algorithms.
*
* @see ArcInspector
*/
public enum Mode {
TIME, LENGTH
}
// Graph
private final Graph graph;
// Arc filter.
protected final ArcInspector arcInspector;
/**
* Create a new AbstractInputData instance for the given graph, mode and filter.
*
* @param graph Graph for this input data.
* @param arcInspector Arc inspector for this input data.
*/
protected AbstractInputData(Graph graph, ArcInspector arcInspector) {
this.graph = graph;
this.arcInspector = arcInspector;
}
/**
* @return Graph associated with this input.
*/
public Graph getGraph() {
return graph;
}
/**
* Retrieve the cost associated with the given arc according to the underlying
* arc inspector.
*
* @param arc Arc for which cost should be retrieved.
*
* @return Cost for the given arc.
*
* @see ArcInspector
*/
public double getCost(Arc arc) {
return this.arcInspector.getCost(arc);
}
/**
* @return Mode associated with this input data.
*
* @see Mode
*/
public Mode getMode() {
return this.arcInspector.getMode();
}
/**
* Retrieve the maximum speed associated with this input data, or
* {@link GraphStatistics#NO_MAXIMUM_SPEED} if none is associated. The maximum
* speed associated with input data is different from the maximum speed
* associated with graph (accessible via {@link Graph#getGraphInformation()}).
*
* @return The maximum speed for this inspector, or
* {@link GraphStatistics#NO_MAXIMUM_SPEED} if none is set.
*/
public int getMaximumSpeed() {
return this.arcInspector.getMaximumSpeed();
}
/**
* Check if the given arc is allowed for the filter corresponding to this input.
*
* @param arc Arc to check.
*
* @return true if the given arc is allowed.
*
* @see ArcInspector
*/
public boolean isAllowed(Arc arc) {
return this.arcInspector.isAllowed(arc);
}
}

View File

@@ -0,0 +1,88 @@
package org.insa.graphs.algorithm;
import java.time.Duration;
/**
* Base class for solution classes returned by the algorithm. This class
* contains the basic information that any solution should have: status of the
* solution (unknown, infeasible, etc.), solving time and the original input
* data.
*/
public abstract class AbstractSolution {
/**
* Possible status for a solution.
*
*/
public enum Status {
UNKNOWN, INFEASIBLE, FEASIBLE, OPTIMAL,
};
// Status of the solution.
private final Status status;
// Solving time for the solution.
private Duration solvingTime;
// Original input of the solution.
private final AbstractInputData data;
/**
* Create a new abstract solution with unknown status.
*
* @param data
*/
protected AbstractSolution(AbstractInputData data) {
this.data = data;
this.solvingTime = Duration.ZERO;
this.status = Status.UNKNOWN;
}
/**
*
* @param data
* @param status
*/
protected AbstractSolution(AbstractInputData data, Status status) {
this.data = data;
this.status = status;
}
/**
* @return Original input for this solution.
*/
public AbstractInputData getInputData() {
return data;
}
/**
* @return Status of this solution.
*/
public Status getStatus() {
return status;
}
/**
* @return Solving time of this solution.
*/
public Duration getSolvingTime() {
return solvingTime;
}
/**
* Set the solving time of this solution.
*
* @param solvingTime Solving time for the solution.
*/
protected void setSolvingTime(Duration solvingTime) {
this.solvingTime = solvingTime;
}
/**
* @return true if the solution is feasible or optimal.
*/
public boolean isFeasible() {
return status == Status.FEASIBLE || status == Status.OPTIMAL;
}
}

View File

@@ -0,0 +1,128 @@
package org.insa.graphs.algorithm;
import java.lang.reflect.Constructor;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.insa.graphs.algorithm.shortestpath.AStarAlgorithm;
import org.insa.graphs.algorithm.shortestpath.BellmanFordAlgorithm;
import org.insa.graphs.algorithm.shortestpath.DijkstraAlgorithm;
import org.insa.graphs.algorithm.shortestpath.ShortestPathAlgorithm;
import org.insa.graphs.algorithm.weakconnectivity.WeaklyConnectedComponentsAlgorithm;
/**
* Factory class used to register and retrieve algorithms based on their common
* ancestor and name.
*
*/
public class AlgorithmFactory {
// Map between algorithm names and class.
private final static Map<Class<? extends AbstractAlgorithm<?>>, Map<String, Class<? extends AbstractAlgorithm<?>>>> ALGORITHMS = new IdentityHashMap<>();
static {
// Register weakly-connected components algorithm:
registerAlgorithm(WeaklyConnectedComponentsAlgorithm.class, "WCC basic",
WeaklyConnectedComponentsAlgorithm.class);
// Register shortest path algorithm:
registerAlgorithm(ShortestPathAlgorithm.class, "Bellman-Ford", BellmanFordAlgorithm.class);
registerAlgorithm(ShortestPathAlgorithm.class, "Dijkstra", DijkstraAlgorithm.class);
registerAlgorithm(ShortestPathAlgorithm.class, "A*", AStarAlgorithm.class);
// Register your algorithms here:
// registerAlgorithm(CarPoolingAlgorithm.class, "My Awesome Algorithm",
// MyCarPoolingAlgorithm.class);
}
/**
* Register the given algorithm class with the given name as a child class of
* the given base algorithm.
*
* @param baseAlgorithm Base algorithm class that corresponds to the newly
* registered algorithm class (e.g., generic algorithm
* class for the problem).
* @param name Name for the registered algorithm class.
* @param algoClass Algorithm class to register.
*/
public static void registerAlgorithm(Class<? extends AbstractAlgorithm<?>> baseAlgorithm,
String name, Class<? extends AbstractAlgorithm<?>> algoClass) {
if (!ALGORITHMS.containsKey(baseAlgorithm)) {
ALGORITHMS.put(baseAlgorithm, new LinkedHashMap<>());
}
ALGORITHMS.get(baseAlgorithm).put(name, algoClass);
}
/**
* Create an instance of the given algorithm class using the given input data.
* Assuming algorithm correspond to a class "Algorithm", this function returns
* an object equivalent to `new Algorithm(data)`.
*
* @param algorithm Class of the algorithm to create.
* @param data Input data for the algorithm.
*
* @return A new instance of the given algorithm class using the given data.
*
* @throws Exception if something wrong happens when constructing the object,
* i.e. the given input data does not correspond to the given
* algorithm and/or no constructor that takes a single
* parameter of type (data.getClass()) exists.
*/
public static AbstractAlgorithm<?> createAlgorithm(
Class<? extends AbstractAlgorithm<?>> algorithm, AbstractInputData data)
throws Exception {
// Retrieve the set of constructors for the given algorithm class.
Constructor<?>[] constructors = algorithm.getDeclaredConstructors();
// Within this set, find the constructor that can be called with "data" (only).
AbstractAlgorithm<?> constructed = null;
for (Constructor<?> c: constructors) {
Class<?>[] params = c.getParameterTypes();
if (params.length == 1 && params[0].isAssignableFrom(data.getClass())) {
c.setAccessible(true);
constructed = (AbstractAlgorithm<?>) c.newInstance(new Object[] { data });
break;
}
}
return constructed;
}
/**
* Return the algorithm class corresponding to the given base algorithm class
* and name. The algorithm must have been previously registered using
* registerAlgorithm.
*
* @param baseAlgorithm Base algorithm class for the algorithm to retrieve.
* @param name Name of the algorithm to retrieve.
*
* @return Class corresponding to the given name.
*
* @see #registerAlgorithm
*/
public static Class<? extends AbstractAlgorithm<?>> getAlgorithmClass(
Class<? extends AbstractAlgorithm<?>> baseAlgorithm, String name) {
return ALGORITHMS.get(baseAlgorithm).get(name);
}
/**
* Return the list of names corresponding to the registered algorithm classes
* for the given base algorithm class.
*
* @param baseAlgorithm Base algorithm class for the algorithm class names to
* retrieve.
*
* @return Names of the currently registered algorithms.
*
* @see #registerAlgorithm
*/
public static Set<String> getAlgorithmNames(
Class<? extends AbstractAlgorithm<?>> baseAlgorithm) {
if (!ALGORITHMS.containsKey(baseAlgorithm)) {
return new TreeSet<>();
}
return ALGORITHMS.get(baseAlgorithm).keySet();
}
}

View File

@@ -0,0 +1,43 @@
package org.insa.graphs.algorithm;
import org.insa.graphs.algorithm.AbstractInputData.Mode;
import org.insa.graphs.model.Arc;
import org.insa.graphs.model.GraphStatistics;
/**
* This class can be used to indicate to an algorithm which arcs can be used and
* the costs of the usable arcs..
*
*/
public interface ArcInspector {
/**
* Check if the given arc can be used (is allowed).
*
* @param arc Arc to check.
*
* @return true if the given arc is allowed.
*/
public boolean isAllowed(Arc arc);
/**
* Find the cost of the given arc.
*
* @param arc Arc for which the cost should be returned.
*
* @return Cost of the arc.
*/
public double getCost(Arc arc);
/**
* @return The maximum speed for this inspector, or
* {@link GraphStatistics#NO_MAXIMUM_SPEED} if none is set.
*/
public int getMaximumSpeed();
/**
* @return Mode for this arc inspector.
*/
public Mode getMode();
}

View File

@@ -0,0 +1,177 @@
package org.insa.graphs.algorithm;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import org.insa.graphs.algorithm.AbstractInputData.Mode;
import org.insa.graphs.model.Arc;
import org.insa.graphs.model.GraphStatistics;
import org.insa.graphs.model.AccessRestrictions.AccessMode;
import org.insa.graphs.model.AccessRestrictions.AccessRestriction;
public class ArcInspectorFactory {
/**
* @return List of all arc filters in this factory.
*/
public static List<ArcInspector> getAllFilters() {
List<ArcInspector> filters = new ArrayList<>();
// Common filters:
// No filter (all arcs allowed):
filters.add(new ArcInspector() {
@Override
public boolean isAllowed(Arc arc) {
return true;
}
@Override
public double getCost(Arc arc) {
return arc.getLength();
}
@Override
public int getMaximumSpeed() {
return GraphStatistics.NO_MAXIMUM_SPEED;
}
@Override
public Mode getMode() {
return Mode.LENGTH;
}
@Override
public String toString() {
return "Shortest path, all roads allowed";
}
});
// Only road allowed for cars and length:
filters.add(new ArcInspector() {
@Override
public boolean isAllowed(Arc arc) {
return arc.getRoadInformation().getAccessRestrictions()
.isAllowedForAny(AccessMode.MOTORCAR, EnumSet.complementOf(EnumSet
.of(AccessRestriction.FORBIDDEN, AccessRestriction.PRIVATE)));
}
@Override
public double getCost(Arc arc) {
return arc.getLength();
}
@Override
public int getMaximumSpeed() {
return GraphStatistics.NO_MAXIMUM_SPEED;
}
@Override
public Mode getMode() {
return Mode.LENGTH;
}
@Override
public String toString() {
return "Shortest path, only roads open for cars";
}
});
// Only road allowed for cars and time:
filters.add(new ArcInspector() {
@Override
public boolean isAllowed(Arc arc) {
return true;
}
@Override
public double getCost(Arc arc) {
return arc.getMinimumTravelTime();
}
@Override
public int getMaximumSpeed() {
return GraphStatistics.NO_MAXIMUM_SPEED;
}
@Override
public Mode getMode() {
return Mode.TIME;
}
@Override
public String toString() {
return "Fastest path, all roads allowed";
}
});
filters.add(new ArcInspector() {
@Override
public boolean isAllowed(Arc arc) {
return arc.getRoadInformation().getAccessRestrictions()
.isAllowedForAny(AccessMode.MOTORCAR, EnumSet.complementOf(EnumSet
.of(AccessRestriction.FORBIDDEN, AccessRestriction.PRIVATE)));
}
@Override
public double getCost(Arc arc) {
return arc.getMinimumTravelTime();
}
@Override
public int getMaximumSpeed() {
return GraphStatistics.NO_MAXIMUM_SPEED;
}
@Override
public Mode getMode() {
return Mode.TIME;
}
@Override
public String toString() {
return "Fastest path, only roads open for cars";
}
});
// Non-private roads for pedestrian and bicycle:
filters.add(new ArcInspector() {
@Override
public boolean isAllowed(Arc arc) {
return arc.getRoadInformation().getAccessRestrictions()
.isAllowedForAny(AccessMode.FOOT, EnumSet.complementOf(EnumSet
.of(AccessRestriction.FORBIDDEN, AccessRestriction.PRIVATE)));
}
@Override
public double getCost(Arc arc) {
return arc.getTravelTime(
Math.min(getMaximumSpeed(), arc.getRoadInformation().getMaximumSpeed()));
}
@Override
public String toString() {
return "Fastest path for pedestrian";
}
@Override
public int getMaximumSpeed() {
return 5;
}
@Override
public Mode getMode() {
return Mode.TIME;
}
});
// Add your own filters here (do not forget to implement toString()
// to get an understandable output!):
return filters;
}
}

View File

@@ -0,0 +1,24 @@
package org.insa.graphs.algorithm.carpooling;
import org.insa.graphs.algorithm.AbstractAlgorithm;
public abstract class CarPoolingAlgorithm extends AbstractAlgorithm<CarPoolingObserver> {
protected CarPoolingAlgorithm(CarPoolingData data) {
super(data);
}
@Override
public CarPoolingSolution run() {
return (CarPoolingSolution) super.run();
}
@Override
protected abstract CarPoolingSolution doRun();
@Override
public CarPoolingData getInputData() {
return (CarPoolingData) super.getInputData();
}
}

View File

@@ -0,0 +1,13 @@
package org.insa.graphs.algorithm.carpooling;
import org.insa.graphs.algorithm.AbstractInputData;
import org.insa.graphs.algorithm.ArcInspector;
import org.insa.graphs.model.Graph;
public class CarPoolingData extends AbstractInputData {
protected CarPoolingData(Graph graph, ArcInspector arcFilter) {
super(graph, arcFilter);
}
}

View File

@@ -0,0 +1,5 @@
package org.insa.graphs.algorithm.carpooling;
public class CarPoolingGraphicObserver implements CarPoolingObserver {
}

View File

@@ -0,0 +1,5 @@
package org.insa.graphs.algorithm.carpooling;
public interface CarPoolingObserver {
}

View File

@@ -0,0 +1,11 @@
package org.insa.graphs.algorithm.carpooling;
import org.insa.graphs.algorithm.AbstractSolution;
public class CarPoolingSolution extends AbstractSolution {
protected CarPoolingSolution(CarPoolingData data, Status status) {
super(data, status);
}
}

View File

@@ -0,0 +1,5 @@
package org.insa.graphs.algorithm.carpooling;
public class CarPoolingTextObserver implements CarPoolingObserver {
}

View File

@@ -0,0 +1,29 @@
package org.insa.graphs.algorithm.packageswitch;
import org.insa.graphs.algorithm.AbstractAlgorithm;
public abstract class PackageSwitchAlgorithm extends AbstractAlgorithm<PackageSwitchObserver> {
/**
* Create a new PackageSwitchAlgorithm with the given data.
*
* @param data
*/
protected PackageSwitchAlgorithm(PackageSwitchData data) {
super(data);
}
@Override
public PackageSwitchSolution run() {
return (PackageSwitchSolution) super.run();
}
@Override
protected abstract PackageSwitchSolution doRun();
@Override
public PackageSwitchData getInputData() {
return (PackageSwitchData) super.getInputData();
}
}

View File

@@ -0,0 +1,13 @@
package org.insa.graphs.algorithm.packageswitch;
import org.insa.graphs.algorithm.AbstractInputData;
import org.insa.graphs.algorithm.ArcInspector;
import org.insa.graphs.model.Graph;
public class PackageSwitchData extends AbstractInputData {
protected PackageSwitchData(Graph graph, ArcInspector arcFilter) {
super(graph, arcFilter);
}
}

View File

@@ -0,0 +1,5 @@
package org.insa.graphs.algorithm.packageswitch;
public class PackageSwitchGraphicObserver implements PackageSwitchObserver {
}

View File

@@ -0,0 +1,5 @@
package org.insa.graphs.algorithm.packageswitch;
public interface PackageSwitchObserver {
}

View File

@@ -0,0 +1,11 @@
package org.insa.graphs.algorithm.packageswitch;
import org.insa.graphs.algorithm.AbstractSolution;
public class PackageSwitchSolution extends AbstractSolution {
protected PackageSwitchSolution(PackageSwitchData data, Status status) {
super(data, status);
}
}

View File

@@ -0,0 +1,5 @@
package org.insa.graphs.algorithm.packageswitch;
public class PackageSwitchTextObserver implements PackageSwitchObserver {
}

View File

@@ -0,0 +1,9 @@
package org.insa.graphs.algorithm.shortestpath;
public class AStarAlgorithm extends DijkstraAlgorithm {
public AStarAlgorithm(ShortestPathData data) {
super(data);
}
}

View File

@@ -0,0 +1,100 @@
package org.insa.graphs.algorithm.shortestpath;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import org.insa.graphs.algorithm.AbstractSolution.Status;
import org.insa.graphs.model.Arc;
import org.insa.graphs.model.Graph;
import org.insa.graphs.model.Node;
import org.insa.graphs.model.Path;
public class BellmanFordAlgorithm extends ShortestPathAlgorithm {
public BellmanFordAlgorithm(ShortestPathData data) {
super(data);
}
@Override
protected ShortestPathSolution doRun() {
// Retrieve the graph.
ShortestPathData data = getInputData();
Graph graph = data.getGraph();
final int nbNodes = graph.size();
// Initialize array of distances.
double[] distances = new double[nbNodes];
Arrays.fill(distances, Double.POSITIVE_INFINITY);
distances[data.getOrigin().getId()] = 0;
// Notify observers about the first event (origin processed).
notifyOriginProcessed(data.getOrigin());
// Initialize array of predecessors.
Arc[] predecessorArcs = new Arc[nbNodes];
// Actual algorithm, we will assume the graph does not contain negative
// cycle...
boolean found = false;
for (int i = 0; !found && i < nbNodes; ++i) {
found = true;
for (Node node: graph.getNodes()) {
for (Arc arc: node.getSuccessors()) {
// Small test to check allowed roads...
if (!data.isAllowed(arc)) {
continue;
}
// Retrieve weight of the arc.
double w = data.getCost(arc);
double oldDistance = distances[arc.getDestination().getId()];
double newDistance = distances[node.getId()] + w;
if (Double.isInfinite(oldDistance) && Double.isFinite(newDistance)) {
notifyNodeReached(arc.getDestination());
}
// Check if new distances would be better, if so update...
if (newDistance < oldDistance) {
found = false;
distances[arc.getDestination().getId()] = distances[node.getId()] + w;
predecessorArcs[arc.getDestination().getId()] = arc;
}
}
}
}
ShortestPathSolution solution = null;
// Destination has no predecessor, the solution is infeasible...
if (predecessorArcs[data.getDestination().getId()] == null) {
solution = new ShortestPathSolution(data, Status.INFEASIBLE);
}
else {
// The destination has been found, notify the observers.
notifyDestinationReached(data.getDestination());
// Create the path from the array of predecessors...
ArrayList<Arc> arcs = new ArrayList<>();
Arc arc = predecessorArcs[data.getDestination().getId()];
while (arc != null) {
arcs.add(arc);
arc = predecessorArcs[arc.getOrigin().getId()];
}
// Reverse the path...
Collections.reverse(arcs);
// Create the final solution.
solution = new ShortestPathSolution(data, Status.OPTIMAL, new Path(graph, arcs));
}
return solution;
}
}

View File

@@ -0,0 +1,17 @@
package org.insa.graphs.algorithm.shortestpath;
public class DijkstraAlgorithm extends ShortestPathAlgorithm {
public DijkstraAlgorithm(ShortestPathData data) {
super(data);
}
@Override
protected ShortestPathSolution doRun() {
final ShortestPathData data = getInputData();
ShortestPathSolution solution = null;
// TODO:
return solution;
}
}

View File

@@ -0,0 +1,69 @@
package org.insa.graphs.algorithm.shortestpath;
import org.insa.graphs.algorithm.AbstractAlgorithm;
import org.insa.graphs.model.Node;
public abstract class ShortestPathAlgorithm extends AbstractAlgorithm<ShortestPathObserver> {
protected ShortestPathAlgorithm(ShortestPathData data) {
super(data);
}
@Override
public ShortestPathSolution run() {
return (ShortestPathSolution) super.run();
}
@Override
protected abstract ShortestPathSolution doRun();
@Override
public ShortestPathData getInputData() {
return (ShortestPathData) super.getInputData();
}
/**
* Notify all observers that the origin has been processed.
*
* @param node Origin.
*/
public void notifyOriginProcessed(Node node) {
for (ShortestPathObserver obs: getObservers()) {
obs.notifyOriginProcessed(node);
}
}
/**
* Notify all observers that a node has been reached for the first time.
*
* @param node Node that has been reached.
*/
public void notifyNodeReached(Node node) {
for (ShortestPathObserver obs: getObservers()) {
obs.notifyNodeReached(node);
}
}
/**
* Notify all observers that a node has been marked, i.e. its final value has
* been set.
*
* @param node Node that has been marked.
*/
public void notifyNodeMarked(Node node) {
for (ShortestPathObserver obs: getObservers()) {
obs.notifyNodeMarked(node);
}
}
/**
* Notify all observers that the destination has been reached.
*
* @param node Destination.
*/
public void notifyDestinationReached(Node node) {
for (ShortestPathObserver obs: getObservers()) {
obs.notifyDestinationReached(node);
}
}
}

View File

@@ -0,0 +1,47 @@
package org.insa.graphs.algorithm.shortestpath;
import org.insa.graphs.algorithm.AbstractInputData;
import org.insa.graphs.algorithm.ArcInspector;
import org.insa.graphs.model.Graph;
import org.insa.graphs.model.Node;
public class ShortestPathData extends AbstractInputData {
// Origin and destination nodes.
private final Node origin, destination;
/**
* Construct a new instance of ShortestPathInputData with the given parameters.
*
* @param graph Graph in which the path should be looked for.
* @param origin Origin node of the path.
* @param destination Destination node of the path.
* @param arcInspector Filter for arcs (used to allow only a specific set of
* arcs in the graph to be used).
*/
public ShortestPathData(Graph graph, Node origin, Node destination, ArcInspector arcInspector) {
super(graph, arcInspector);
this.origin = origin;
this.destination = destination;
}
/**
* @return Origin node for the path.
*/
public Node getOrigin() {
return origin;
}
/**
* @return Destination node for the path.
*/
public Node getDestination() {
return destination;
}
@Override
public String toString() {
return "Shortest-path from #" + origin.getId() + " to #" + destination.getId() + " ["
+ this.arcInspector.toString().toLowerCase() + "]";
}
}

View File

@@ -0,0 +1,37 @@
package org.insa.graphs.algorithm.shortestpath;
import org.insa.graphs.model.Node;
public interface ShortestPathObserver {
/**
* Notify the observer that the origin has been processed.
*
* @param node Origin.
*/
public void notifyOriginProcessed(Node node);
/**
* Notify the observer that a node has been reached for the first
* time.
*
* @param node Node that has been reached.
*/
public void notifyNodeReached(Node node);
/**
* Notify the observer that a node has been marked, i.e. its final
* value has been set.
*
* @param node Node that has been marked.
*/
public void notifyNodeMarked(Node node);
/**
* Notify the observer that the destination has been reached.
*
* @param node Destination.
*/
public void notifyDestinationReached(Node node);
}

View File

@@ -0,0 +1,74 @@
package org.insa.graphs.algorithm.shortestpath;
import org.insa.graphs.algorithm.AbstractInputData.Mode;
import org.insa.graphs.model.Arc;
import org.insa.graphs.model.Path;
import org.insa.graphs.algorithm.AbstractSolution;
public class ShortestPathSolution extends AbstractSolution {
// Optimal solution.
private final Path path;
/**
* Create a new infeasible shortest-path solution for the given input and
* status.
*
* @param data Original input data for this solution.
* @param status Status of the solution (UNKNOWN / INFEASIBLE).
*/
public ShortestPathSolution(ShortestPathData data, Status status) {
super(data, status);
this.path = null;
}
/**
* Create a new shortest-path solution.
*
* @param data Original input data for this solution.
* @param status Status of the solution (FEASIBLE / OPTIMAL).
* @param path Path corresponding to the solution.
*/
public ShortestPathSolution(ShortestPathData data, Status status, Path path) {
super(data, status);
this.path = path;
}
@Override
public ShortestPathData getInputData() {
return (ShortestPathData) super.getInputData();
}
/**
* @return The path of this solution, if any.
*/
public Path getPath() {
return path;
}
@Override
public String toString() {
String info = null;
if (!isFeasible()) {
info = String.format("No path found from node #%d to node #%d",
getInputData().getOrigin().getId(), getInputData().getDestination().getId());
}
else {
double cost = 0;
for (Arc arc: getPath().getArcs()) {
cost += getInputData().getCost(arc);
}
info = String.format("Found a path from node #%d to node #%d",
getInputData().getOrigin().getId(), getInputData().getDestination().getId());
if (getInputData().getMode() == Mode.LENGTH) {
info = String.format("%s, %.4f kilometers", info, cost / 1000.0);
}
else {
info = String.format("%s, %.4f minutes", info, cost / 60.0);
}
}
info += " in " + getSolvingTime().getSeconds() + " seconds.";
return info;
}
}

View File

@@ -0,0 +1,37 @@
package org.insa.graphs.algorithm.shortestpath;
import java.io.PrintStream;
import org.insa.graphs.model.Node;
public class ShortestPathTextObserver implements ShortestPathObserver {
private final PrintStream stream;
public ShortestPathTextObserver(PrintStream stream) {
this.stream = stream;
}
@Override
public void notifyOriginProcessed(Node node) {
// TODO Auto-generated method stub
}
@Override
public void notifyNodeReached(Node node) {
stream.println("Node " + node.getId() + " reached.");
}
@Override
public void notifyNodeMarked(Node node) {
stream.println("Node " + node.getId() + " marked.");
}
@Override
public void notifyDestinationReached(Node node) {
// TODO Auto-generated method stub
}
}

View File

@@ -0,0 +1,205 @@
package org.insa.graphs.algorithm.utils;
import java.util.ArrayList;
/**
* Implements a binary heap containing elements of type E.
*
* Note that all comparisons are based on the compareTo method, hence E must
* implement Comparable
*
* @author Mark Allen Weiss
* @author DLB
*/
public class BinaryHeap<E extends Comparable<E>> implements PriorityQueue<E> {
// Number of elements in heap.
private int currentSize;
// The heap array.
protected final ArrayList<E> array;
/**
* Construct a new empty binary heap.
*/
public BinaryHeap() {
this.currentSize = 0;
this.array = new ArrayList<E>();
}
/**
* Construct a copy of the given heap.
*
* @param heap Binary heap to copy.
*/
public BinaryHeap(BinaryHeap<E> heap) {
this.currentSize = heap.currentSize;
this.array = new ArrayList<E>(heap.array);
}
/**
* Set an element at the given index.
*
* @param index Index at which the element should be set.
* @param value Element to set.
*/
private void arraySet(int index, E value) {
if (index == this.array.size()) {
this.array.add(value);
}
else {
this.array.set(index, value);
}
}
/**
* @return Index of the parent of the given index.
*/
protected int indexParent(int index) {
return (index - 1) / 2;
}
/**
* @return Index of the left child of the given index.
*/
protected int indexLeft(int index) {
return index * 2 + 1;
}
/**
* Internal method to percolate up in the heap.
*
* @param index Index at which the percolate begins.
*/
private void percolateUp(int index) {
E x = this.array.get(index);
for (; index > 0
&& x.compareTo(this.array.get(indexParent(index))) < 0; index = indexParent(
index)) {
E moving_val = this.array.get(indexParent(index));
this.arraySet(index, moving_val);
}
this.arraySet(index, x);
}
/**
* Internal method to percolate down in the heap.
*
* @param index Index at which the percolate begins.
*/
private void percolateDown(int index) {
int ileft = indexLeft(index);
int iright = ileft + 1;
if (ileft < this.currentSize) {
E current = this.array.get(index);
E left = this.array.get(ileft);
boolean hasRight = iright < this.currentSize;
E right = (hasRight) ? this.array.get(iright) : null;
if (!hasRight || left.compareTo(right) < 0) {
// Left is smaller
if (left.compareTo(current) < 0) {
this.arraySet(index, left);
this.arraySet(ileft, current);
this.percolateDown(ileft);
}
}
else {
// Right is smaller
if (right.compareTo(current) < 0) {
this.arraySet(index, right);
this.arraySet(iright, current);
this.percolateDown(iright);
}
}
}
}
@Override
public boolean isEmpty() {
return this.currentSize == 0;
}
@Override
public int size() {
return this.currentSize;
}
@Override
public void insert(E x) {
int index = this.currentSize++;
this.arraySet(index, x);
this.percolateUp(index);
}
@Override
public void remove(E x) throws ElementNotFoundException {
// TODO:
}
@Override
public E findMin() throws EmptyPriorityQueueException {
if (isEmpty())
throw new EmptyPriorityQueueException();
return this.array.get(0);
}
@Override
public E deleteMin() throws EmptyPriorityQueueException {
E minItem = findMin();
E lastItem = this.array.get(--this.currentSize);
this.arraySet(0, lastItem);
this.percolateDown(0);
return minItem;
}
/**
* Creates a multi-lines string representing a sorted view of this binary heap.
*
* @return a string containing a sorted view this binary heap.
*/
public String toStringSorted() {
return BinaryHeapFormatter.toStringSorted(this, -1);
}
/**
* Creates a multi-lines string representing a sorted view of this binary heap.
*
* @param maxElement Maximum number of elements to display. or {@code -1} to
* display all the elements.
*
* @return a string containing a sorted view this binary heap.
*/
public String toStringSorted(int maxElement) {
return BinaryHeapFormatter.toStringSorted(this, maxElement);
}
/**
* Creates a multi-lines string representing a tree view of this binary heap.
*
* @return a string containing a tree view of this binary heap.
*/
public String toStringTree() {
return BinaryHeapFormatter.toStringTree(this, Integer.MAX_VALUE);
}
/**
* Creates a multi-lines string representing a tree view of this binary heap.
*
* @param maxDepth Maximum depth of the tree to display.
*
* @return a string containing a tree view of this binary heap.
*/
public String toStringTree(int maxDepth) {
return BinaryHeapFormatter.toStringTree(this, maxDepth);
}
@Override
public String toString() {
return BinaryHeapFormatter.toStringTree(this, 8);
}
}

View File

@@ -0,0 +1,198 @@
package org.insa.graphs.algorithm.utils;
import java.util.ArrayList;
public class BinaryHeapFormatter {
/**
* This class is used by {@link #toStringTree}, and simply contains three string
* accumulating. This is an immutable class.
*
*/
private static class Context {
// Output text:
public final String acu;
// Margin to get back exactly under the current position:
public final String margin;
// Last margin used for the last child of a node. The markers are different:
public final String lastmargin;
/**
* Creaet a new {@code Context}.
*
* @param acu The accumulated string.
* @param margin The current margin.
* @param lastMargin The last margin used.
*/
public Context(String acu, String margin, String lastMargin) {
this.acu = acu;
this.margin = margin;
this.lastmargin = lastMargin;
}
/**
* Creates a new context by appending newlines to this context.
*
* @param n Number of newlines to append.
*
* @return a new context with {@code n} newlines appended.
*/
public Context appendNewlines(int n) {
if (n <= 0) {
return this;
}
else {
return (new Context(this.acu + "\n" + this.margin, this.margin, this.lastmargin)
.appendNewlines(n - 1));
}
}
/**
* Creates a new context by appending the given string to this context.
*
* @param count Number of spaces to add to the margin, or {@code null} to use
* the length of the string.
* @param text String to append.
*
* @return a new context with {@code text} appended.
*/
public Context appendText(Integer count, String text) {
int cnt = (count == null) ? text.length() : count;
final String spaces = new String(new char[cnt]).replace('\0', ' ');
return new Context(this.acu + text, this.margin + spaces, this.lastmargin + spaces);
}
/**
* Creates a new context by appending a branch to this context.
*
* @param n Number of spaces to add to the margin, or {@code null} to use
* the length of the string.
* @param label Name of the branch.
*
* @return a new context with the branch appended.
*/
public Context appendBranch(Integer count, String label) {
final Context ctxt = this.appendText(count, label);
if (count == null) {
return new Context(ctxt.acu + "_", ctxt.margin + "|", ctxt.margin + " ");
}
else {
return new Context(ctxt.acu, ctxt.margin + "|", ctxt.margin + " ")
.appendNewlines(1);
}
}
}
/*
* Input : ready to write the current node at the current context position.
* Output : the last character of acu is the last character of the current node.
*/
protected static <E extends Comparable<E>> Context toStringLoop(BinaryHeap<E> heap,
Context ctxt, int node, int max_depth) {
if (max_depth < 0) {
return ctxt.appendText(null, "...");
}
else {
E nodeval = heap.array.get(node);
String nodevals = nodeval.toString();
ArrayList<Integer> childs = new ArrayList<Integer>();
// Add childs
int index_left = heap.indexLeft(node);
int index_right = index_left + 1;
if (index_left < heap.size()) {
childs.add(index_left);
}
if (index_right < heap.size()) {
childs.add(index_right);
}
Context ctxt2 = childs.isEmpty() ? ctxt.appendText(null, nodevals)
: ctxt.appendBranch(1, nodevals);
for (int ch = 0; ch < childs.size(); ch++) {
boolean is_last = (ch == childs.size() - 1);
int child = childs.get(ch);
if (is_last) {
Context ctxt3 = new Context(ctxt2.acu, ctxt2.lastmargin, ctxt2.lastmargin);
ctxt2 = new Context(toStringLoop(heap, ctxt3.appendText(null, "___"), child,
max_depth - 1).acu, ctxt2.margin, ctxt2.lastmargin);
}
else {
ctxt2 = new Context(toStringLoop(heap, ctxt2.appendText(null, "___"), child,
max_depth - 1).acu, ctxt2.margin, ctxt2.lastmargin).appendNewlines(2);
}
}
return ctxt2;
}
}
/**
* Creates a multi-lines string representing a tree view of the given binary
* heap.
*
* @param heap The binary heap to display.
* @param maxDepth Maximum depth of the tree to display.
*
* @return a string containing a tree view of the given binary heap.
*/
public static <E extends Comparable<E>> String toStringTree(BinaryHeap<E> heap, int maxDepth) {
final Context init_context = new Context(" ", " ", " ");
final Context result = toStringLoop(heap, init_context, 0, maxDepth);
return result.acu;
}
/**
* Creates a multi-lines string representing a sorted view of the given binary
* heap.
*
* @param heap The binary heap to display.
* @param maxElement Maximum number of elements to display. or {@code -1} to
* display all the elements.
*
* @return a string containing a sorted view the given binary heap.
*/
public static <E extends Comparable<E>> String toStringSorted(BinaryHeap<E> heap,
int max_elements) {
String result = "";
final BinaryHeap<E> copy = new BinaryHeap<E>(heap);
final String truncate;
if (max_elements < 0 || max_elements >= heap.size()) {
truncate = "";
}
else {
truncate = ", only " + max_elements + " elements are shown";
}
result += "======== Sorted HEAP (size = " + heap.size() + truncate + ") ========\n\n";
while (!copy.isEmpty() && max_elements-- != 0) {
result += copy.deleteMin() + "\n";
}
result += "\n-------- End of heap --------";
return result;
}
public static void main(String[] args) {
final BinaryHeap<Integer> heap = new BinaryHeap<Integer>();
for (int i = 0; i < 12; i++) {
heap.insert(i);
}
System.out.println(heap.toStringSorted(-1));
System.out.println(heap.toStringTree(6));
}
}

View File

@@ -0,0 +1,64 @@
package org.insa.graphs.algorithm.utils;
import java.util.SortedSet;
import java.util.TreeSet;
public class BinarySearchTree<E extends Comparable<E>> implements PriorityQueue<E> {
// Underlying implementation
private final SortedSet<E> sortedSet;
/**
* Create a new empty binary search tree.
*/
public BinarySearchTree() {
this.sortedSet = new TreeSet<>();
}
/**
* Create a copy of the given binary search tree.
*
* @param bst Binary search tree to copy.
*/
public BinarySearchTree(BinarySearchTree<E> bst) {
this.sortedSet = new TreeSet<>(bst.sortedSet);
}
@Override
public boolean isEmpty() {
return sortedSet.isEmpty();
}
@Override
public int size() {
return sortedSet.size();
}
@Override
public void insert(E x) {
sortedSet.add(x);
}
@Override
public void remove(E x) throws ElementNotFoundException {
if (!sortedSet.remove(x)) {
throw new ElementNotFoundException(x);
}
}
@Override
public E findMin() throws EmptyPriorityQueueException {
if (isEmpty()) {
throw new EmptyPriorityQueueException();
}
return sortedSet.first();
}
@Override
public E deleteMin() throws EmptyPriorityQueueException {
E min = findMin();
remove(min);
return min;
}
}

View File

@@ -0,0 +1,32 @@
package org.insa.graphs.algorithm.utils;
public class ElementNotFoundException extends RuntimeException {
/**
*
*/
private static final long serialVersionUID = 1L;
// Element not found
private final Object element;
/**
* @param element Element that was not found.
*/
public ElementNotFoundException(Object element) {
this.element = element;
}
/**
* @return The element that was not found.
*/
public Object getElement() {
return this.element;
}
@Override
public String toString() {
return "element not found: " + element;
}
}

View File

@@ -0,0 +1,16 @@
package org.insa.graphs.algorithm.utils;
public class EmptyPriorityQueueException extends RuntimeException {
/**
*
*/
private static final long serialVersionUID = 1L;
/**
*
*/
public EmptyPriorityQueueException() {
}
}

View File

@@ -0,0 +1,81 @@
package org.insa.graphs.algorithm.utils;
/**
* Interface representing a basic priority queue.
*
* Implementation should enforce the required complexity of each method.
*
*/
public interface PriorityQueue<E extends Comparable<E>> {
/**
* Check if the priority queue is empty.
*
* <p>
* <b>Complexity:</b> <i>O(1)</i>
* </p>
*
* @return true if the queue is empty, false otherwise.
*/
public boolean isEmpty();
/**
* Get the number of elements in this queue.
*
* <p>
* <b>Complexity:</b> <i>O(1)</i>
* </p>
*
* @return Current size (number of elements) of this queue.
*/
public int size();
/**
* Insert the given element into the queue.
*
* <p>
* <b>Complexity:</b> <i>O(log n)</i>
* </p>
*
* @param x Item to insert.
*/
public void insert(E x);
/**
* Remove the given element from the priority queue.
*
* <p>
* <b>Complexity:</b> <i>O(log n)</i>
* </p>
*
* @param x Item to remove.
*/
public void remove(E x) throws ElementNotFoundException;
/**
* Retrieve (but not remove) the smallest item in the queue.
*
* <p>
* <b>Complexity:</b> <i>O(1)</i>
* </p>
*
* @return The smallest item in the queue.
*
* @throws EmptyPriorityQueueException if this queue is empty.
*/
public E findMin() throws EmptyPriorityQueueException;
/**
* Remove and return the smallest item from the priority queue.
*
* <p>
* <b>Complexity:</b> <i>O(log n)</i>
* </p>
*
* @return The smallest item in the queue.
*
* @throws EmptyPriorityQueueException if this queue is empty.
*/
public E deleteMin() throws EmptyPriorityQueueException;
}

View File

@@ -0,0 +1,30 @@
package org.insa.graphs.algorithm.weakconnectivity;
import java.util.ArrayList;
import org.insa.graphs.model.Node;
public interface WeaklyConnectedComponentObserver {
/**
* Notify that the algorithm is entering a new component.
*
* @param curNode Starting node for the component.
*/
public void notifyStartComponent(Node curNode);
/**
* Notify that a new node has been found for the current component.
*
* @param node New node found for the current component.
*/
public void notifyNewNodeInComponent(Node node);
/**
* Notify that the algorithm has computed a new component.
*
* @param nodes List of nodes in the component.
*/
public void notifyEndComponent(ArrayList<Node> nodes);
}

View File

@@ -0,0 +1,36 @@
package org.insa.graphs.algorithm.weakconnectivity;
import java.io.PrintStream;
import java.util.ArrayList;
import org.insa.graphs.model.Node;
public class WeaklyConnectedComponentTextObserver implements WeaklyConnectedComponentObserver {
// Number of the current component.
private int numComponent = 1;
// Output stream
PrintStream stream;
public WeaklyConnectedComponentTextObserver(PrintStream stream) {
this.stream = stream;
}
@Override
public void notifyStartComponent(Node curNode) {
stream.print("Entering component #" + numComponent + " from node #" + curNode.getId() + "... ");
}
@Override
public void notifyNewNodeInComponent(Node node) {
}
@Override
public void notifyEndComponent(ArrayList<Node> nodes) {
stream.println(nodes.size() + " nodes found.");
stream.flush();
numComponent += 1;
}
}

View File

@@ -0,0 +1,159 @@
package org.insa.graphs.algorithm.weakconnectivity;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
import org.insa.graphs.algorithm.AbstractAlgorithm;
import org.insa.graphs.algorithm.AbstractSolution.Status;
import org.insa.graphs.model.Arc;
import org.insa.graphs.model.Graph;
import org.insa.graphs.model.Node;
public class WeaklyConnectedComponentsAlgorithm
extends AbstractAlgorithm<WeaklyConnectedComponentObserver> {
/**
* @param data Input data for this algorithm.
*/
public WeaklyConnectedComponentsAlgorithm(WeaklyConnectedComponentsData data) {
super(data);
}
@Override
public WeaklyConnectedComponentsSolution run() {
return (WeaklyConnectedComponentsSolution) super.run();
}
@Override
public WeaklyConnectedComponentsData getInputData() {
return (WeaklyConnectedComponentsData) super.getInputData();
}
/**
* Notify all observers that the algorithm is entering a new component.
*
* @param curNode Starting node for the component.
*/
protected void notifyStartComponent(Node curNode) {
for (WeaklyConnectedComponentObserver obs: getObservers()) {
obs.notifyStartComponent(curNode);
}
}
/**
* Notify all observers that a new node has been found for the current
* component.
*
* @param node New node found for the current component.
*/
protected void notifyNewNodeInComponent(Node node) {
for (WeaklyConnectedComponentObserver obs: getObservers()) {
obs.notifyNewNodeInComponent(node);
}
}
/**
* Notify all observers that the algorithm has computed a new component.
*
* @param nodes List of nodes in the component.
*/
protected void notifyEndComponent(ArrayList<Node> nodes) {
for (WeaklyConnectedComponentObserver obs: getObservers()) {
obs.notifyEndComponent(nodes);
}
}
/**
* @return An adjacency list for the undirected graph equivalent to the stored
* graph.
*/
protected ArrayList<HashSet<Integer>> createUndirectedGraph() {
int nNodes = getInputData().getGraph().size();
ArrayList<HashSet<Integer>> res = new ArrayList<HashSet<Integer>>(nNodes);
for (int i = 0; i < nNodes; ++i) {
res.add(new HashSet<Integer>());
}
for (Node node: getInputData().getGraph().getNodes()) {
for (Arc arc: node.getSuccessors()) {
res.get(node.getId()).add(arc.getDestination().getId());
if (arc.getRoadInformation().isOneWay()) {
res.get(arc.getDestination().getId()).add(node.getId());
}
}
}
return res;
}
/**
* Apply a breadth first search algorithm on the given undirected graph
* (adjacency list), starting at node cur, and marking nodes in marked.
*
* @param marked
* @param cur
*
* @return
*/
protected ArrayList<Node> bfs(ArrayList<HashSet<Integer>> ugraph, boolean[] marked, int cur) {
Graph graph = getInputData().getGraph();
ArrayList<Node> component = new ArrayList<Node>();
// Using a queue because we are doing a BFS
Queue<Integer> queue = new LinkedList<Integer>();
// Notify observers about the current component.
notifyStartComponent(graph.get(cur));
// Add original node and loop until the queue is empty.
queue.add(cur);
marked[cur] = true;
while (!queue.isEmpty()) {
Node node = graph.get(queue.remove());
component.add(node);
// Notify observers
notifyNewNodeInComponent(node);
for (Integer destId: ugraph.get(node.getId())) {
Node dest = graph.get(destId);
if (!marked[dest.getId()]) {
queue.add(destId);
marked[destId] = true;
}
}
}
notifyEndComponent(component);
return component;
}
@Override
protected WeaklyConnectedComponentsSolution doRun() {
Graph graph = getInputData().getGraph();
ArrayList<HashSet<Integer>> ugraph = createUndirectedGraph();
boolean[] marked = new boolean[graph.size()];
Arrays.fill(marked, false);
ArrayList<ArrayList<Node>> components = new ArrayList<ArrayList<Node>>();
// perform algorithm
int cur = 0;
while (cur < marked.length) {
// Apply BFS
components.add(this.bfs(ugraph, marked, cur));
// Find next non-marked
for (; cur < marked.length && marked[cur]; ++cur)
;
}
return new WeaklyConnectedComponentsSolution(getInputData(), Status.OPTIMAL, components);
}
}

View File

@@ -0,0 +1,20 @@
package org.insa.graphs.algorithm.weakconnectivity;
import org.insa.graphs.algorithm.AbstractInputData;
import org.insa.graphs.model.Graph;
public class WeaklyConnectedComponentsData extends AbstractInputData {
/**
* @param graph Graph for which components should be retrieved.
*/
public WeaklyConnectedComponentsData(Graph graph) {
super(graph, null);
}
@Override
public String toString() {
return "Weakly-connected components from #0.";
}
}

View File

@@ -0,0 +1,57 @@
package org.insa.graphs.algorithm.weakconnectivity;
import java.util.ArrayList;
import org.insa.graphs.algorithm.AbstractSolution;
import org.insa.graphs.model.Node;
public class WeaklyConnectedComponentsSolution extends AbstractSolution {
// Components
private ArrayList<ArrayList<Node>> components;
protected WeaklyConnectedComponentsSolution(WeaklyConnectedComponentsData data) {
super(data);
}
protected WeaklyConnectedComponentsSolution(WeaklyConnectedComponentsData data, Status status,
ArrayList<ArrayList<Node>> components) {
super(data, status);
this.components = components;
}
@Override
public WeaklyConnectedComponentsData getInputData() {
return (WeaklyConnectedComponentsData) super.getInputData();
}
/**
* @return Components of the solution, if any.
*/
public ArrayList<ArrayList<Node>> getComponents() {
return components;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
int nIsolated = 0;
int nGt10 = 0;
for (ArrayList<Node> component: components) {
if (component.size() == 1) {
nIsolated += 1;
}
else if (component.size() > 10) {
nGt10 += 1;
}
}
return "Found " + components.size() + " components (" + nGt10 + " with more than 10 nodes, "
+ nIsolated + " isolated nodes) in " + getSolvingTime().getSeconds() + " seconds.";
}
}