package vroom.common.modeling.dataModel; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import vroom.common.utilities.Utilities; /** * The Class <code>DoublyLinkedRoute</code> is an extension of {@link RouteBase} that stores a route in the form of a * permutation. * <ul> * <li>{@link #getNodePosition(INodeVisit)} : O(n)</li> * </ul> * <p> * <p> * <b>WARNING: This implementation needs debugging and refactoring, in particular it does not support repeated depot id, * and the array of predecessors and successors should be stored at the {@link Solution} level</b> * </p> * Creation date: Feb 15, 2011 - 6:43:25 PM. * * @author Victor Pillac, <a href="http://uniandes.edu.co">Universidad de Los Andes</a>-<a * href="http://copa.uniandes.edu.co">Copa</a> <a href="http://www.emn.fr">Ecole des Mines de Nantes</a>-<a * href="http://www.irccyn.ec-nantes.fr/irccyn/d/en/equipes/Slp">SLP</a> * @version 1.0 */ public class DoublyLinkedRoute extends RouteBase { // FIXME debug and move the pred/succ arrays to the parent solution /** The Constant UNDEFINED. */ private final static int UNDEFINED = -2; /** The Predecessor of each node */ private final int[] mPred; /** The Successor of each node */ private final int[] mSucc; /** The Nodes. */ private final INodeVisit[] mNodes; /** The Cumulated cost at each node */ private final double[] mCumulatedCost; /** The First Node ID. */ private int mFirst; /** The Last Node ID. */ private int mLast; /** The Length. */ private int mLength = 0; /** The max id of the parent instance nodevisits */ private final int mOriginalMaxId; /** * Instantiates a new doubly linked tsp route. * * @param parentSolution * the parent solution * @param vehicle * the vehicle */ public DoublyLinkedRoute(IVRPSolution<?> parentSolution, Vehicle vehicle) { super(parentSolution, vehicle); int maxId = Utilities.getMaxId(parentSolution.getParentInstance().getNodeVisits()) + 1; mOriginalMaxId = maxId; // Allow for a copy of each depot maxId += Utilities.getMaxId(parentSolution.getParentInstance().getDepotsVisits()) + 1; mPred = new int[maxId]; mSucc = new int[maxId]; mCumulatedCost = new double[maxId]; mNodes = new INodeVisit[maxId]; Arrays.fill(mPred, UNDEFINED); Arrays.fill(mSucc, UNDEFINED); Arrays.fill(mCumulatedCost, 0); // Store the nodes for faster access for (INodeVisit d : getParentSolution().getParentInstance().getDepotsVisits()) { cacheNodeVisit(d, d.getID()); cacheNodeVisit(d, getDepotDuplicateID(d)); } for (INodeVisit n : getParentSolution().getParentInstance().getNodeVisits()) { cacheNodeVisit(n, n.getID()); } mFirst = UNDEFINED; mLast = UNDEFINED; mLength = 0; } private void cacheNodeVisit(INodeVisit n, int id) { if (mNodes[id] != null) throw new IllegalStateException(String.format("Two nodes have the same id: %s and %s", mNodes[n.getID()], n)); mNodes[id] = n; } /** * Returns the id corresponding to the duplicate of depot * * @param depot * @return the id corresponding to the duplicate of depot * @author vpillac */ private int getDepotDuplicateID(INodeVisit depot) { return depot.getID() + mOriginalMaxId; } /** * Returns the id corresponding to the original depot * * @param depotDuplicateId * the id of the depot duplicate * @return the id corresponding to the original depot * @author vpillac */ private int getDepotOriginalID(int depotDuplicateId) { return depotDuplicateId - mOriginalMaxId; } /** * Creates a new <code>DoublyLinkedRoute</code> by cloning the given instance * * @param doublyLinkedRoute */ private DoublyLinkedRoute(DoublyLinkedRoute original) { super(original.getParentSolution(), original.getVehicle()); this.mPred = Arrays.copyOf(original.mPred, original.mPred.length); this.mSucc = Arrays.copyOf(original.mSucc, original.mSucc.length); this.mCumulatedCost = Arrays .copyOf(original.mCumulatedCost, original.mCumulatedCost.length); this.mNodes = Arrays.copyOf(original.mNodes, original.mNodes.length); this.mFirst = original.mFirst; this.mLast = original.mLast; this.mLength = original.mLength; mOriginalMaxId = original.mOriginalMaxId; } /* * (non-Javadoc) * * @see vroom.common.modeling.dataModel.IRoute#getNodePosition(vroom.common. * modelling.dataModel.INodeVisit) */ @Override public int getNodePosition(INodeVisit node) { // Complexity : O(n) int cursor = 0; int pred = node.getID(); while (this.mPred[pred] != UNDEFINED) { pred = this.mPred[pred]; cursor++; } return cursor; } /* * (non-Javadoc) * * @see vroom.common.modeling.dataModel.IRoute#getFirstNode() */ @Override public INodeVisit getFirstNode() { return getNode(mFirst); } /* * (non-Javadoc) * * @see vroom.common.modeling.dataModel.IRoute#getLastNode() */ @Override public INodeVisit getLastNode() { return getNode(mLast); } /* * (non-Javadoc) * * @see vroom.common.modeling.dataModel.IRoute#iterator() */ @Override public ListIterator<INodeVisit> iterator() { return new DoublyLinkedRouteIterator(); } /** * Gets the node id at. * * @param index * the index * @return the node id at */ private int getNodeIdAt(int index) { if (index < length() / 2) { int cursor = 0; int node = mFirst; while (cursor < index) { cursor++; node = this.mSucc[node]; } return node; } else { int cursor = length() - 1; int node = mLast; while (cursor > index) { cursor--; node = this.mPred[node]; } return node; } } /* * (non-Javadoc) * * @see vroom.common.modeling.dataModel.RouteBase#getNodeAtImplem(int) */ @Override protected INodeVisit getNodeAtImplem(int index) { return getNode(getNodeIdAt(index)); } /* * (non-Javadoc) * * @see vroom.common.modeling.dataModel.RouteBase#subrouteImplem(int, int) */ @Override protected List<INodeVisit> subrouteImplem(int start, int end) { List<INodeVisit> subroute = new ArrayList<INodeVisit>(end - start + 1); int node = getNodeIdAt(start); int cursor = start; while (cursor < end) { subroute.add(getNode(node)); node = this.mSucc[node]; cursor++; } return subroute; } /* * (non-Javadoc) * * @see * vroom.common.modeling.dataModel.RouteBase#appendNodeImplem(vroom.common * .modelling.dataModel.INodeVisit) */ @Override protected boolean appendNodeImplem(INodeVisit node) { int nodeId = checkAddNode(node); // Ignore if route is empty if (mLast != UNDEFINED) { this.mSucc[mLast] = nodeId; this.mPred[nodeId] = mLast; } else { // Initialize the route this.mFirst = nodeId; } this.mLast = nodeId; mLength++; return true; } /* * (non-Javadoc) * * @see * vroom.common.modeling.dataModel.RouteBase#appendNodesImplem(java.util * .List) */ @Override protected boolean appendNodesImplem(List<? extends INodeVisit> nodes) { for (INodeVisit n : nodes) { appendNodeImplem(n); } return true; } /* * (non-Javadoc) * * @see * vroom.common.modeling.dataModel.RouteBase#appendRouteImplem(vroom.common * .modelling.dataModel.IRoute) */ @Override protected boolean appendRouteImplem(IRoute<? extends INodeVisit> appendedRoute) { return appendNodesImplem(appendedRoute.getNodeSequence()); } /* * (non-Javadoc) * * @see vroom.common.modeling.dataModel.RouteBase#setNodeAtImplem(int, * vroom.common.modeling.dataModel.INodeVisit) */ @Override protected INodeVisit[] setNodeAtImplem(int index, INodeVisit node) { int current = getNodeIdAt(index); if (current != node.getID()) { int nodeId = checkAddNode(node); if (this.mPred[current] != UNDEFINED) this.mSucc[this.mPred[current]] = nodeId; if (this.mSucc[current] != UNDEFINED) this.mPred[this.mSucc[current]] = nodeId; this.mPred[nodeId] = this.mPred[current]; this.mSucc[nodeId] = this.mSucc[current]; this.mPred[current] = UNDEFINED; this.mSucc[current] = UNDEFINED; } return new INodeVisit[] { getNode(this.mPred[node.getID()]), getNode(current), getNode(this.mSucc[node.getID()]) }; } /* * (non-Javadoc) * * @see vroom.common.modeling.dataModel.RouteBase#extractNodeImplem(int) */ @Override protected INodeVisit[] extractNodeImplem(int index) { int node = getNodeIdAt(index); if (this.mPred[node] != UNDEFINED) this.mSucc[this.mPred[node]] = this.mSucc[node]; else mFirst = this.mSucc[node]; if (this.mSucc[node] != UNDEFINED) this.mPred[this.mSucc[node]] = this.mPred[node]; else mLast = this.mPred[node]; this.mPred[node] = UNDEFINED; this.mSucc[node] = UNDEFINED; return new INodeVisit[] { getNode(this.mPred[node]), getNode(node), getNode(this.mSucc[node]) }; } /* * (non-Javadoc) * * @see vroom.common.modeling.dataModel.RouteBase#extractSubrouteImplem(int, * int) */ @Override protected Object[] extractSubrouteImplem(int start, int end) { List<INodeVisit> subroute = new ArrayList<INodeVisit>(end - start + 1); int node = getNodeIdAt(start); int pred = getPred(node); int succ = getSucc(node); int index = start; while (index <= end) { subroute.add(getNode(node)); succ = getSucc(node); this.mPred[node] = UNDEFINED; this.mSucc[node] = UNDEFINED; node = succ; index++; } this.mSucc[pred] = succ; this.mPred[succ] = pred; return new Object[] { getNode(pred), subroute, getNode(succ) }; } /* * (non-Javadoc) * * @see vroom.common.modeling.dataModel.RouteBase#extractNodesImplem(int, * int) */ @Override protected Object[] extractNodesImplem(int start, int end) { List<INodeVisit> nodes = new ArrayList<INodeVisit>(end - start + 1); int current = getNodeIdAt(start); int predecessor = this.mPred[current]; int next = this.mSucc[current]; int cursor = start; while (cursor <= end && current != UNDEFINED) { next = this.mSucc[current]; this.mPred[current] = UNDEFINED; this.mSucc[current] = UNDEFINED; current = next; cursor++; } this.mSucc[predecessor] = next; this.mPred[next] = predecessor; return new Object[] { getNode(predecessor), nodes, getNode(next) }; } /* * (non-Javadoc) * * @see vroom.common.modeling.dataModel.RouteBase#removeImplem(vroom.common. * modelling.dataModel.INodeVisit) */ @Override protected INodeVisit[] removeImplem(INodeVisit node) { int n = node.getID(); int pred = this.mPred[n]; int succ = this.mSucc[n]; if (pred != UNDEFINED) this.mSucc[pred] = succ; if (succ != UNDEFINED) this.mPred[succ] = pred; this.mPred[n] = UNDEFINED; this.mSucc[n] = UNDEFINED; return new INodeVisit[] { getNode(pred), getNode(succ) }; } /* * (non-Javadoc) * * @see vroom.common.modeling.dataModel.RouteBase#insertNodeImplem(int, * vroom.common.modeling.dataModel.INodeVisit) */ @Override protected INodeVisit[] insertNodeImplem(int index, INodeVisit node) { int nodeId = checkAddNode(node); int current = getNodeIdAt(index); this.mSucc[this.mPred[current]] = nodeId; this.mPred[nodeId] = this.mPred[current]; this.mPred[current] = nodeId; this.mSucc[nodeId] = current; return new INodeVisit[] { getPredecessor(node), getSuccessor(node) }; } /* * (non-Javadoc) * * @see vroom.common.modeling.dataModel.RouteBase#insertNodesImplem(int, * java.util.List) */ @Override protected INodeVisit[] insertNodesImplem(int index, List<? extends INodeVisit> subroute) { if (subroute.isEmpty()) return new INodeVisit[2]; int current = getNodeIdAt(index); INodeVisit[] r = new INodeVisit[] { getPredecessor(current), getNode(current) }; Iterator<? extends INodeVisit> it = subroute.iterator(); int first = UNDEFINED, last = UNDEFINED; int pred = checkAddNode(it.next()); int succ = pred; first = pred; while (it.hasNext()) { succ = checkAddNode(it.next()); this.mPred[succ] = pred; this.mSucc[pred] = succ; pred = succ; } last = succ; this.mSucc[this.mPred[current]] = first; this.mPred[first] = this.mPred[current]; this.mPred[current] = last; this.mSucc[last] = current; return r; } /* * (non-Javadoc) * * @see vroom.common.modeling.dataModel.RouteBase#reverseSubrouteImplem(int, * int) */ @Override protected INodeVisit[] reverseSubrouteImplem(int start, int end) { int first = getNodeIdAt(start), last = first; int pred = this.mPred[first]; int node = first, succ = first; int index = start; // Reverse the subroute while (index <= end) { succ = this.mSucc[node]; this.mSucc[node] = this.mPred[node]; this.mPred[node] = succ; last = node; node = succ; index++; } // Relink the subroute extremities this.mSucc[first] = succ; this.mPred[succ] = first; this.mSucc[last] = pred; this.mPred[pred] = last; return new INodeVisit[] { getNode(pred), getNode(first), getNode(last), getNode(succ) }; } /** * Check if a node is already present in this route and return the internal id. This method allows for depots to be * added twice. * * @param node * the node that is to be added to this route * @return the internal id for {@code node} * @throws IllegalArgumentException * if {@code node} is already present in this route and cannot be added * @author vpillac */ private int checkAddNode(INodeVisit node) { int id = node.getID(); if (contains(id)) { if (node.isDepot()) { id = getDepotDuplicateID(node); if (contains(id)) { throw new IllegalArgumentException(String.format( "Depot %s is already present twice in this route", node)); } } else { throw new IllegalArgumentException(String.format( "Node %s is already present in this route", node)); } } return id; } /* * (non-Javadoc) * * @see vroom.common.modeling.dataModel.RouteBase#length() */ @Override public int length() { return mLength; } /* * (non-Javadoc) * * @see vroom.common.modeling.dataModel.RouteBase#getNodeSequence() */ @Override public List<INodeVisit> getNodeSequence() { List<INodeVisit> seq = new ArrayList<INodeVisit>(length()); int node = mFirst; while (node != UNDEFINED) { seq.add(getNode(node)); node = this.mSucc[node]; } return seq; } /* * (non-Javadoc) * * @see * vroom.common.modeling.dataModel.RouteBase#contains(vroom.common.modeling * .dataModel.INodeVisit) */ @Override public boolean contains(INodeVisit node) { return contains(node.getID()); } /** * Returns {@code true} if this routes contains the node of id {@code nodeId} * * @param nodeId * @return {@code true} if this routes contains the node of id {@code nodeId} * @author vpillac */ public boolean contains(int nodeId) { return mPred[nodeId] != UNDEFINED || mSucc[nodeId] != UNDEFINED || mFirst == nodeId; } /* * (non-Javadoc) * * @see vroom.common.modeling.dataModel.RouteBase#clone() */ @Override public DoublyLinkedRoute clone() { return new DoublyLinkedRoute(this); } /** * Gets the {@link INodeVisit} associated with a given id. * * @param id * the id * @return the node */ public INodeVisit getNode(int id) { return id >= 0 && id < mNodes.length ? mNodes[id] : null; } /** * Gets the predecessor id. * * @param id * the id * @return the pred */ public int getPred(int id) { return this.mPred[id]; } /** * Gets the predecessor. * * @param id * the id of the considered node * @return the predecessor */ public INodeVisit getPredecessor(int id) { return getNode(getPred(id)); } /** * Gets the predecessor. * * @param node * the node * @return the predecessor */ public INodeVisit getPredecessor(INodeVisit node) { return getNode(getPred(node.getID())); } /** * Gets the successor id * * @param id * the id * @return the succ */ public int getSucc(int id) { return this.mSucc[id]; } /** * Gets the predecessor. * * @param id * the id of the considered node * @return the predecessor */ public INodeVisit getSuccessor(int id) { return getNode(getSucc(id)); } /** * Gets the predecessor. * * @param node * the node * @return the predecessor */ public INodeVisit getSuccessor(INodeVisit node) { return getNode(getSucc(node.getID())); } /** * The Class <code>DoublyLinkedRouteIterator</code> is an implementation of {@link ListIterator} to iterate over a * {@link DoublyLinkedRoute} * <p> * Creation date: Feb 16, 2011 - 5:15:56 PM. * * @author Victor Pillac, <a href="http://uniandes.edu.co">Universidad de Los Andes</a>-<a * href="http://copa.uniandes.edu.co">Copa</a> <a href="http://www.emn.fr">Ecole des Mines de Nantes</a>-<a * href="http://www.irccyn.ec-nantes.fr/irccyn/d/en/equipes/Slp" >SLP</a> * @version 1.0 */ protected class DoublyLinkedRouteIterator implements ListIterator<INodeVisit> { private int current; private int cursor; protected DoublyLinkedRouteIterator() { current = mFirst; cursor = 0; } @Override public boolean hasNext() { return cursor < DoublyLinkedRoute.this.length(); } @Override public boolean hasPrevious() { return cursor > 0; } @Override public INodeVisit next() { if (hasNext()) { cursor++; int c = current; current = DoublyLinkedRoute.this.mSucc[current]; return getNode(c); } else throw new IllegalStateException("Iterator has no next element"); } @Override public INodeVisit previous() { if (hasPrevious()) { cursor--; int c = current; current = DoublyLinkedRoute.this.mPred[current]; return getNode(c); } else throw new IllegalStateException("Iterator has no previous element"); } @Override public int nextIndex() { return cursor + 1; } @Override public int previousIndex() { return cursor - 1; } @Override public void remove() { throw new UnsupportedOperationException( "remove operation is not supported by this iterator"); } @Override public void set(INodeVisit e) { throw new UnsupportedOperationException( "set operation is not supported by this iterator"); } @Override public void add(INodeVisit e) { throw new UnsupportedOperationException( "add operation is not supported by this iterator"); } } }