modified graph

This commit is contained in:
Ksan 2025-08-01 19:34:19 +02:00
parent 5f68391cdc
commit 167dea9451
5 changed files with 308 additions and 159 deletions

View File

@ -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;
}

View File

@ -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 + '}';
}
}

View File

@ -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() {

View File

@ -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;
}

View File

@ -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) {