diff --git a/src/main/org/insa/algo/AbstractInputData.java b/src/main/org/insa/algo/AbstractInputData.java index 4eff040..d5296a5 100644 --- a/src/main/org/insa/algo/AbstractInputData.java +++ b/src/main/org/insa/algo/AbstractInputData.java @@ -2,6 +2,7 @@ package org.insa.algo; import org.insa.graph.Arc; import org.insa.graph.Graph; +import org.insa.graph.GraphStatistics; /** * Base class for algorithm input data classes. This class contains the basic @@ -12,77 +13,29 @@ import org.insa.graph.Graph; public abstract class AbstractInputData { /** - * Mode for computing costs on the arc (time or length). - * + * Enum specifying the top mode of the algorithms. + * + * @see ArcInspector */ public enum Mode { TIME, LENGTH } - /** - * Filtering interface for arcs - This class can be used to indicate to an - * algorithm which arc can be used. - * - */ - public interface ArcFilter { - - /** - * 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); - - } - // Graph private final Graph graph; - // Mode for the computation of the costs. - private final Mode mode; - // Arc filter. - private final ArcFilter arcFilter; + protected final ArcInspector arcInspector; /** * Create a new AbstractInputData instance for the given graph, mode and filter. * - * @param graph - * @parma mode - * @param arcFilter + * @param graph Graph for this input data. + * @param arcInspector Arc inspector for this input data. */ - protected AbstractInputData(Graph graph, Mode mode, ArcFilter arcFilter) { + protected AbstractInputData(Graph graph, ArcInspector arcInspector) { this.graph = graph; - this.mode = mode; - this.arcFilter = arcFilter; - } - - /** - * Create a new AbstractInputData instance for the given graph and mode, with no - * filtering on the arc. - * - * @param graph - * @param mode - */ - protected AbstractInputData(Graph graph, Mode mode) { - this(graph, mode, new AbstractInputData.ArcFilter() { - @Override - public boolean isAllowed(Arc arc) { - return true; - } - }); - } - - /** - * Create a new AbstractInputData instance for the given graph, with default - * mode (LENGHT), with no filtering on the arc. - * - * @param graph - */ - protected AbstractInputData(Graph graph) { - this(graph, Mode.LENGTH); + this.arcInspector = arcInspector; } /** @@ -93,12 +46,39 @@ public abstract class AbstractInputData { } /** - * @return Mode of the algorithm (time or length). + * 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 mode; + 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(); } /** @@ -108,10 +88,10 @@ public abstract class AbstractInputData { * * @return true if the given arc is allowed. * - * @see ArcFilter + * @see ArcInspector */ public boolean isAllowed(Arc arc) { - return this.arcFilter.isAllowed(arc); + return this.arcInspector.isAllowed(arc); } } diff --git a/src/main/org/insa/algo/ArcFilterFactory.java b/src/main/org/insa/algo/ArcFilterFactory.java deleted file mode 100644 index 07ce634..0000000 --- a/src/main/org/insa/algo/ArcFilterFactory.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.insa.algo; - -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.List; - -import org.insa.algo.AbstractInputData.ArcFilter; -import org.insa.graph.AccessRestrictions.AccessMode; -import org.insa.graph.AccessRestrictions.AccessRestriction; -import org.insa.graph.Arc; - -public class ArcFilterFactory { - - /** - * @return List of all arc filters in this factory. - */ - public static List getAllFilters() { - List filters = new ArrayList<>(); - - // Common filters: - - // 1. No filter (all arcs allowed): - filters.add(new ArcFilter() { - @Override - public boolean isAllowed(Arc arc) { - return true; - } - - @Override - public String toString() { - return "All roads are allowed."; - } - }); - - // 2. Only road allowed for cars: - filters.add(new ArcFilter() { - @Override - public boolean isAllowed(Arc arc) { - return arc.getRoadInformation().getAccessRestrictions() - .isAllowedForAny(AccessMode.MOTORCAR, EnumSet.complementOf(EnumSet - .of(AccessRestriction.FORBIDDEN, AccessRestriction.PRIVATE))); - } - - @Override - public String toString() { - return "Only roads open for cars."; - } - }); - - // 3. Non-private roads for pedestrian and bicycle: - filters.add(new ArcFilter() { - @Override - public boolean isAllowed(Arc arc) { - return arc.getRoadInformation().getAccessRestrictions() - .isAllowedForAny(AccessMode.FOOT, EnumSet.complementOf(EnumSet - .of(AccessRestriction.FORBIDDEN, AccessRestriction.PRIVATE))); - } - - @Override - public String toString() { - return "Non-private roads for pedestrian."; - } - }); - - // 3. Add your own filters here (do not forget to implement toString() to get an - // understandable output!): - - return filters; - } - -} diff --git a/src/main/org/insa/algo/ArcInspector.java b/src/main/org/insa/algo/ArcInspector.java new file mode 100644 index 0000000..663af31 --- /dev/null +++ b/src/main/org/insa/algo/ArcInspector.java @@ -0,0 +1,43 @@ +package org.insa.algo; + +import org.insa.algo.AbstractInputData.Mode; +import org.insa.graph.Arc; +import org.insa.graph.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(); + +} \ No newline at end of file diff --git a/src/main/org/insa/algo/ArcInspectorFactory.java b/src/main/org/insa/algo/ArcInspectorFactory.java new file mode 100644 index 0000000..2404674 --- /dev/null +++ b/src/main/org/insa/algo/ArcInspectorFactory.java @@ -0,0 +1,149 @@ +package org.insa.algo; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; + +import org.insa.algo.AbstractInputData.Mode; +import org.insa.graph.AccessRestrictions.AccessMode; +import org.insa.graph.AccessRestrictions.AccessRestriction; +import org.insa.graph.Arc; +import org.insa.graph.GraphStatistics; + +public class ArcInspectorFactory { + + /** + * @return List of all arc filters in this factory. + */ + public static List getAllFilters() { + List 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 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; + } + +} diff --git a/src/main/org/insa/algo/carpooling/CarPoolingData.java b/src/main/org/insa/algo/carpooling/CarPoolingData.java index 048ceeb..559e6da 100644 --- a/src/main/org/insa/algo/carpooling/CarPoolingData.java +++ b/src/main/org/insa/algo/carpooling/CarPoolingData.java @@ -1,12 +1,13 @@ package org.insa.algo.carpooling; import org.insa.algo.AbstractInputData; +import org.insa.algo.ArcInspector; import org.insa.graph.Graph; public class CarPoolingData extends AbstractInputData { - protected CarPoolingData(Graph graph, Mode mode, ArcFilter arcFilter) { - super(graph, mode, arcFilter); + protected CarPoolingData(Graph graph, ArcInspector arcFilter) { + super(graph, arcFilter); } } diff --git a/src/main/org/insa/algo/packageswitch/PackageSwitchData.java b/src/main/org/insa/algo/packageswitch/PackageSwitchData.java index 8f7edcb..57c581f 100644 --- a/src/main/org/insa/algo/packageswitch/PackageSwitchData.java +++ b/src/main/org/insa/algo/packageswitch/PackageSwitchData.java @@ -1,12 +1,13 @@ package org.insa.algo.packageswitch; import org.insa.algo.AbstractInputData; +import org.insa.algo.ArcInspector; import org.insa.graph.Graph; public class PackageSwitchData extends AbstractInputData { - protected PackageSwitchData(Graph graph, Mode mode, ArcFilter arcFilter) { - super(graph, mode, arcFilter); + protected PackageSwitchData(Graph graph, ArcInspector arcFilter) { + super(graph, arcFilter); } } diff --git a/src/main/org/insa/algo/shortestpath/BellmanFordAlgorithm.java b/src/main/org/insa/algo/shortestpath/BellmanFordAlgorithm.java index fddac4f..3c34c67 100644 --- a/src/main/org/insa/algo/shortestpath/BellmanFordAlgorithm.java +++ b/src/main/org/insa/algo/shortestpath/BellmanFordAlgorithm.java @@ -4,7 +4,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import org.insa.algo.AbstractInputData; import org.insa.algo.AbstractSolution.Status; import org.insa.graph.Arc; import org.insa.graph.Graph; @@ -13,90 +12,88 @@ import org.insa.graph.Path; public class BellmanFordAlgorithm extends ShortestPathAlgorithm { - public BellmanFordAlgorithm(ShortestPathData data) { - super(data); - } + public BellmanFordAlgorithm(ShortestPathData data) { + super(data); + } - @Override - protected ShortestPathSolution doRun() { + @Override + protected ShortestPathSolution doRun() { - // Retrieve the graph. - ShortestPathData data = getInputData(); - Graph graph = data.getGraph(); + // Retrieve the graph. + ShortestPathData data = getInputData(); + Graph graph = data.getGraph(); - final int nbNodes = graph.size(); + 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; + // 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()); + // Notify observers about the first event (origin processed). + notifyOriginProcessed(data.getOrigin()); - // Initialize array of predecessors. - Arc[] predecessorArcs = new Arc[nbNodes]; + // 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) { - for (Arc arc: node) { + // 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) { + for (Arc arc : node) { - // Small test to check allowed roads... - if (!data.isAllowed(arc)) { - continue; - } + // Small test to check allowed roads... + if (!data.isAllowed(arc)) { + continue; + } - // Retrieve weight of the arc. - double w = data.getMode() == AbstractInputData.Mode.LENGTH ? arc.getLength() - : arc.getMinimumTravelTime(); + // Retrieve weight of the arc. + double w = data.getCost(arc); + double oldDistance = distances[arc.getDestination().getId()]; + double newDistance = distances[node.getId()] + w; - double oldDistance = distances[arc.getDestination().getId()]; - double newDistance = distances[node.getId()] + w; + if (Double.isInfinite(oldDistance) && Double.isFinite(newDistance)) { + notifyNodeReached(arc.getDestination()); + } - 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; + } + } + } + } - // 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; - ShortestPathSolution solution = null; + // Destination has no predecessor, the solution is infeasible... + if (predecessorArcs[data.getDestination().getId()] == null) { + solution = new ShortestPathSolution(data, Status.INFEASIBLE); + } else { - // 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()); - // The destination has been found, notify the observers. - notifyDestinationReached(data.getDestination()); + // Create the path from the array of predecessors... + ArrayList arcs = new ArrayList<>(); + Arc arc = predecessorArcs[data.getDestination().getId()]; + while (arc != null) { + arcs.add(arc); + arc = predecessorArcs[arc.getOrigin().getId()]; + } - // Create the path from the array of predecessors... - ArrayList 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); - // Reverse the path... - Collections.reverse(arcs); + // Create the final solution. + solution = new ShortestPathSolution(data, Status.OPTIMAL, new Path(graph, arcs)); + } - // Create the final solution. - solution = new ShortestPathSolution(data, Status.OPTIMAL, new Path(graph, arcs)); - } - - return solution; - } + return solution; + } } diff --git a/src/main/org/insa/algo/shortestpath/ShortestPathData.java b/src/main/org/insa/algo/shortestpath/ShortestPathData.java index 09f69a1..e0c453a 100644 --- a/src/main/org/insa/algo/shortestpath/ShortestPathData.java +++ b/src/main/org/insa/algo/shortestpath/ShortestPathData.java @@ -1,6 +1,7 @@ package org.insa.algo.shortestpath; import org.insa.algo.AbstractInputData; +import org.insa.algo.ArcInspector; import org.insa.graph.Graph; import org.insa.graph.Node; @@ -9,34 +10,17 @@ public class ShortestPathData extends AbstractInputData { // Origin and destination nodes. private final Node origin, destination; - /** - * Construct a new instance of ShortestPathData with the given parameters and - * for which all arcs are allowed. - * - * @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 mode Cost mode for the path. - */ - public ShortestPathData(Graph graph, Node origin, Node destination, Mode mode) { - super(graph, mode); - this.origin = origin; - this.destination = 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 mode Cost mode for the path. - * @param arcFilter Filter for arcs (used to allow only a specific set of arcs - * in the graph to be used). + * @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, Mode mode, - AbstractInputData.ArcFilter arcFilter) { - super(graph, mode, arcFilter); + public ShortestPathData(Graph graph, Node origin, Node destination, ArcInspector arcInspector) { + super(graph, arcInspector); this.origin = origin; this.destination = destination; } @@ -58,6 +42,6 @@ public class ShortestPathData extends AbstractInputData { @Override public String toString() { return "Shortest-path from #" + origin.getId() + " to #" + destination.getId() + " [" - + getMode().toString().toLowerCase() + "]"; + + this.arcInspector.toString().toLowerCase() + "]"; } } diff --git a/src/main/org/insa/algo/shortestpath/ShortestPathSolution.java b/src/main/org/insa/algo/shortestpath/ShortestPathSolution.java index 39ff5d9..3cb8c31 100644 --- a/src/main/org/insa/algo/shortestpath/ShortestPathSolution.java +++ b/src/main/org/insa/algo/shortestpath/ShortestPathSolution.java @@ -1,7 +1,8 @@ package org.insa.algo.shortestpath; -import org.insa.algo.AbstractInputData; +import org.insa.algo.AbstractInputData.Mode; import org.insa.algo.AbstractSolution; +import org.insa.graph.Arc; import org.insa.graph.Path; public class ShortestPathSolution extends AbstractSolution { @@ -59,14 +60,17 @@ public class ShortestPathSolution extends AbstractSolution { 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() == AbstractInputData.Mode.LENGTH) { - info = String.format("%s, %.4f kilometers", info, (getPath().getLength() / 1000.0)); + if (getInputData().getMode() == Mode.LENGTH) { + info = String.format("%s, %.4f kilometers", info, cost / 1000.0); } else { - info = String.format("%s, %.4f minutes", info, - (getPath().getMinimumTravelTime() / 60.0)); + info = String.format("%s, %.4f minutes", info, cost / 60.0); } } info += " in " + getSolvingTime().getSeconds() + " seconds."; diff --git a/src/main/org/insa/algo/utils/BinaryHeap.java b/src/main/org/insa/algo/utils/BinaryHeap.java index 814c02a..d3f350a 100644 --- a/src/main/org/insa/algo/utils/BinaryHeap.java +++ b/src/main/org/insa/algo/utils/BinaryHeap.java @@ -18,14 +18,13 @@ import java.util.ArrayList; * @author Mark Allen Weiss * @author DLB */ -public class BinaryHeap> { +public class BinaryHeap> implements PriorityQueue { // Number of elements in heap. private int currentSize; - // The heap array. Java genericity does not work with arrays so we have to use - // an ArrayList. - private ArrayList array; + // The heap array. + private final ArrayList array; /** * Construct a new empty binary heap. @@ -126,62 +125,37 @@ public class BinaryHeap> { } } - /** - * @return true if the heap is empty, false otherwise. - */ + @Override public boolean isEmpty() { return this.currentSize == 0; } - /** - * @return Current size (number of elements) of this heap. - */ + @Override public int size() { return this.currentSize; } - /** - * Insert the given element into the heap. - * - * @param x Item to insert. - */ - public void add(E x) { + @Override + public void insert(E x) { int index = this.currentSize++; this.arraySet(index, x); this.percolateUp(index); } - /** - * Tell the binary heap that the given element has been modified and should be - * re-positioned inside the heap. - * - * @param x Item to update. - */ - public void update(E x) { + @Override + public void remove(E x) throws ElementNotFoundException { // TODO: } - /** - * Find the smallest item in the heap. - * - * @return The smallest item in the heap. - * - * @throws RuntimeException if this heap is empty. - */ - public E findMin() throws RuntimeException { + @Override + public E findMin() throws EmptyPriorityQueueException { if (isEmpty()) throw new RuntimeException("Empty binary heap."); return this.array.get(0); } - /** - * Remove the smallest item from the heap. - * - * @return The smallest item in the heap. - * - * @throws RuntimeException if this heap is empty. - */ - public E deleteMin() throws RuntimeException { + @Override + public E deleteMin() throws EmptyPriorityQueueException { E minItem = findMin(); E lastItem = this.array.get(--this.currentSize); this.arraySet(0, lastItem); diff --git a/src/main/org/insa/algo/utils/BinarySearchTree.java b/src/main/org/insa/algo/utils/BinarySearchTree.java new file mode 100644 index 0000000..775257b --- /dev/null +++ b/src/main/org/insa/algo/utils/BinarySearchTree.java @@ -0,0 +1,64 @@ +package org.insa.algo.utils; + +import java.util.SortedSet; +import java.util.TreeSet; + +public class BinarySearchTree> implements PriorityQueue { + + // Underlying implementation + private final SortedSet 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 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; + } + +} diff --git a/src/main/org/insa/algo/utils/ElementNotFoundException.java b/src/main/org/insa/algo/utils/ElementNotFoundException.java new file mode 100644 index 0000000..7f2be3c --- /dev/null +++ b/src/main/org/insa/algo/utils/ElementNotFoundException.java @@ -0,0 +1,32 @@ +package org.insa.algo.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; + } + +} diff --git a/src/main/org/insa/algo/utils/EmptyPriorityQueueException.java b/src/main/org/insa/algo/utils/EmptyPriorityQueueException.java new file mode 100644 index 0000000..1dc4877 --- /dev/null +++ b/src/main/org/insa/algo/utils/EmptyPriorityQueueException.java @@ -0,0 +1,16 @@ +package org.insa.algo.utils; + +public class EmptyPriorityQueueException extends RuntimeException { + + /** + * + */ + private static final long serialVersionUID = 1L; + + /** + * + */ + public EmptyPriorityQueueException() { + } + +} diff --git a/src/main/org/insa/algo/utils/PriorityQueue.java b/src/main/org/insa/algo/utils/PriorityQueue.java new file mode 100644 index 0000000..1258041 --- /dev/null +++ b/src/main/org/insa/algo/utils/PriorityQueue.java @@ -0,0 +1,54 @@ +package org.insa.algo.utils; + +/** + * Interface representing a basic priority queue. + * + * @see https://en.wikipedia.org/wiki/Priority_queue + */ +public interface PriorityQueue> { + + /** + * Check if the priority queue is empty. + * + * @return true if the queue is empty, false otherwise. + */ + public boolean isEmpty(); + + /** + * @return Current size (number of elements) of this queue. + */ + public int size(); + + /** + * Insert the given element into the queue. + * + * @param x Item to insert. + */ + public void insert(E x); + + /** + * Remove the given element from the priority queue. + * + * @param x Item to remove. + */ + public void remove(E x) throws ElementNotFoundException; + + /** + * Retrieve (but not remove) the smallest item in the queue. + * + * @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. + * + * @return The smallest item in the queue. + * + * @throws EmptyPriorityQueueException if this queue is empty. + */ + public E deleteMin() throws EmptyPriorityQueueException; + +} diff --git a/src/main/org/insa/algo/weakconnectivity/WeaklyConnectedComponentsData.java b/src/main/org/insa/algo/weakconnectivity/WeaklyConnectedComponentsData.java index 2faffa9..f64819b 100644 --- a/src/main/org/insa/algo/weakconnectivity/WeaklyConnectedComponentsData.java +++ b/src/main/org/insa/algo/weakconnectivity/WeaklyConnectedComponentsData.java @@ -9,7 +9,7 @@ public class WeaklyConnectedComponentsData extends AbstractInputData { * @param graph Graph for which components should be retrieved. */ public WeaklyConnectedComponentsData(Graph graph) { - super(graph); + super(graph, null); } @Override diff --git a/src/main/org/insa/base/Launch.java b/src/main/org/insa/base/Launch.java index 9314caf..c377b0c 100644 --- a/src/main/org/insa/base/Launch.java +++ b/src/main/org/insa/base/Launch.java @@ -23,6 +23,8 @@ public class Launch { * Create a new Drawing inside a JFrame an return it. * * @return The created drawing. + * + * @throws Exception if something wrong happens when creating the graph. */ public static Drawing createDrawing() throws Exception { BasicDrawing basicDrawing = new BasicDrawing(); diff --git a/src/main/org/insa/graph/GraphStatistics.java b/src/main/org/insa/graph/GraphStatistics.java index c90f24e..0688e1b 100644 --- a/src/main/org/insa/graph/GraphStatistics.java +++ b/src/main/org/insa/graph/GraphStatistics.java @@ -94,6 +94,8 @@ public class GraphStatistics { } /** + * @param other Box to intersect. + * * @return true if this box contains the given box. */ public boolean contains(BoundingBox other) { diff --git a/src/main/org/insa/graphics/AlgorithmPanel.java b/src/main/org/insa/graphics/AlgorithmPanel.java index e9b4b8e..fdbc28e 100644 --- a/src/main/org/insa/graphics/AlgorithmPanel.java +++ b/src/main/org/insa/graphics/AlgorithmPanel.java @@ -14,22 +14,18 @@ import java.util.List; import javax.swing.Box; import javax.swing.BoxLayout; -import javax.swing.ButtonGroup; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; -import javax.swing.JRadioButton; import javax.swing.border.EmptyBorder; import org.insa.algo.AbstractAlgorithm; -import org.insa.algo.AbstractInputData; -import org.insa.algo.AbstractInputData.ArcFilter; -import org.insa.algo.AbstractInputData.Mode; import org.insa.algo.AlgorithmFactory; -import org.insa.algo.ArcFilterFactory; +import org.insa.algo.ArcInspector; +import org.insa.algo.ArcInspectorFactory; import org.insa.graph.Node; import org.insa.graphics.NodesInputPanel.InputChangedEvent; import org.insa.graphics.drawing.Drawing; @@ -55,20 +51,18 @@ public class AlgorithmPanel extends JPanel implements DrawingChangeListener { protected static final int START_EVENT_ID = 0x1; private final List nodes; - private final AbstractInputData.Mode mode; private final Class> algoClass; - private final AbstractInputData.ArcFilter arcFilter; + private final ArcInspector arcFilter; private final boolean graphicVisualization; private final boolean textualVisualization; public StartActionEvent(Class> algoClass, List nodes, - Mode mode, ArcFilter arcFilter, boolean graphicVisualization, + ArcInspector arcFilter, boolean graphicVisualization, boolean textualVisualization) { super(AlgorithmPanel.this, START_EVENT_ID, START_EVENT_COMMAND); this.nodes = nodes; - this.mode = mode; this.algoClass = algoClass; this.graphicVisualization = graphicVisualization; this.textualVisualization = textualVisualization; @@ -82,17 +76,10 @@ public class AlgorithmPanel extends JPanel implements DrawingChangeListener { return this.nodes; } - /** - * @return Mode associated with this event. - */ - public Mode getMode() { - return this.mode; - } - /** * @return Arc filter associated with this event. */ - public ArcFilter getArcFilter() { + public ArcInspector getArcFilter() { return this.arcFilter; } @@ -147,16 +134,13 @@ public class AlgorithmPanel extends JPanel implements DrawingChangeListener { * @param baseAlgorithm Base algorithm for this algorithm panel. * @param title Title of the panel. * @param nodeNames Names of the input nodes. - * @param enableModeSelection true to enable {@link Mode} - * selection. - * @param enableArcFilterSelection true to enable {@link ArcFilter} - * selection. + * @param enableArcFilterSelection true to enable + * {@link ArcInspector} selection. * - * @see ArcFilterFactory + * @see ArcInspectorFactory */ public AlgorithmPanel(Component parent, Class> baseAlgorithm, - String title, String[] nodeNames, boolean enableModeSelection, - boolean enableArcFilterSelection) { + String title, String[] nodeNames, boolean enableArcFilterSelection) { super(); setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS)); @@ -178,20 +162,14 @@ public class AlgorithmPanel extends JPanel implements DrawingChangeListener { add(this.nodesInputPanel); components.add(this.nodesInputPanel); - JComboBox arcFilterSelect = new JComboBox<>( - ArcFilterFactory.getAllFilters().toArray(new ArcFilter[0])); + JComboBox arcFilterSelect = new JComboBox<>( + ArcInspectorFactory.getAllFilters().toArray(new ArcInspector[0])); arcFilterSelect.setBackground(Color.WHITE); // Add mode selection JPanel modeAndObserverPanel = new JPanel(); modeAndObserverPanel.setAlignmentX(Component.LEFT_ALIGNMENT); modeAndObserverPanel.setLayout(new GridBagLayout()); - JRadioButton lengthModeButton = new JRadioButton("Length"); - lengthModeButton.setSelected(true); - JRadioButton timeModeButton = new JRadioButton("Time"); - ButtonGroup group = new ButtonGroup(); - group.add(lengthModeButton); - group.add(timeModeButton); graphicObserverCheckbox = new JCheckBox("Graphic"); graphicObserverCheckbox.setSelected(true); @@ -201,19 +179,6 @@ public class AlgorithmPanel extends JPanel implements DrawingChangeListener { c.fill = GridBagConstraints.HORIZONTAL; - if (enableModeSelection) { - c.gridx = 0; - c.gridy = 0; - c.weightx = 0; - modeAndObserverPanel.add(new JLabel("Mode: "), c); - c.gridx = 1; - c.weightx = 1; - modeAndObserverPanel.add(lengthModeButton, c); - c.gridx = 2; - c.weightx = 1; - modeAndObserverPanel.add(timeModeButton, c); - } - c.gridy = 2; c.gridx = 0; c.weightx = 0; @@ -236,8 +201,6 @@ public class AlgorithmPanel extends JPanel implements DrawingChangeListener { modeAndObserverPanel.add(arcFilterSelect, c); } - components.add(timeModeButton); - components.add(lengthModeButton); components.add(arcFilterSelect); components.add(textualObserverCheckbox); @@ -258,16 +221,12 @@ public class AlgorithmPanel extends JPanel implements DrawingChangeListener { startAlgoButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - AbstractInputData.Mode mode = lengthModeButton.isSelected() - ? AbstractInputData.Mode.LENGTH - : AbstractInputData.Mode.TIME; - for (ActionListener lis: startActionListeners) { lis.actionPerformed(new StartActionEvent( AlgorithmFactory.getAlgorithmClass(baseAlgorithm, (String) algoSelect.getSelectedItem()), - nodesInputPanel.getNodeForInputs(), mode, - (AbstractInputData.ArcFilter) arcFilterSelect.getSelectedItem(), + nodesInputPanel.getNodeForInputs(), + (ArcInspector) arcFilterSelect.getSelectedItem(), graphicObserverCheckbox.isSelected(), textualObserverCheckbox.isSelected())); } diff --git a/src/main/org/insa/graphics/MainWindow.java b/src/main/org/insa/graphics/MainWindow.java index b10f643..252b197 100644 --- a/src/main/org/insa/graphics/MainWindow.java +++ b/src/main/org/insa/graphics/MainWindow.java @@ -157,7 +157,7 @@ public class MainWindow extends JFrame { this.currentPalette = this.basicPalette; wccPanel = new AlgorithmPanel(this, WeaklyConnectedComponentsAlgorithm.class, - "Weakly-Connected Components", new String[] {}, false, false); + "Weakly-Connected Components", new String[] {}, false); wccPanel.addStartActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { @@ -202,13 +202,13 @@ public class MainWindow extends JFrame { }); spPanel = new AlgorithmPanel(this, ShortestPathAlgorithm.class, "Shortest-Path", - new String[] { "Origin", "Destination" }, true, true); + new String[] { "Origin", "Destination" }, true); spPanel.addStartActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { StartActionEvent evt = (StartActionEvent) e; ShortestPathData data = new ShortestPathData(graph, evt.getNodes().get(0), - evt.getNodes().get(1), evt.getMode(), evt.getArcFilter()); + evt.getNodes().get(1), evt.getArcFilter()); ShortestPathAlgorithm spAlgorithm = null; try { @@ -257,11 +257,10 @@ public class MainWindow extends JFrame { cpPanel = new AlgorithmPanel( this, CarPoolingAlgorithm.class, "Car-Pooling", new String[] { "Origin Car", "Origin Pedestrian", "Destination Car", "Destination Pedestrian" }, - true, true); + true); psPanel = new AlgorithmPanel(this, PackageSwitchAlgorithm.class, "Car-Pooling", - new String[] { "Oribin A", "Origin B", "Destination A", "Destination B" }, true, - true); + new String[] { "Oribin A", "Origin B", "Destination A", "Destination B" }, true); // add algorithm panels algoPanels.add(wccPanel); diff --git a/src/main/org/insa/graphics/drawing/components/BasicDrawing.java b/src/main/org/insa/graphics/drawing/components/BasicDrawing.java index 88a87b9..5c7dd96 100644 --- a/src/main/org/insa/graphics/drawing/components/BasicDrawing.java +++ b/src/main/org/insa/graphics/drawing/components/BasicDrawing.java @@ -15,7 +15,6 @@ import java.awt.geom.Point2D; import java.awt.image.BufferedImage; import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -65,6 +64,11 @@ public class BasicDrawing extends JPanel implements Drawing { this.color = color; } + /** + * @return The Z level of this overlay (>= 1). + */ + public abstract int getZLevel(); + @Override public void setColor(Color color) { this.color = color; @@ -88,10 +92,7 @@ public class BasicDrawing extends JPanel implements Drawing { @Override public void delete() { - synchronized (overlays) { - BasicDrawing.this.overlays.remove(this); - } - BasicDrawing.this.repaint(); + BasicDrawing.this.overlays.remove(this); } /** @@ -134,6 +135,10 @@ public class BasicDrawing extends JPanel implements Drawing { this.alphaMode = alphaMode; } + public int getZLevel() { + return 3; + } + @Override public Point getPoint() { return point; @@ -181,6 +186,10 @@ public class BasicDrawing extends JPanel implements Drawing { this.color = color; } + public int getZLevel() { + return 2; + } + @Override public void setColor(Color color) { super.setColor(color); @@ -243,6 +252,10 @@ public class BasicDrawing extends JPanel implements Drawing { this.graphics.setBackground(new Color(0, 0, 0, 0)); } + public int getZLevel() { + return 1; + } + @Override public void setColor(Color color) { super.setColor(color); @@ -294,6 +307,68 @@ public class BasicDrawing extends JPanel implements Drawing { } + /** + * Class encapsulating a set of overlays. + * + */ + private class BasicOverlays { + + // List of overlays. + private ArrayList> overlays = new ArrayList<>(); + + public synchronized void draw(Graphics2D g) { + // Clear overlays. + for (ArrayList arr: this.overlays) { + for (BasicOverlay overlay: arr) { + overlay.draw(g); + } + } + } + + public synchronized void remove(BasicOverlay overlay) { + overlays.get(overlay.getZLevel() - 1).remove(overlay); + BasicDrawing.this.repaint(); + } + + public void clear() { + clear(true); + } + + public void clear(boolean repaint) { + // Clear overlays. + for (ArrayList arr: this.overlays) { + arr.clear(); + } + // Repaint if requested. + if (repaint) { + BasicDrawing.this.repaint(); + } + } + + public BasicOverlay add(BasicOverlay marker) { + return add(marker, true); + } + + public synchronized BasicOverlay add(BasicOverlay overlay, boolean repaint) { + + // Check if we have a level for this... + for (int i = overlays.size(); i < overlay.getZLevel(); ++i) { + overlays.add(new ArrayList<>()); + } + + // Add overlay to the given list. + overlays.get(overlay.getZLevel() - 1).add(overlay); + + // Repaint if requested. + if (repaint) { + BasicDrawing.this.repaint(); + } + + return overlay; + } + + }; + // Default path color. public static final Color DEFAULT_PATH_COLOR = new Color(66, 134, 244); @@ -317,8 +392,7 @@ public class BasicDrawing extends JPanel implements Drawing { private Graphics2D graphGraphics = null; // List of image for markers - private List overlays = Collections - .synchronizedList(new ArrayList()); + private BasicOverlays overlays = new BasicOverlays(); // Mapping DrawingClickListener -> MouseEventListener private List drawingClickListeners = new ArrayList<>(); @@ -391,11 +465,7 @@ public class BasicDrawing extends JPanel implements Drawing { } // Draw markers - synchronized (overlays) { - for (BasicOverlay overlay: overlays) { - overlay.draw(g); - } - } + this.overlays.draw(g); g.setTransform(sTransform); if (this.zoomControls != null) { @@ -416,9 +486,7 @@ public class BasicDrawing extends JPanel implements Drawing { if (this.graphGraphics != null) { this.graphGraphics.clearRect(0, 0, this.width, this.height); } - synchronized (overlays) { - this.overlays.clear(); - } + this.overlays.clear(false); this.repaint(); } @@ -429,10 +497,7 @@ public class BasicDrawing extends JPanel implements Drawing { */ @Override public void clearOverlays() { - synchronized (overlays) { - this.overlays.clear(); - } - this.repaint(); + this.overlays.clear(); } /** @@ -495,21 +560,12 @@ public class BasicDrawing extends JPanel implements Drawing { @Override public MarkerOverlay drawMarker(Point point, Color outer, Color inner, AlphaMode mode) { - BasicMarkerOverlay marker = createMarker(point, outer, inner, mode); - synchronized (overlays) { - this.overlays.add(marker); - } - this.repaint(); - return marker; + return (MarkerOverlay) this.overlays.add(createMarker(point, outer, inner, mode)); } @Override public PointSetOverlay createPointSetOverlay() { - BasicPointSetOverlay ps = new BasicPointSetOverlay(); - synchronized (overlays) { - this.overlays.add(ps); - } - return ps; + return (PointSetOverlay) this.overlays.add(new BasicPointSetOverlay(), false); } @Override @@ -671,12 +727,8 @@ public class BasicDrawing extends JPanel implements Drawing { destination = createMarker(path.getDestination().getPoint(), color, color, AlphaMode.TRANSPARENT); } - BasicPathOverlay overlay = new BasicPathOverlay(points, color, origin, destination); - synchronized (overlays) { - this.overlays.add(overlay); - } - this.repaint(); - return overlay; + return (PathOverlay) this.overlays + .add(new BasicPathOverlay(points, color, origin, destination)); } @Override diff --git a/src/main/org/insa/graphics/drawing/overlays/MarkerAutoScaling.java b/src/main/org/insa/graphics/drawing/overlays/MarkerAutoScaling.java index 8c6f0a3..d512b59 100644 --- a/src/main/org/insa/graphics/drawing/overlays/MarkerAutoScaling.java +++ b/src/main/org/insa/graphics/drawing/overlays/MarkerAutoScaling.java @@ -18,7 +18,8 @@ import org.mapsforge.map.layer.overlay.Marker; * correcting this. Internally, this image stores an {@link Image} instance and * scale it when a redraw is requested. * - * @see MarkerUtils#getMarkerForColor(java.awt.Color) + * @see MarkerUtils#getMarkerForColor(java.awt.Color, java.awt.Color, + * org.insa.graphics.drawing.Drawing.AlphaMode) * @see PaintUtils#getStrokeWidth(int, byte) */ public class MarkerAutoScaling extends Marker { diff --git a/src/main/org/insa/graphics/drawing/overlays/MarkerUtils.java b/src/main/org/insa/graphics/drawing/overlays/MarkerUtils.java index 1ec9704..5af89ca 100644 --- a/src/main/org/insa/graphics/drawing/overlays/MarkerUtils.java +++ b/src/main/org/insa/graphics/drawing/overlays/MarkerUtils.java @@ -18,8 +18,6 @@ public class MarkerUtils { * @param mode Mode to use to fill the inner part of the marker. * * @return An image representing a marker. - * - * @see MarkerUtils#getMarkerForColor(Color, AlphaMode) */ public static Image getMarkerForColor(Color outer, Color inner, AlphaMode mode) { // create image diff --git a/src/test/org/insa/algo/utils/BinaryHeapTest.java b/src/test/org/insa/algo/utils/BinaryHeapTest.java index 3501e04..d9ff2a7 100644 --- a/src/test/org/insa/algo/utils/BinaryHeapTest.java +++ b/src/test/org/insa/algo/utils/BinaryHeapTest.java @@ -1,6 +1,7 @@ package org.insa.algo.utils; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.util.Arrays; @@ -59,46 +60,78 @@ public class BinaryHeapTest { this.heap2 = new BinaryHeap<>(); for (MutableInteger v: data1) { - this.heap1.add(v); + this.heap1.insert(v); } for (MutableInteger v: data2) { - this.heap2.add(v); + this.heap2.insert(v); } } + @Test + public void testIsEmpty() { + BinaryHeap tree = new BinaryHeap<>(); + assertTrue(tree.isEmpty()); + assertFalse(this.heap1.isEmpty()); + assertFalse(this.heap2.isEmpty()); + } + + @Test + public void testSize() { + BinaryHeap tree = new BinaryHeap<>(); + assertEquals(0, tree.size()); + assertEquals(20, this.heap1.size()); + assertEquals(7, this.heap2.size()); + } + @Test public void testInsert() { BinaryHeap heap = new BinaryHeap<>(); int size = 0; for (MutableInteger x: data1) { - heap.add(x); - size += 1; - assertEquals(heap.size(), size); + heap.insert(x); + assertEquals(++size, heap.size()); } assertEquals(data1.length, heap.size()); heap = new BinaryHeap<>(); size = 0; for (MutableInteger x: data2) { - heap.add(x); - size += 1; - assertEquals(heap.size(), size); + heap.insert(x); + assertEquals(++size, heap.size()); } assertEquals(data2.length, heap.size()); } + @Test(expected = EmptyPriorityQueueException.class) + public void testEmptyFindMin() { + BinaryHeap heap = new BinaryHeap<>(); + heap.findMin(); + } + + @Test + public void testFindMin() { + assertEquals(0, heap1.findMin().get()); + assertEquals(1, heap2.findMin().get()); + } + + @Test(expected = EmptyPriorityQueueException.class) + public void testEmptyDeleteMin() { + BinaryHeap heap = new BinaryHeap<>(); + heap.deleteMin(); + } + @Test public void testDeleteMin() { // range 1 (sorted) int size = data1.length; assertEquals(heap1.size(), size); for (MutableInteger x: data1) { - assertEquals(heap1.deleteMin(), x); + assertEquals(x, heap1.deleteMin()); size -= 1; - assertEquals(heap1.size(), size); + assertEquals(size, heap1.size()); } - assertEquals(heap1.size(), 0); + assertEquals(0, heap1.size()); assertTrue(heap1.isEmpty()); // range 2 (was not sorted) @@ -107,20 +140,56 @@ public class BinaryHeapTest { size = range2.length; assertEquals(heap2.size(), size); for (MutableInteger x: range2) { - assertEquals(heap2.deleteMin().get(), x.get()); + assertEquals(x.get(), heap2.deleteMin().get()); size -= 1; - assertEquals(heap2.size(), size); + assertEquals(size, heap2.size()); + } + assertEquals(0, heap2.size()); + assertTrue(heap2.isEmpty()); + } + + @Test(expected = ElementNotFoundException.class) + public void testRemoveEmpty() { + BinaryHeap heap = new BinaryHeap<>(); + heap.remove(new MutableInteger(0)); + } + + @Test(expected = ElementNotFoundException.class) + public void testRemoveNotFound() { + heap1.remove(new MutableInteger(20)); + } + + @Test + public void testRemove() { + // heap 1 + int size1 = heap1.size(); + int[] deleteOrder1 = new int[] { 12, 17, 18, 19, 4, 5, 3, 2, 0, 9, 10, 16, 8, 14, 13, 15, 7, + 6, 1, 11 }; + for (int x: deleteOrder1) { + heap1.remove(this.data1[x]); + assertEquals(--size1, heap1.size()); + } + assertTrue(heap1.isEmpty()); + + // heap 2 + int size2 = heap2.size(); + int[] deleteOrder2 = new int[] { 6, 5, 0, 1, 4, 2, 3 }; + for (int x: deleteOrder2) { + heap2.remove(this.data2[x]); + assertEquals(--size2, heap2.size()); } - assertEquals(heap2.size(), 0); assertTrue(heap2.isEmpty()); } @Test - public void testUpdate() { - MutableInteger newMin = data2[data2.length - 1]; - newMin.set(0); - heap2.update(newMin); - assertEquals(heap2.findMin(), newMin); + public void testRemoveThenAdd() { + MutableInteger mi5 = this.data1[6]; + heap1.remove(mi5); + assertEquals(19, heap1.size()); + mi5.set(-20); + heap1.insert(mi5); + assertEquals(20, heap1.size()); + assertEquals(-20, heap1.findMin().get()); } } diff --git a/src/test/org/insa/algo/utils/BinarySearchTreeTest.java b/src/test/org/insa/algo/utils/BinarySearchTreeTest.java new file mode 100644 index 0000000..5c58442 --- /dev/null +++ b/src/test/org/insa/algo/utils/BinarySearchTreeTest.java @@ -0,0 +1,195 @@ +package org.insa.algo.utils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.stream.IntStream; + +import org.junit.Before; +import org.junit.Test; + +public class BinarySearchTreeTest { + + class MutableInteger implements Comparable { + + // Actual value + private int value; + + public MutableInteger(int value) { + this.value = value; + } + + /** + * @return The integer value stored inside this MutableInteger. + */ + public int get() { + return this.value; + } + + /** + * Update the integer value stored inside this MutableInteger. + * + * @param value New value to set. + */ + public void set(int value) { + this.value = value; + } + + @Override + public int compareTo(MutableInteger other) { + return Integer.compare(this.value, other.value); + } + + }; + + // Raw data arrays. + private MutableInteger[] data1 = IntStream.range(0, 20).mapToObj(MutableInteger::new) + .toArray(MutableInteger[]::new); + private MutableInteger[] data2 = Arrays.stream(new int[] { 8, 1, 6, 3, 4, 5, 9 }) + .mapToObj(MutableInteger::new).toArray(MutableInteger[]::new); + + // Actual searchTree. + private BinarySearchTree searchTree1, searchTree2; + + @Before + public void init() { + // Create the range searchTree + this.searchTree1 = new BinarySearchTree<>(); + this.searchTree2 = new BinarySearchTree<>(); + + for (MutableInteger v: data1) { + this.searchTree1.insert(v); + } + + for (MutableInteger v: data2) { + this.searchTree2.insert(v); + } + } + + @Test + public void testIsEmpty() { + BinarySearchTree tree = new BinarySearchTree<>(); + assertTrue(tree.isEmpty()); + assertFalse(this.searchTree1.isEmpty()); + assertFalse(this.searchTree2.isEmpty()); + } + + @Test + public void testSize() { + BinarySearchTree tree = new BinarySearchTree<>(); + assertEquals(0, tree.size()); + assertEquals(20, this.searchTree1.size()); + assertEquals(7, this.searchTree2.size()); + } + + @Test + public void testInsert() { + BinarySearchTree searchTree = new BinarySearchTree<>(); + int size = 0; + for (MutableInteger x: data1) { + searchTree.insert(x); + assertEquals(++size, searchTree.size()); + } + assertEquals(data1.length, searchTree.size()); + + searchTree = new BinarySearchTree<>(); + size = 0; + for (MutableInteger x: data2) { + searchTree.insert(x); + assertEquals(++size, searchTree.size()); + } + assertEquals(data2.length, searchTree.size()); + } + + @Test(expected = EmptyPriorityQueueException.class) + public void testEmptyFindMin() { + BinarySearchTree searchTree = new BinarySearchTree<>(); + searchTree.findMin(); + } + + @Test + public void testFindMin() { + assertEquals(0, searchTree1.findMin().get()); + assertEquals(1, searchTree2.findMin().get()); + } + + @Test(expected = EmptyPriorityQueueException.class) + public void testEmptyDeleteMin() { + BinarySearchTree searchTree = new BinarySearchTree<>(); + searchTree.deleteMin(); + } + + @Test + public void testDeleteMin() { + // range 1 (sorted) + int size = data1.length; + assertEquals(searchTree1.size(), size); + for (MutableInteger x: data1) { + assertEquals(x, searchTree1.deleteMin()); + size -= 1; + assertEquals(size, searchTree1.size()); + } + assertEquals(0, searchTree1.size()); + assertTrue(searchTree1.isEmpty()); + + // range 2 (was not sorted) + MutableInteger[] range2 = Arrays.copyOf(data2, data2.length); + Arrays.sort(range2); + size = range2.length; + assertEquals(searchTree2.size(), size); + for (MutableInteger x: range2) { + assertEquals(x.get(), searchTree2.deleteMin().get()); + size -= 1; + assertEquals(size, searchTree2.size()); + } + assertEquals(0, searchTree2.size()); + assertTrue(searchTree2.isEmpty()); + } + + @Test(expected = ElementNotFoundException.class) + public void testRemoveEmpty() { + BinarySearchTree searchTree = new BinarySearchTree<>(); + searchTree.remove(new MutableInteger(0)); + } + + @Test(expected = ElementNotFoundException.class) + public void testRemoveNotFound() { + searchTree1.remove(new MutableInteger(20)); + } + + @Test + public void testRemove() { + // searchTree 1 + int size1 = searchTree1.size(); + int[] deleteOrder1 = new int[] { 12, 17, 18, 19, 4, 5, 3, 2, 0, 9, 10, 16, 8, 14, 13, 15, 7, + 6, 1, 11 }; + for (int x: deleteOrder1) { + searchTree1.remove(this.data1[x]); + assertEquals(--size1, searchTree1.size()); + } + assertTrue(searchTree1.isEmpty()); + + // searchTree 2 + int size2 = searchTree2.size(); + int[] deleteOrder2 = new int[] { 6, 5, 0, 1, 4, 2, 3 }; + for (int x: deleteOrder2) { + searchTree2.remove(this.data2[x]); + assertEquals(--size2, searchTree2.size()); + } + assertTrue(searchTree2.isEmpty()); + } + + @Test + public void testRemoveThenAdd() { + MutableInteger mi5 = this.data1[6]; + searchTree1.remove(mi5); + assertEquals(19, searchTree1.size()); + mi5.set(-20); + searchTree1.insert(mi5); + assertEquals(20, searchTree1.size()); + assertEquals(-20, searchTree1.findMin().get()); + } + +}