diff --git a/src/main/java/dev/ksan/travelpathoptimizer/controller/MainController.java b/src/main/java/dev/ksan/travelpathoptimizer/controller/MainController.java index 9fb9d23..321e12b 100644 --- a/src/main/java/dev/ksan/travelpathoptimizer/controller/MainController.java +++ b/src/main/java/dev/ksan/travelpathoptimizer/controller/MainController.java @@ -27,6 +27,7 @@ public class MainController { @FXML private GridPane map; @FXML private Label welcomeText; @FXML private Text selectedFileText; + @FXML private Button mapSelectButton; private City startCity = null; private City endCity = null; private boolean selectingStart = false; @@ -37,7 +38,6 @@ public class MainController { @FXML private Button endCityButton; City[][] cities; - private CityManager cityManager; private File selectedFile; @FXML private Button openFileButton; @FXML private TextField nTextField; @@ -153,18 +153,6 @@ public class MainController { updateMap(); } - private boolean cityExists(String name) { - for (int row = 0; row < cities.length; row++) { - for (int col = 0; col < cities[0].length; col++) { - City city = cities[row][col]; - if (city != null && city.getName().equalsIgnoreCase(name.trim())) { - return true; - } - } - } - return false; - } - @FXML public void initialize() { endCityText @@ -175,7 +163,7 @@ public class MainController { endCity = null; endCityText.setStyle("-fx-text-fill: black;"); } else { - City match = cityManager.getCityByName(newText.trim()); + City match = CityManager.getCityByName(newText.trim()); if (match != null) { endCity = match; endCityText.setStyle("-fx-text-fill: green;"); @@ -195,7 +183,7 @@ public class MainController { startCity = null; startCityText.setStyle("-fx-text-fill: black;"); } else { - City match = cityManager.getCityByName(newText.trim()); + City match = CityManager.getCityByName(newText.trim()); if (match != null) { startCity = match; startCityText.setStyle("-fx-text-fill: green;"); @@ -228,6 +216,7 @@ public class MainController { } private void getData() { + CityManager.clear(); List cities = JsonParser.parseCities(selectedFile.toString(), "stations"); List departures = JsonParser.getDeparturesList("transport_data.json", "departures"); cities = JsonParser.loadDepartures(cities, departures); @@ -260,19 +249,7 @@ public class MainController { } getData(); updateMap(); + + this.mapSelectButton.setText(selectedFile.getName().toString()); } - /* - @FXML - void openLoadedFileDesktop(){ - selectedFileText.setOnMouseClicked(event -> { - if(selectedFile != null && selectedFile.exists()){ - try{ - Desktop.getDesktop().open(selectedFile); - } - catch(Exception e){ - e.printStackTrace(); - } - } - }); - }*/ } diff --git a/src/main/java/dev/ksan/travelpathoptimizer/graph/Graph.java b/src/main/java/dev/ksan/travelpathoptimizer/graph/Graph.java index bae67df..5a82529 100644 --- a/src/main/java/dev/ksan/travelpathoptimizer/graph/Graph.java +++ b/src/main/java/dev/ksan/travelpathoptimizer/graph/Graph.java @@ -5,16 +5,20 @@ 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; public class Graph { private City[][] matrix; -/* + private List allPaths = new ArrayList<>(); + private int pathIdCounter = 1; + public static void main(String[] args) { List cities = JsonParser.parseCities("transport_data.json", "stations"); List departures = JsonParser.getDeparturesList("transport_data.json", "departures"); @@ -23,16 +27,144 @@ public class Graph { Graph graph = new Graph(map); cities = JsonParser.loadDepartures(cities, departures); - System.out.println(cities.getFirst().getName() + " do " + cities.getLast().getName()); + System.out.println(cities.getFirst().getName() + " do " + cities.get(3).getName()); for (City city : cities) { CityManager.addCity(city); } Map result = - graph.calculateShortestPath(cities.getFirst(), cities.get(3), "hops"); - System.out.println( - cities.getLast().getName() + " = " + result.get(cities.get(3).getLocation())); + graph.calculateShortestPath(cities.getFirst(), cities.get(3), "time"); + System.out.println(cities.get(3).getName() + " = " + result.get(cities.get(3).getLocation())); + List 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()); + + System.out.println("Departures:"); + for (Departure dep : path.getDepartures()) { + System.out.println( + " " + + dep.getDestinationCity().getName() + + " (Price: " + + dep.getPrice() + + ", Time: " + + dep.getDuration() + + ")"); + } + + System.out.println("-----"); + } + } + + private boolean hasCycle(List path) { + Set seen = new HashSet<>(); + for (City c : path) { + if (!seen.add(c.getName())) return true; + } + return false; + } + + public List calculateRankedPaths(City startCity, City endCity, String type) { + + List allFoundPaths = new ArrayList<>(); + + PriorityQueue pq = new PriorityQueue<>(Comparator.comparingDouble(s -> s.cost)); + pq.add(new PathState(startCity, new ArrayList<>(List.of(startCity)), new ArrayList<>(), 0.0)); + + Set 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 newPath = new ArrayList<>(current.getPath()); + newPath.add(nextCity); + + List 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 selectedPaths = allFoundPaths.subList(0, Math.min(toStore, + // allFoundPaths.size())); + + Map 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 selectedPaths = + bestPaths.values().stream() + .sorted(Comparator.comparingDouble(PathResult::getCost)) + .limit(5) + .toList(); + + allPaths.clear(); + allPaths.addAll(selectedPaths); + return selectedPaths; + } + + private String getRichPathSignature(List path, List 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("->"); + } + } + return sb.toString(); } - */ public Map calculateShortestPath(City startCity, City endCity, String type) { int n = matrix.length; @@ -83,6 +215,10 @@ public class Graph { return distances; } + public List getAllPaths() { + return allPaths; + } + public Graph(City[][] matrix) { this.matrix = matrix; } diff --git a/src/main/java/dev/ksan/travelpathoptimizer/graph/PathResult.java b/src/main/java/dev/ksan/travelpathoptimizer/graph/PathResult.java new file mode 100644 index 0000000..efa13a1 --- /dev/null +++ b/src/main/java/dev/ksan/travelpathoptimizer/graph/PathResult.java @@ -0,0 +1,51 @@ +package dev.ksan.travelpathoptimizer.graph; + +import dev.ksan.travelpathoptimizer.model.City; +import dev.ksan.travelpathoptimizer.model.Departure; +import java.util.List; + +public class PathResult { + private int id; + private List path; + private List departures; + private double cost; + + public PathResult(int id, List path, List departures, double cost) { + this.id = id; + this.path = path; + this.departures = departures; + this.cost = cost; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public List getPath() { + return path; + } + + public void setPath(List path) { + this.path = path; + } + + public List getDepartures() { + return departures; + } + + public void setDepartures(List departures) { + this.departures = departures; + } + + public double getCost() { + return cost; + } + + public void setCost(double cost) { + this.cost = cost; + } +} diff --git a/src/main/java/dev/ksan/travelpathoptimizer/graph/PathState.java b/src/main/java/dev/ksan/travelpathoptimizer/graph/PathState.java new file mode 100644 index 0000000..d9b259a --- /dev/null +++ b/src/main/java/dev/ksan/travelpathoptimizer/graph/PathState.java @@ -0,0 +1,56 @@ +package dev.ksan.travelpathoptimizer.graph; + +import dev.ksan.travelpathoptimizer.model.City; +import dev.ksan.travelpathoptimizer.model.Departure; +import java.util.List; +import java.util.stream.Collectors; + +public class PathState { + private City city; + private List path; + private List departures; + double cost; + + public PathState(City city, List path, List departures, double cost) { + this.city = city; + this.path = path; + this.departures = departures; + this.cost = cost; + } + + public String getPathSignature() { + return path.stream().map(c -> c.getLocation().toString()).collect(Collectors.joining("->")); + } + + public City getCity() { + return city; + } + + public void setCity(City city) { + this.city = city; + } + + public List getPath() { + return path; + } + + public void setPath(List path) { + this.path = path; + } + + public List getDepartures() { + return departures; + } + + public void setDepartures(List departures) { + this.departures = departures; + } + + public double getCost() { + return cost; + } + + public void setCost(double cost) { + this.cost = cost; + } +} diff --git a/src/main/java/dev/ksan/travelpathoptimizer/model/Location.java b/src/main/java/dev/ksan/travelpathoptimizer/model/Location.java index b0bf4b1..dab5f8f 100644 --- a/src/main/java/dev/ksan/travelpathoptimizer/model/Location.java +++ b/src/main/java/dev/ksan/travelpathoptimizer/model/Location.java @@ -40,4 +40,9 @@ public class Location { public int hashCode() { return Objects.hash(x, y); } + + @Override + public String toString() { + return x + "_" + y; + } } diff --git a/src/main/java/dev/ksan/travelpathoptimizer/service/CityManager.java b/src/main/java/dev/ksan/travelpathoptimizer/service/CityManager.java index 5a5df1e..24a8c20 100644 --- a/src/main/java/dev/ksan/travelpathoptimizer/service/CityManager.java +++ b/src/main/java/dev/ksan/travelpathoptimizer/service/CityManager.java @@ -1,13 +1,16 @@ package dev.ksan.travelpathoptimizer.service; import dev.ksan.travelpathoptimizer.model.City; - import java.util.HashMap; import java.util.Map; public class CityManager { private static Map cities = new HashMap<>(); + public static void clear() { + cities.clear(); + } + public static void addCity(City city) { cities.put(city.getName(), city); } diff --git a/src/main/resources/dev/ksan/travelpathoptimizer/app/main.fxml b/src/main/resources/dev/ksan/travelpathoptimizer/app/main.fxml index 12bc983..582e51e 100644 --- a/src/main/resources/dev/ksan/travelpathoptimizer/app/main.fxml +++ b/src/main/resources/dev/ksan/travelpathoptimizer/app/main.fxml @@ -3,7 +3,6 @@ - @@ -101,6 +100,20 @@ + + + + + + + + + + @@ -140,29 +153,17 @@ - - - - - - - - - - - + + +