implemented function to calculate and rank paths and return a list of best paths
This commit is contained in:
parent
38fa1c69e0
commit
5f68391cdc
@ -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<City> cities = JsonParser.parseCities(selectedFile.toString(), "stations");
|
||||
List<Departure> 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();
|
||||
}
|
||||
}
|
||||
});
|
||||
}*/
|
||||
}
|
||||
|
@ -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<PathResult> allPaths = new ArrayList<>();
|
||||
private int pathIdCounter = 1;
|
||||
|
||||
public static void main(String[] args) {
|
||||
List<City> cities = JsonParser.parseCities("transport_data.json", "stations");
|
||||
List<Departure> 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<Location, Double> 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<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());
|
||||
|
||||
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<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) {
|
||||
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<Location, Double> calculateShortestPath(City startCity, City endCity, String type) {
|
||||
int n = matrix.length;
|
||||
@ -83,6 +215,10 @@ public class Graph {
|
||||
return distances;
|
||||
}
|
||||
|
||||
public List<PathResult> getAllPaths() {
|
||||
return allPaths;
|
||||
}
|
||||
|
||||
public Graph(City[][] matrix) {
|
||||
this.matrix = matrix;
|
||||
}
|
||||
|
@ -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<City> path;
|
||||
private List<Departure> departures;
|
||||
private double cost;
|
||||
|
||||
public PathResult(int id, List<City> path, List<Departure> 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<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 double getCost() {
|
||||
return cost;
|
||||
}
|
||||
|
||||
public void setCost(double cost) {
|
||||
this.cost = cost;
|
||||
}
|
||||
}
|
@ -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<City> path;
|
||||
private List<Departure> departures;
|
||||
double cost;
|
||||
|
||||
public PathState(City city, List<City> path, List<Departure> 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<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 double getCost() {
|
||||
return cost;
|
||||
}
|
||||
|
||||
public void setCost(double cost) {
|
||||
this.cost = cost;
|
||||
}
|
||||
}
|
@ -40,4 +40,9 @@ public class Location {
|
||||
public int hashCode() {
|
||||
return Objects.hash(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return x + "_" + y;
|
||||
}
|
||||
}
|
||||
|
@ -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<String, City> cities = new HashMap<>();
|
||||
|
||||
public static void clear() {
|
||||
cities.clear();
|
||||
}
|
||||
|
||||
public static void addCity(City city) {
|
||||
cities.put(city.getName(), city);
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.MenuItem?>
|
||||
<?import javafx.scene.control.TextField?>
|
||||
<?import javafx.scene.image.Image?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
@ -101,6 +100,20 @@
|
||||
</VBox>
|
||||
<VBox fx:id="calculatorSideBar" managed="true" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="354.0" prefWidth="180.0" visible="true">
|
||||
<children>
|
||||
<HBox prefHeight="32.0" prefWidth="180.0">
|
||||
<children>
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Map:" textAlignment="CENTER" wrappingWidth="37.562997817993164">
|
||||
<HBox.margin>
|
||||
<Insets left="10.0" right="10.0" top="5.0" />
|
||||
</HBox.margin>
|
||||
</Text>
|
||||
<Button fx:id="mapSelectButton" mnemonicParsing="false" onAction="#showMapSideBar" prefHeight="26.0" prefWidth="111.0" text="select">
|
||||
<HBox.margin>
|
||||
<Insets left="10.0" right="10.0" top="3.0" />
|
||||
</HBox.margin>
|
||||
</Button>
|
||||
</children>
|
||||
</HBox>
|
||||
<Text fx:id="sectionText1" strokeType="OUTSIDE" strokeWidth="0.0" text="Path Calculator" textAlignment="CENTER" wrappingWidth="179.2100067138672" />
|
||||
<HBox prefHeight="39.0" prefWidth="180.0">
|
||||
<children>
|
||||
@ -140,29 +153,17 @@
|
||||
<Insets left="10.0" right="10.0" top="-5.0" />
|
||||
</VBox.margin>
|
||||
</Button>
|
||||
<VBox prefHeight="80.0" prefWidth="180.0">
|
||||
<children>
|
||||
<Button fx:id="openFileButton1" maxWidth="1.7976931348623157E308" minHeight="0.0" minWidth="0.0" mnemonicParsing="false" text="Load Map" VBox.vgrow="ALWAYS">
|
||||
<VBox.margin>
|
||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||
</VBox.margin>
|
||||
</Button>
|
||||
<Text fx:id="selectedFileText1" strokeType="OUTSIDE" strokeWidth="0.0" text="no file" textAlignment="CENTER" textOrigin="CENTER" wrappingWidth="147.13000106811523" VBox.vgrow="ALWAYS">
|
||||
<VBox.margin>
|
||||
<Insets left="15.0" right="15.0" />
|
||||
</VBox.margin>
|
||||
</Text>
|
||||
</children>
|
||||
</VBox>
|
||||
|
||||
</children>
|
||||
</VBox>
|
||||
<GridPane fx:id="map" alignment="CENTER" gridLinesVisible="true" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" HBox.hgrow="ALWAYS">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
|
||||
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
</rowConstraints>
|
||||
<padding>
|
||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||
|
Loading…
x
Reference in New Issue
Block a user