package org.croudtrip.trips;
import com.google.common.base.Objects;
import org.croudtrip.api.account.User;
import org.croudtrip.api.directions.RouteLocation;
import org.croudtrip.api.trips.JoinTripRequest;
import org.croudtrip.api.trips.JoinTripStatus;
import org.croudtrip.api.trips.SuperTripSubQuery;
import org.croudtrip.api.trips.TripOffer;
import org.croudtrip.api.trips.TripQuery;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import javax.inject.Inject;
/**
* "Solver" for traveling salesman problem (TSP). Be careful though,
* it's using brute force ...
*/
public class TspSolver {
@Inject
TspSolver() {}
/**
* Find the best waypoint order using TSP based on a list of participating {@link org.croudtrip.api.trips.JoinTripRequest}
* for an {@link org.croudtrip.api.trips.TripOffer} and a {@link org.croudtrip.api.trips.TripQuery}
* from a passenger that wants to join this trip.
* @param joinTripRequests all join requests that should be considered, regardless of what state they are in
* @param tripOffer route information for the driver
* @param tripQuery additional way points that should be considered
* @return all possible routes sorted by their air distance (shortes first)
*/
public List<List<TspWayPoint>> getBestOrder(
List<JoinTripRequest> joinTripRequests,
TripOffer tripOffer,
TripQuery tripQuery) {
List<TripRequest> passengerTripRequests = joinTripRequestsToTripRequests(joinTripRequests);
passengerTripRequests.add(new TripRequest(
tripQuery.getPassenger(),
tripQuery.getStartLocation(),
tripQuery.getDestinationLocation()));
TripRequest driverTripRequest = new TripRequest(
tripOffer.getDriver(),
tripOffer.getDriverRoute().getWayPoints().get(0),
tripOffer.getDriverRoute().getWayPoints().get(1));
return getBestOrder(passengerTripRequests, driverTripRequest);
}
/**
* Find the best waypoint order using TSP based on a list of participating {@link org.croudtrip.api.trips.JoinTripRequest}
* for an {@link org.croudtrip.api.trips.TripOffer}.
* @param joinTripRequests all join requests that should be considered, regardless of what state they are in
* @param tripOffer route information for the driver
* @return all possible routes sorted by their air distance (shortes first)
*/
public List<List<TspWayPoint>> getBestOrder(
List<JoinTripRequest> joinTripRequests,
TripOffer tripOffer) {
List<TripRequest> passengerTripRequests = joinTripRequestsToTripRequests(joinTripRequests);
TripRequest driverTripRequest = new TripRequest(
tripOffer.getDriver(),
tripOffer.getDriverRoute().getWayPoints().get(0),
tripOffer.getDriverRoute().getWayPoints().get(1));
return getBestOrder(passengerTripRequests, driverTripRequest);
}
public List<List<TspWayPoint>> getBestOrder(
List<TripRequest> passengerTripRequests,
TripRequest driverTripRequest) {
// get possible passenger routes (not including driver start / end)
List<List<TspWayPoint>> passengerPermutations = new ArrayList<>();
findAllPassengerPermutations(
passengerTripRequests,
new LinkedList<TspWayPoint>(),
passengerPermutations);
// compute distances of routes (including driver start / end)
Map<Long, List<TspWayPoint>> sortedRoutes = new TreeMap<>(); // distance <--> route
for (List<TspWayPoint> passengerPermutation : passengerPermutations) {
long totalDistance = 0;
passengerPermutation.add(0, new TspWayPoint(driverTripRequest.getUser(), driverTripRequest.getStart(), true));
passengerPermutation.add(new TspWayPoint(driverTripRequest.getUser(), driverTripRequest.getEnd(), false));
for (int i = 0; i < passengerPermutation.size() - 1; ++i) {
totalDistance += passengerPermutation.get(i).getLocation().distanceFrom(passengerPermutation.get(i+1).getLocation());
}
sortedRoutes.put(totalDistance, passengerPermutation);
}
return new ArrayList<>(sortedRoutes.values());
}
private void findAllPassengerPermutations(
List<TripRequest> passengerTripRequests,
LinkedList<TspWayPoint> routeBuilder,
List<List<TspWayPoint>> resultRoutes) {
boolean isRouteComplete = true;
for (int passenger = 0; passenger < passengerTripRequests.size(); ++passenger) {
TripRequest tripRequest = passengerTripRequests.get(passenger);
if (tripRequest.start != null) {
// a start location has not yet been included in route
isRouteComplete = false;
RouteLocation nextLocation = tripRequest.start;
routeBuilder.addLast(new TspWayPoint(tripRequest.getUser(), nextLocation, true));
tripRequest.start = null;
findAllPassengerPermutations(passengerTripRequests, routeBuilder, resultRoutes);
routeBuilder.removeLast();
tripRequest.start = nextLocation;
} else if (tripRequest.end != null) {
// an end location has not yet been included in route
isRouteComplete = false;
RouteLocation nextLocation = tripRequest.end;
routeBuilder.addLast(new TspWayPoint(tripRequest.getUser(), nextLocation, false));
tripRequest.end = null;
findAllPassengerPermutations(passengerTripRequests, routeBuilder, resultRoutes);
routeBuilder.removeLast();
tripRequest.end = nextLocation;
}
}
if (isRouteComplete) {
resultRoutes.add(new ArrayList<>(routeBuilder));
}
}
private List<TripRequest> joinTripRequestsToTripRequests(List<JoinTripRequest> joinTripRequests) {
List<TripRequest> tripRequests = new ArrayList<>();
for (JoinTripRequest joinTripRequest : joinTripRequests) {
SuperTripSubQuery subQuery = joinTripRequest.getSubQuery();
TripRequest tripRequest = new TripRequest(joinTripRequest.getSuperTrip().getQuery().getPassenger());
switch (joinTripRequest.getStatus()) {
case DRIVER_ACCEPTED:
tripRequest.setStart(subQuery.getStartLocation());
case PASSENGER_IN_CAR:
tripRequest.setEnd(subQuery.getDestinationLocation());
}
// add the trip request to the final list.
if( joinTripRequest.getStatus() == JoinTripStatus.DRIVER_ACCEPTED ||
joinTripRequest.getStatus() == JoinTripStatus.PASSENGER_IN_CAR)
tripRequests.add(tripRequest);
}
return tripRequests;
}
public static class TripRequest {
private final User user;
private RouteLocation start, end;
public TripRequest(User user) {
this.user = user;
}
public TripRequest(User user, RouteLocation start, RouteLocation end) {
this.user = user;
this.start = start;
this.end = end;
}
public User getUser() {
return user;
}
public RouteLocation getStart() {
return start;
}
public void setStart(RouteLocation start) {
this.start = start;
}
public RouteLocation getEnd() {
return end;
}
public void setEnd(RouteLocation end) {
this.end = end;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TripRequest that = (TripRequest) o;
return Objects.equal(user, that.user) &&
Objects.equal(start, that.start) &&
Objects.equal(end, that.end);
}
@Override
public int hashCode() {
return Objects.hashCode(user, start, end);
}
}
public static class TspWayPoint {
private final User user;
private final RouteLocation location;
private final boolean isStart;
public TspWayPoint(User user, RouteLocation location, boolean isStart) {
this.user = user;
this.location = location;
this.isStart = isStart;
}
public User getUser() {
return user;
}
public RouteLocation getLocation() {
return location;
}
public boolean isStart() {
return isStart;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TspWayPoint tspWayPoint = (TspWayPoint) o;
return Objects.equal(isStart, tspWayPoint.isStart) &&
Objects.equal(user, tspWayPoint.user) &&
Objects.equal(location, tspWayPoint.location);
}
@Override
public int hashCode() {
return Objects.hashCode(user, location, isStart);
}
}
}