modified graph
This commit is contained in:
parent
5f68391cdc
commit
167dea9451
@ -5,167 +5,291 @@ import dev.ksan.travelpathoptimizer.model.Departure;
|
||||
import dev.ksan.travelpathoptimizer.model.Location;
|
||||
import dev.ksan.travelpathoptimizer.service.CityManager;
|
||||
import dev.ksan.travelpathoptimizer.util.JsonParser;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.Set;
|
||||
import java.time.LocalTime;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class Graph {
|
||||
private City[][] matrix;
|
||||
private List<PathResult> allPaths = new ArrayList<>();
|
||||
private int pathIdCounter = 1;
|
||||
|
||||
private static int nextid = 0;
|
||||
private static Set<String> visitedRoutes = new HashSet<>();
|
||||
public static PriorityQueue<PathResult> topPaths = new PriorityQueue<>(5, Comparator.comparingDouble(PathResult::getCost).reversed());
|
||||
public static void main(String[] args) {
|
||||
List<City> cities = JsonParser.parseCities("transport_data.json", "stations");
|
||||
List<Departure> departures = JsonParser.getDeparturesList("transport_data.json", "departures");
|
||||
for (Departure dep : departures) {
|
||||
for (City city : cities) {
|
||||
if (dep.getTo().equals(city.getName())) {
|
||||
dep.setToCity(city);
|
||||
}
|
||||
}
|
||||
}
|
||||
cities = JsonParser.loadDepartures(cities, departures);
|
||||
City[][] map = JsonParser.loadMap("transport_data.json", "countryMap", cities);
|
||||
|
||||
Graph graph = new Graph(map);
|
||||
cities = JsonParser.loadDepartures(cities, departures);
|
||||
System.out.println(cities.getFirst().getName() + " do " + cities.get(3).getName());
|
||||
System.out.println(cities.getFirst().getName() + " do " + cities.getLast().getName());
|
||||
for (City city : cities) {
|
||||
CityManager.addCity(city);
|
||||
}
|
||||
Map<Location, Double> result =
|
||||
graph.calculateShortestPath(cities.getFirst(), cities.get(3), "time");
|
||||
System.out.println(cities.get(3).getName() + " = " + result.get(cities.get(3).getLocation()));
|
||||
List<PathResult> ranked = graph.calculateRankedPaths(cities.getFirst(), cities.get(3), "time");
|
||||
for (PathResult path : ranked) {
|
||||
String cityNames =
|
||||
path.getPath().stream().map(City::getName).reduce((x, y) -> x + " -> " + y).orElse("");
|
||||
|
||||
System.out.println("Path ID: " + path.getId());
|
||||
System.out.println("Cities: " + cityNames);
|
||||
System.out.println("Cost: " + path.getCost());
|
||||
Map<Location, Double> result =
|
||||
graph.calculateShortestPath(cities.getFirst(), cities.getLast(), "time");
|
||||
System.out.println(
|
||||
cities.get(1).getName() + " = " + result.get(cities.getLast().getLocation()));
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
graph.calculateTopPaths(cities.getFirst(), cities.getLast());
|
||||
|
||||
List<PathResult> sorted = new ArrayList<>(graph.topPaths);
|
||||
sorted.sort(Comparator.comparingDouble(PathResult::getCost));
|
||||
for (PathResult res : sorted) {
|
||||
System.out.println("Path ID: " + res.getId());
|
||||
System.out.print("Cities: ");
|
||||
for (City city : res.getPath()) {
|
||||
System.out.print(city.getName() + " → ");
|
||||
}
|
||||
System.out.println("End");
|
||||
|
||||
System.out.println("Departures:");
|
||||
for (Departure dep : path.getDepartures()) {
|
||||
System.out.println(
|
||||
" "
|
||||
+ dep.getDestinationCity().getName()
|
||||
+ " (Price: "
|
||||
+ dep.getPrice()
|
||||
+ ", Time: "
|
||||
+ dep.getDuration()
|
||||
+ ")");
|
||||
}
|
||||
|
||||
System.out.println("-----");
|
||||
|
||||
System.out.println("Total Cost: " + res.getCost() + " min");
|
||||
System.out.println("Arrival Time: " + res.getArrivalTime());
|
||||
System.out.println("--------------------------------------------------");
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
List<City> path = new ArrayList<>();
|
||||
List<Integer> departuress = new ArrayList<>();
|
||||
LocalTime currentTime = LocalTime.of(1, 0); // Assume starting at 8:00 AM
|
||||
double totalCost = 0.0;
|
||||
|
||||
calculateTopPaths(
|
||||
cities.getFirst(), cities.getLast(), path, totalCost, currentTime, departuress);
|
||||
System.out.println(topPaths.size());
|
||||
// Output the top 5 paths
|
||||
printTopPaths(); }
|
||||
|
||||
|
||||
public static void addToTopPaths(PathResult newPath) {
|
||||
String routeHash = generateRouteHash(newPath.getDeparturesUsed());
|
||||
|
||||
if (visitedRoutes.contains(routeHash)) {
|
||||
return;
|
||||
}
|
||||
|
||||
visitedRoutes.add(routeHash);
|
||||
|
||||
if (topPaths.size() < 5) {
|
||||
topPaths.offer(newPath);
|
||||
} else {
|
||||
if (newPath.getCost() < topPaths.peek().getCost()) {
|
||||
System.out.println("Removing path with cost: " + topPaths.peek().getCost());
|
||||
topPaths.poll();
|
||||
topPaths.offer(newPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasCycle(List<City> path) {
|
||||
Set<String> seen = new HashSet<>();
|
||||
for (City c : path) {
|
||||
if (!seen.add(c.getName())) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public List<PathResult> calculateRankedPaths(City startCity, City endCity, String type) {
|
||||
|
||||
List<PathResult> allFoundPaths = new ArrayList<>();
|
||||
|
||||
PriorityQueue<PathState> pq = new PriorityQueue<>(Comparator.comparingDouble(s -> s.cost));
|
||||
pq.add(new PathState(startCity, new ArrayList<>(List.of(startCity)), new ArrayList<>(), 0.0));
|
||||
|
||||
Set<String> visitedPaths = new HashSet<>();
|
||||
|
||||
while (!pq.isEmpty()) {
|
||||
PathState current = pq.poll();
|
||||
City currentCity = current.getCity();
|
||||
|
||||
String pathKey = current.getPathSignature();
|
||||
// if (visitedPaths.contains(pathKey)) continue;
|
||||
if (hasCycle(current.getPath())) continue;
|
||||
visitedPaths.add(pathKey);
|
||||
|
||||
if (currentCity.getName().equals(endCity.getName())) {
|
||||
|
||||
allFoundPaths.add(
|
||||
new PathResult(
|
||||
pathIdCounter++,
|
||||
new ArrayList<>(current.getPath()),
|
||||
new ArrayList<>(current.getDepartures()),
|
||||
current.getCost()));
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Departure dep : currentCity.getDestinations()) {
|
||||
City nextCity = dep.getDestinationCity();
|
||||
double additionalCost =
|
||||
switch (type) {
|
||||
case "price" -> dep.getPrice();
|
||||
case "time" -> dep.getDuration();
|
||||
case "hops" -> 1.0;
|
||||
default -> throw new IllegalArgumentException("Invalid type: " + type);
|
||||
};
|
||||
|
||||
List<City> newPath = new ArrayList<>(current.getPath());
|
||||
newPath.add(nextCity);
|
||||
|
||||
List<Departure> newDepartures = new ArrayList<>(current.getDepartures());
|
||||
newDepartures.add(dep);
|
||||
|
||||
pq.add(new PathState(nextCity, newPath, newDepartures, current.getCost() + additionalCost));
|
||||
}
|
||||
}
|
||||
|
||||
// allFoundPaths.sort(Comparator.comparingDouble(PathResult::getCost));
|
||||
|
||||
// int toStore = Math.max(5, (int) Math.ceil(allFoundPaths.size() * 0.05));
|
||||
|
||||
// List<PathResult> selectedPaths = allFoundPaths.subList(0, Math.min(toStore,
|
||||
// allFoundPaths.size()));
|
||||
|
||||
Map<String, PathResult> bestPaths = new HashMap<>();
|
||||
|
||||
for (PathResult pathResult : allFoundPaths) {
|
||||
String signature = getRichPathSignature(pathResult.getPath(), pathResult.getDepartures());
|
||||
|
||||
bestPaths.merge(
|
||||
signature,
|
||||
pathResult,
|
||||
(existing, candidate) -> candidate.getCost() < existing.getCost() ? candidate : existing);
|
||||
}
|
||||
|
||||
List<PathResult> selectedPaths =
|
||||
bestPaths.values().stream()
|
||||
.sorted(Comparator.comparingDouble(PathResult::getCost))
|
||||
.limit(5)
|
||||
.toList();
|
||||
|
||||
allPaths.clear();
|
||||
allPaths.addAll(selectedPaths);
|
||||
return selectedPaths;
|
||||
}
|
||||
|
||||
private String getRichPathSignature(List<City> path, List<Departure> departures) {
|
||||
private static String generateRouteHash(List<Integer> departures) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < path.size(); i++) {
|
||||
City city = path.get(i);
|
||||
sb.append(city.getLocation().toString());
|
||||
if (i > 0) {
|
||||
Departure dep = departures.get(i - 1);
|
||||
sb.append(":")
|
||||
.append(dep.getType())
|
||||
.append("-")
|
||||
.append(dep.getDuration())
|
||||
.append("-")
|
||||
.append(dep.getPrice());
|
||||
}
|
||||
if (i < path.size() - 1) {
|
||||
sb.append("->");
|
||||
}
|
||||
for (Integer depId : departures) {
|
||||
sb.append(depId).append("-");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
public static void calculateTopPaths(
|
||||
City currentCity,
|
||||
City endCity,
|
||||
List<City> path,
|
||||
double totalCost,
|
||||
LocalTime currentTime,
|
||||
List<Integer> departures) {
|
||||
|
||||
if (currentCity.getLocation().equals(endCity.getLocation())) {
|
||||
|
||||
addToTopPaths(new PathResult(nextid++, new ArrayList<>(path), new ArrayList<>(departures), totalCost, currentTime));
|
||||
}
|
||||
|
||||
for (Departure dep : currentCity.getDestinations()) {
|
||||
if (dep.getDepartureTime().isBefore(currentTime)) continue;
|
||||
City nextCity;
|
||||
if (path.contains(dep.getDestinationCity())) {
|
||||
continue;
|
||||
} else {
|
||||
nextCity = dep.getDestinationCity();
|
||||
}
|
||||
LocalTime arrivalTime = dep.getDepartureTime().plusMinutes(dep.getDuration());
|
||||
|
||||
if (topPaths.size() >= 5 && totalCost + dep.getDuration() > topPaths.peek().getCost()) {
|
||||
return;
|
||||
}
|
||||
path.add(nextCity);
|
||||
departures.add(dep.getIdCounter());
|
||||
calculateTopPaths(
|
||||
nextCity, endCity, path, totalCost + dep.getDuration(), arrivalTime, departures);
|
||||
|
||||
departures.remove(departures.size() - 1);
|
||||
path.remove(path.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
public static void printTopPaths() {
|
||||
System.out.println("Top 5 Paths:");
|
||||
int rank = 5;
|
||||
while (!topPaths.isEmpty()) {
|
||||
PathResult pathResult = topPaths.poll();
|
||||
System.out.println("Rank: " + rank--);
|
||||
System.out.println("ID: " + pathResult.getId());
|
||||
System.out.println("Cost: " + pathResult.getCost());
|
||||
System.out.println("Path: " + pathResult.getPath().size());
|
||||
System.out.println("Departures: " + pathResult.getDeparturesUsed());
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
private PriorityQueue<PathResult> topPaths =
|
||||
new PriorityQueue<>(Comparator.comparingDouble(PathResult::getCost).reversed());
|
||||
private final AtomicInteger pathIdGenerator = new AtomicInteger(1); // Start from 1
|
||||
private final Set<String> uniquePathKeys = new HashSet<>();
|
||||
|
||||
private int makeDeparturePathHash(Set<Departure> departures) {
|
||||
// A simple approach: sum of hashes or use Objects.hash(...)
|
||||
return departures.stream()
|
||||
.mapToInt(
|
||||
dep ->
|
||||
Objects.hash(
|
||||
dep.getDepartureTime()
|
||||
.toSecondOfDay(), // convert time to int seconds for consistency
|
||||
dep.getDestinationCity()
|
||||
.getLocation(), // assuming Location implements hashCode properly
|
||||
Double.valueOf(dep.getDuration()).hashCode()))
|
||||
.sorted() // sort to avoid order affecting hash
|
||||
.reduce(1, (a, b) -> 31 * a + b); // combine with a hash combiner
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private HashSet<Integer> uniquePathHashes = new HashSet<Integer>();
|
||||
public void calculateTopPaths(City start, City end) {
|
||||
topPaths.clear();
|
||||
uniquePathKeys.clear();
|
||||
pathIdGenerator.set(1);
|
||||
|
||||
for (Departure dep : start.getDestinations()) {
|
||||
City next = dep.getDestinationCity();
|
||||
double duration = dep.getDuration();
|
||||
LocalTime depTime = dep.getDepartureTime();
|
||||
LocalTime arrivalTime = depTime.plusMinutes((long) duration);
|
||||
|
||||
List<City> initialPath = new ArrayList<>();
|
||||
Set<Departure> usedDepartures = new HashSet<>();
|
||||
usedDepartures.add(dep);
|
||||
|
||||
initialPath.add(start); // include start
|
||||
initialPath.add(next); // move to first destination
|
||||
List<Integer> initialDepartures = new ArrayList<>();
|
||||
initialDepartures.add(dep.getIdCounter());
|
||||
Set<Location> visited = new HashSet<>();
|
||||
visited.add(start.getLocation());
|
||||
|
||||
findPaths(
|
||||
next,
|
||||
end,
|
||||
initialPath,
|
||||
usedDepartures,
|
||||
initialDepartures,
|
||||
duration,
|
||||
arrivalTime,
|
||||
visited);
|
||||
}
|
||||
}
|
||||
|
||||
public void findPaths(
|
||||
City current,
|
||||
City end,
|
||||
List<City> pathSoFar,
|
||||
Set<Departure> usedDepartures,
|
||||
List<Integer> departuresSoFar,
|
||||
double totalCost,
|
||||
LocalTime currentTime,
|
||||
Set<Location> visitedCities
|
||||
) {
|
||||
if (visitedCities.contains(current.getLocation())) return;
|
||||
|
||||
visitedCities.add(current.getLocation());
|
||||
|
||||
if (current.getLocation().equals(end.getLocation())) {
|
||||
int pathHash = makeDeparturePathHash(usedDepartures);
|
||||
if (uniquePathHashes.contains(pathHash)) {
|
||||
visitedCities.remove(current.getLocation());
|
||||
return;
|
||||
}
|
||||
|
||||
uniquePathHashes.add(pathHash);
|
||||
int pathId = pathIdGenerator.getAndIncrement();
|
||||
topPaths.add(
|
||||
new PathResult(
|
||||
pathId,
|
||||
new ArrayList<>(pathSoFar),
|
||||
new ArrayList<>(departuresSoFar),
|
||||
totalCost,
|
||||
currentTime));
|
||||
|
||||
if (topPaths.size() > 5) topPaths.poll();
|
||||
|
||||
visitedCities.remove(current.getLocation()); // backtrack
|
||||
return;
|
||||
}
|
||||
|
||||
for (Departure dep : current.getDestinations()) {
|
||||
if (usedDepartures.contains(dep)) continue;
|
||||
if (dep.getDepartureTime().isBefore(currentTime)) continue;
|
||||
|
||||
City next = dep.getDestinationCity();
|
||||
if (visitedCities.contains(next.getLocation())) continue; // skip if already visited
|
||||
|
||||
LocalTime arrivalTime = dep.getDepartureTime().plusMinutes((long) dep.getDuration());
|
||||
|
||||
usedDepartures.add(dep);
|
||||
departuresSoFar.add(dep.getIdCounter());
|
||||
pathSoFar.add(next);
|
||||
if (!(topPaths.peek() == null)) {
|
||||
if (totalCost + dep.getDuration() > topPaths.peek().getCost()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
findPaths(
|
||||
next,
|
||||
end,
|
||||
pathSoFar,
|
||||
usedDepartures,
|
||||
departuresSoFar,
|
||||
totalCost + dep.getDuration(),
|
||||
arrivalTime,
|
||||
visitedCities);
|
||||
|
||||
// backtrack
|
||||
pathSoFar.remove(pathSoFar.size() - 1);
|
||||
departuresSoFar.remove(departuresSoFar.size() - 1);
|
||||
usedDepartures.remove(dep);
|
||||
}
|
||||
|
||||
visitedCities.remove(current.getLocation()); // backtrack
|
||||
}
|
||||
*/
|
||||
public Map<Location, Double> calculateShortestPath(City startCity, City endCity, String type) {
|
||||
int n = matrix.length;
|
||||
int m = matrix[0].length;
|
||||
@ -186,7 +310,6 @@ public class Graph {
|
||||
while (!pq.isEmpty()) {
|
||||
City current = pq.poll();
|
||||
|
||||
// If we reached the end city, we can stop early
|
||||
if (current == endCity) {
|
||||
break;
|
||||
}
|
||||
@ -215,6 +338,8 @@ public class Graph {
|
||||
return distances;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public List<PathResult> getAllPaths() {
|
||||
return allPaths;
|
||||
}
|
||||
|
@ -2,50 +2,47 @@ package dev.ksan.travelpathoptimizer.graph;
|
||||
|
||||
import dev.ksan.travelpathoptimizer.model.City;
|
||||
import dev.ksan.travelpathoptimizer.model.Departure;
|
||||
|
||||
import java.time.LocalTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class PathResult {
|
||||
private int id;
|
||||
private List<City> path;
|
||||
private List<Departure> departures;
|
||||
private double cost;
|
||||
private int id;
|
||||
private List<City> path = new ArrayList<>();
|
||||
private List<Integer> departuresUsed = new ArrayList<>();
|
||||
private double cost;
|
||||
private LocalTime arrivalTime;
|
||||
|
||||
public PathResult(int id, List<City> path, List<Departure> departures, double cost) {
|
||||
public PathResult(int id, List<City> path, List<Integer> departuresUsed, double cost, LocalTime arrivalTime) {
|
||||
this.id = id;
|
||||
this.path = path;
|
||||
this.departures = departures;
|
||||
this.departuresUsed = departuresUsed;
|
||||
this.cost = cost;
|
||||
this.arrivalTime = arrivalTime;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public List<City> getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setPath(List<City> path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public List<Departure> getDepartures() {
|
||||
return departures;
|
||||
}
|
||||
|
||||
public void setDepartures(List<Departure> departures) {
|
||||
this.departures = departures;
|
||||
public List<Integer> getDeparturesUsed() {
|
||||
return departuresUsed;
|
||||
}
|
||||
|
||||
public double getCost() {
|
||||
return cost;
|
||||
}
|
||||
|
||||
public void setCost(double cost) {
|
||||
this.cost = cost;
|
||||
public LocalTime getArrivalTime() {
|
||||
return arrivalTime;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PathResult{id=" + id + " cost = " + cost + ", path=" + path + ", arrivalTime=" + arrivalTime + '}';
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,14 @@ public class PathState {
|
||||
}
|
||||
|
||||
public String getPathSignature() {
|
||||
return path.stream().map(c -> c.getLocation().toString()).collect(Collectors.joining("->"));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < path.size(); i++) {
|
||||
sb.append(path.get(i).getLocation().toString());
|
||||
if (i < path.size() - 1) {
|
||||
sb.append("->");
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public City getCity() {
|
||||
|
@ -13,7 +13,9 @@ public class Departure {
|
||||
private LocalTime departureTime;
|
||||
private double price;
|
||||
private int minTransferTime;
|
||||
|
||||
private City toCity;
|
||||
private static int idCounter = 0;
|
||||
private int id;
|
||||
public Departure(
|
||||
TransportType type,
|
||||
String from,
|
||||
@ -22,6 +24,7 @@ public class Departure {
|
||||
int duration,
|
||||
double price,
|
||||
int minTransferTime) {
|
||||
this.id = idCounter++;
|
||||
this.type = type;
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
@ -30,12 +33,21 @@ public class Departure {
|
||||
this.price = price;
|
||||
this.minTransferTime = minTransferTime;
|
||||
}
|
||||
|
||||
public int getIdCounter() {
|
||||
return id;
|
||||
}
|
||||
public void setToCity(City toCity) {
|
||||
this.toCity = toCity;
|
||||
}
|
||||
public City getDestinationCity() {
|
||||
|
||||
return CityManager.getCityByName(to);
|
||||
return toCity;
|
||||
// return CityManager.getCityByName(to);
|
||||
}
|
||||
|
||||
public LocalTime getDepartureTime() {
|
||||
return departureTime;
|
||||
}
|
||||
public TransportType getType() {
|
||||
return type;
|
||||
}
|
||||
|
@ -1,18 +1,26 @@
|
||||
package dev.ksan.travelpathoptimizer.service;
|
||||
|
||||
import dev.ksan.travelpathoptimizer.model.City;
|
||||
import dev.ksan.travelpathoptimizer.model.Location;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class CityManager {
|
||||
private static Map<String, City> cities = new HashMap<>();
|
||||
private static Map<Location, City> citiesByLocation = new HashMap<>();
|
||||
|
||||
public static void clear() {
|
||||
citiesByLocation.clear();
|
||||
cities.clear();
|
||||
}
|
||||
|
||||
public static void addCity(City city) {
|
||||
cities.put(city.getName(), city);
|
||||
citiesByLocation.put(city.getLocation(), city);
|
||||
}
|
||||
|
||||
public static City getCityByLocation(Location loc) {
|
||||
return citiesByLocation.get(loc);
|
||||
}
|
||||
|
||||
public static City getCityByName(String cityName) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user