diff --git a/README.md b/README.md index bb0c84ebb..d6299154c 100644 --- a/README.md +++ b/README.md @@ -64,8 +64,8 @@ B. 돌아가기 ## 조회 결과 [INFO] --- -[INFO] 총 거리: 4km -[INFO] 총 소요 시간: 11분 +[INFO] 총 거리: 6km +[INFO] 총 소요 시간: 14분 [INFO] --- [INFO] 교대역 [INFO] 강남역 diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..36b4ffe35 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,42 @@ +## 구현할 기능 목록 + +1. 메인 화면 출력하기 // 첫 실행시, 종료 입력이 주어지지 않으면 계속 다시 시작됨 +2. 메인화면에서 원하는 기능 입력받기 // 경로 조회, 종료 기능 선택 가능 +3. 경로 조회의 경로 기준 기능 항목 출력하기 +4. 경로 기준에서 원하는 기능 입력받기 // 최단 거리, 최소 시간, 돌아가기 기능 출력 가능 +5. 출발역과 도착역을 입력받기 +6. 최단 거리로 경로 계산하기 +7. 최소 시간으로 경로 계산하기 +8. 조회 결과 출력하기 + + +## 예외 처리 + +1. 기능 선택 화면에서 선택 가능 항목을 입력하지 않을 시 다시 선택 가능 항목을 보여주고 입력 받음 +2. 출발역 입력시 출발역이 존재하지 않으면 예외를 발생시키고 다시 기능 선택 화면으로 돌아감 +3. 도착역 입력시 도착역이 존재하지 않으면 예외를 발생시키고 다시 기능 선택 화면으로 돌아감 +4. 출발역과 도착역이 동일할 시 예외를 발생시키고 다시 기능 선택 화면으로 돌아감 + + +## 구현할 설정 목록 + +1. 사전 등록 정보 설정하기 +2. 노선에서 역과 역 사이의 거리를 저장하는 리스트 생성 +3. 노선에서 역과 역 사이의 소요 시간을 저장하는 리스트 생성. +*주의 사항* : 한 노선에서 갈래길은 없다고 가정한다. +그러면 해당 노선은 단방향이므로 특정 역에서는 다음 역으로 단 하나의 경로만 존재한다. +해당 노선의 특정 역에서 다음 역까지의 거리(시간)정보만 저장하면 해당 노선에서의 거리(시간) 정보를 파악할 수 있다. +환승역을 올바르게 고려하기 위해 노선에 추가되는 역은 이름이 동일하면 모든 노선에서 동일한 역으로 고려되어야 한다. + + +## 추후 고려 사항 +1. 경로를 파악하기 위해 입력된 두 역이 연결되어 있는지 파악해야 한다. +getDijkstraShortestPath() 함수를 찾아보니 경로가 존재하지 않으면 getPath함수의 출력값을 null로 준다고 하는데 +이것을 활용하여 예외처리를 할 수 있을지 고려해본다. (NullPointerException을 통해 예외처리 완료) + + +## 실패한 사항 +해당 역마다 어떤 노선에 등록되어있는지 값을 저장하고 +최종으로 구해진 결과에서 어떤 노선에 등록된 역인지를 구분하여 거리와 소요시간을 구하려 했는데 +그렇게 하려면 setter를 사용해야 하므로 +hash값을 조정하는 등 다른 방법을 계속 시도해 보았는데 시간내에 실패하고 말았습니다. \ No newline at end of file diff --git a/src/main/java/subway/Application.java b/src/main/java/subway/Application.java index 0bcf786cc..b3311d0f3 100644 --- a/src/main/java/subway/Application.java +++ b/src/main/java/subway/Application.java @@ -1,10 +1,20 @@ package subway; import java.util.Scanner; +import subway.controller.MainDashboard; +import subway.controller.RouteCalculator; +import subway.domain.Line; +import subway.domain.LineRepository; +import subway.domain.Station; +import subway.domain.StationRepository; +import subway.view.InputView; public class Application { + public static void main(String[] args) { final Scanner scanner = new Scanner(System.in); - // TODO: 프로그램 구현 + InputView inputView = new InputView(scanner); + new InitialMap(); + MainDashboard mainDashboard = new MainDashboard(inputView); } } diff --git a/src/main/java/subway/InitialMap.java b/src/main/java/subway/InitialMap.java new file mode 100644 index 000000000..8c117835e --- /dev/null +++ b/src/main/java/subway/InitialMap.java @@ -0,0 +1,47 @@ +package subway; + +import subway.domain.Line; +import subway.domain.LineRepository; +import subway.domain.Station; +import subway.domain.StationRepository; + +public class InitialMap { + + public InitialMap() { + Station station1 = new Station("교대역"); + Station station2 = new Station("강남역"); + Station station3 = new Station("역삼역"); + Station station4 = new Station("남부터미널역"); + Station station5 = new Station("양재역"); + Station station6 = new Station("양재시민의숲역"); + Station station7 = new Station("매봉역"); + + StationRepository.addStation(station1); + StationRepository.addStation(station2); + StationRepository.addStation(station3); + StationRepository.addStation(station4); + StationRepository.addStation(station5); + StationRepository.addStation(station6); + StationRepository.addStation(station7); + + Line line1 = new Line("2호선"); + Line line2 = new Line("3호선"); + Line line3 = new Line("신분당선"); + + LineRepository.addLine(line1); + LineRepository.addLine(line2); + LineRepository.addLine(line3); + + line1.addStation(station1, 2, 3); + line1.addStation(station2, 2, 3); + line1.addStation(station3); + line2.addStation(station1, 3, 2); + line2.addStation(station4, 6, 5); + line2.addStation(station5, 1, 1); + line2.addStation(station7); + line3.addStation(station2, 2, 8); + line3.addStation(station5, 10, 3); + line3.addStation(station6); + + } +} diff --git a/src/main/java/subway/SubwayKeyWords.java b/src/main/java/subway/SubwayKeyWords.java new file mode 100644 index 000000000..8d4fa2fd1 --- /dev/null +++ b/src/main/java/subway/SubwayKeyWords.java @@ -0,0 +1,39 @@ +package subway; + +public class SubwayKeyWords { + + public static final String DASHBOARD_MAIN = "## 메인 화면"; + public static final String DASHBOARD_ROUTE = "## 경로 기준"; + + + public static final String OPTION_NUM_1 = "1"; + public static final String OPTION_NUM_2 = "2"; + public static final String OPTION_QUIT = "Q"; + public static final String OPTION_BACK = "B"; + + public static final String DASHBOARD_MAIN_OPTION_1 = "경로 조회"; + + public static final String DASHBOARD_ROUTE_OPTION_1 = "최단 거리"; + public static final String DASHBOARD_ROUTE_OPTION_2 = "최소 시간"; + + public static final String DASHBOARD_OPTION_Q = "종료"; + public static final String DASHBOARD_OPTION_B = "돌아가기"; + + public static final String MESSAGE_CHOOSE_OPTION = "## 원하는 기능을 선택하세요."; + public static final String MESSAGE_CHOOSE_DEPARTURE_STATION = "## 출발역을 입력하세요."; + public static final String MESSAGE_CHOOSE_ARRIVAL_STATION = "## 도착역을 입력하세요."; + + public static final String ERROR_OPTION_UNAVAILABLE = "[ERROR] 선택할 수 없는 기능입니다."; + public static final String ERROR_STATION_UNREACHABLE = "[ERROR] 출발역과 도착역이 연결되어 있지 않습니다."; + public static final String ERROR_STATION_NOT_EXISTS = "[ERROR] 존재하지 않는 역입니다."; + public static final String ERROR_STATION_SAME_NAME = "[ERROR] 출발역과 도착역이 동일합니다."; + + public static final String INFO_LINE = "[INFO] ---"; + public static final String INFO_TOTAL_DISTANCE = "[INFO] 총 거리: %dkm\n"; + public static final String INFO_TOTAL_TIME = "[INFO] 총 소요 시간: %d분\n"; + public static final String INFO_STATION_NAME = "[INFO] %s\n"; + + public static final String RESULT_ANNOUNCEMENT = "## 조회 결과"; + + +} diff --git a/src/main/java/subway/controller/MainDashboard.java b/src/main/java/subway/controller/MainDashboard.java new file mode 100644 index 000000000..ba1072078 --- /dev/null +++ b/src/main/java/subway/controller/MainDashboard.java @@ -0,0 +1,64 @@ +package subway.controller; + +import static subway.SubwayKeyWords.*; + +import java.util.TreeMap; +import subway.exceptions.ExceptionOptionUnavailable; +import subway.view.InputView; +import subway.view.OutputView; + +public class MainDashboard { + + public static String title = DASHBOARD_MAIN; + TreeMap options; + InputView inputView; + boolean power; + + public MainDashboard(InputView inputView) { + this.inputView = inputView; + power = true; + options = new TreeMap<>(); + options.put(OPTION_NUM_1, DASHBOARD_MAIN_OPTION_1); + options.put(OPTION_QUIT, DASHBOARD_OPTION_Q); + startMainDashboard(inputView); + } + + public void startMainDashboard(InputView inputView) { + while (power) { + String chosenOption = makeUserChooseOption(inputView); + startChosenOption(chosenOption); + } + } + + + public String makeUserChooseOption(InputView inputView) { + while (true) { + OutputView.showOptions(title, options); + String optionChosen = inputView.chooseOption(); + try { + checkOptions(optionChosen); + return optionChosen; + } catch (ExceptionOptionUnavailable e) { + OutputView.showErrorMessage(e); + } + } + } + + public void checkOptions(String input) throws ExceptionOptionUnavailable { + if (!options.containsKey(input)) { + throw new ExceptionOptionUnavailable(); + } + } + + public void startChosenOption(String option) { + if (option.equals(OPTION_NUM_1)) { + RouteDashboard routeDashboard = new RouteDashboard(inputView); + } + + if (option.equals(OPTION_QUIT)) { + power = false; + } + } + + +} diff --git a/src/main/java/subway/controller/RouteCalculator.java b/src/main/java/subway/controller/RouteCalculator.java new file mode 100644 index 000000000..9e189e88a --- /dev/null +++ b/src/main/java/subway/controller/RouteCalculator.java @@ -0,0 +1,84 @@ +package subway.controller; + +import java.util.List; +import org.jgrapht.alg.shortestpath.DijkstraShortestPath; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.WeightedMultigraph; +import subway.domain.Line; +import subway.domain.LineRepository; +import subway.domain.Station; +import subway.view.OutputView; + +public class RouteCalculator { + + Station stationDeparture; + Station stationArrival; + String option; + int totalTime = 0; + int totalDistance = 0; + + + public RouteCalculator(Station stationDeparture, Station stationArrival, String option) { + this.stationDeparture = stationDeparture; + this.stationArrival = stationArrival; + this.option = option; + try { + if (option.equals("1")) { + getDijkstraShortestPathByDistance(); + return; + } + + if (option.equals("2")) { + getDijkstraShortestPathByTime(); + return; + } + + } catch (NullPointerException e) { + OutputView.showErrorStationUnreachable(); + } + } + + public void getDijkstraShortestPathByDistance() { + WeightedMultigraph graph = new WeightedMultigraph( + DefaultWeightedEdge.class); + for (Line line : LineRepository.lines()) { + graph.addVertex(line.getStations().get(0)); + for (int i = 1; i < line.getStations().size(); i++) { + graph.addVertex(line.getStations().get(i)); + graph.setEdgeWeight( + graph.addEdge(line.getStations().get(i - 1), line.getStations().get(i)), + line.getDistances().get(i - 1)); + } + } + calculateShortestPath(graph); + } + + public void getDijkstraShortestPathByTime() { + WeightedMultigraph graph = new WeightedMultigraph( + DefaultWeightedEdge.class); + for (Line line : LineRepository.lines()) { + graph.addVertex(line.getStations().get(0)); + for (int i = 1; i < line.getStations().size(); i++) { + graph.addVertex(line.getStations().get(i)); + graph.setEdgeWeight( + graph.addEdge(line.getStations().get(i - 1), line.getStations().get(i)), + line.getTimes().get(i - 1)); + } + } + calculateShortestPath(graph); + } + + + public void calculateShortestPath(WeightedMultigraph graph) { + DijkstraShortestPath dijkstraShortestPath = new DijkstraShortestPath(graph); + List shortestPath = dijkstraShortestPath.getPath(stationDeparture, stationArrival) + .getVertexList(); + + totalTime = (int) dijkstraShortestPath.getPathWeight(stationDeparture, stationArrival); + OutputView.showResult(shortestPath, totalDistance, totalTime); + + } + + + +} diff --git a/src/main/java/subway/controller/RouteDashboard.java b/src/main/java/subway/controller/RouteDashboard.java new file mode 100644 index 000000000..aa3af7e49 --- /dev/null +++ b/src/main/java/subway/controller/RouteDashboard.java @@ -0,0 +1,99 @@ +package subway.controller; + +import static subway.SubwayKeyWords.*; + +import java.util.TreeMap; +import subway.domain.Station; +import subway.domain.StationRepository; +import subway.exceptions.ExceptionOptionUnavailable; +import subway.exceptions.ExceptionSameStationSubmitted; +import subway.exceptions.ExceptionStationNotExists; +import subway.view.InputView; +import subway.view.OutputView; + +public class RouteDashboard { + + public static String title = DASHBOARD_ROUTE; + TreeMap options; + InputView inputView; + boolean power; + String departureStation; + String arrivalStation; + + public RouteDashboard(InputView inputView) { + this.inputView = inputView; + power = true; + options = new TreeMap<>(); + options.put(OPTION_NUM_1, DASHBOARD_ROUTE_OPTION_1); + options.put(OPTION_NUM_2, DASHBOARD_ROUTE_OPTION_2); + options.put(OPTION_BACK, DASHBOARD_OPTION_B); + startRouteDashboard(inputView); + } + + public void startRouteDashboard(InputView inputView) { + while (power) { + String chosenOption = makeUserChooseOption(inputView); + startChosenOption(chosenOption); + } + } + + public String makeUserChooseOption(InputView inputView) { + while (true) { + OutputView.showOptions(title, options); + String optionChosen = inputView.chooseOption(); + try { + checkOptions(optionChosen); + return optionChosen; + } catch (ExceptionOptionUnavailable e) { + OutputView.showErrorMessage(e); + } + } + } + + public void checkOptions(String input) throws ExceptionOptionUnavailable { + if (!options.containsKey(input)) { + throw new ExceptionOptionUnavailable(); + } + } + + public void startChosenOption(String option) { + if (option.equals(OPTION_NUM_1) || option.equals(OPTION_NUM_2)) { + try { + submitStation(inputView); + } catch (Exception e) { + return; + } + RouteCalculator routeCalculator = new RouteCalculator(new Station(departureStation), + new Station(arrivalStation), option); + + power = false; + } + + if (option.equals(OPTION_BACK)) { + power = false; + } + } + + public void submitStation(InputView inputView) throws Exception { + try { + departureStation = inputView.ChooseDepartureStation(); + StationRepository.isValidStationName(departureStation); + arrivalStation = inputView.ChooseArrivalStation(); + StationRepository.isValidStationName(arrivalStation); + checkNameDuplication(departureStation, arrivalStation); + } catch (ExceptionStationNotExists e) { + OutputView.showErrorMessage(e); + throw new Exception(); + } catch (ExceptionSameStationSubmitted e) { + OutputView.showErrorMessage(e); + throw new Exception(); + } + } + + public void checkNameDuplication(String arrivalStation, String departureStation) { + if (arrivalStation.equals(departureStation)) { + throw new ExceptionSameStationSubmitted(); + } + } + +} diff --git a/src/main/java/subway/domain/Line.java b/src/main/java/subway/domain/Line.java index f4d738d5a..e1e04fa68 100644 --- a/src/main/java/subway/domain/Line.java +++ b/src/main/java/subway/domain/Line.java @@ -1,15 +1,46 @@ package subway.domain; +import java.util.ArrayList; +import java.util.List; + public class Line { + private String name; + private List stations = new ArrayList(); + private List times = new ArrayList(); + private List distances = new ArrayList(); + public Line(String name) { this.name = name; } + public void addStation(Station station) { + stations.add(station); + } + + public void addStation(Station station, int distanceToNextStation, int timeToNextStation) { + stations.add(station); + distances.add(distanceToNextStation); + times.add(timeToNextStation); + + } + + public List getStations() { + return stations; + } + + public List getTimes() { + return times; + } + + public List getDistances() { + return distances; + } + public String getName() { return name; } - // 추가 기능 구현 + } diff --git a/src/main/java/subway/domain/Station.java b/src/main/java/subway/domain/Station.java index bdb142590..7b66664c3 100644 --- a/src/main/java/subway/domain/Station.java +++ b/src/main/java/subway/domain/Station.java @@ -1,6 +1,9 @@ package subway.domain; +import java.util.Objects; + public class Station { + private String name; public Station(String name) { @@ -11,5 +14,23 @@ public String getName() { return name; } - // 추가 기능 구현 + public String toString() { + return name; + } + + @Override + public boolean equals(Object object) { + if (object instanceof Station) { + return ((Station) object).name.equals(this.name); + } + return false; + } + + + @Override + public int hashCode() { + return Objects.hash(name); + } + + } diff --git a/src/main/java/subway/domain/StationRepository.java b/src/main/java/subway/domain/StationRepository.java index 8ed9d103f..fa81a4d5f 100644 --- a/src/main/java/subway/domain/StationRepository.java +++ b/src/main/java/subway/domain/StationRepository.java @@ -4,8 +4,10 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import subway.exceptions.ExceptionStationNotExists; public class StationRepository { + private static final List stations = new ArrayList<>(); public static List stations() { @@ -16,6 +18,21 @@ public static void addStation(Station station) { stations.add(station); } + public static Station getStationByName(String stationName) { + for (Station station : StationRepository.stations()) { + if (station.getName().equals(stationName)) { + return station; + } + } + return null; + } + + public static void isValidStationName(String stationName) { + if (!stations.contains(new Station(stationName))) { + throw new ExceptionStationNotExists(); + } + } + public static boolean deleteStation(String name) { return stations.removeIf(station -> Objects.equals(station.getName(), name)); } diff --git a/src/main/java/subway/exceptions/ExceptionOptionUnavailable.java b/src/main/java/subway/exceptions/ExceptionOptionUnavailable.java new file mode 100644 index 000000000..109cf0204 --- /dev/null +++ b/src/main/java/subway/exceptions/ExceptionOptionUnavailable.java @@ -0,0 +1,10 @@ +package subway.exceptions; + +import static subway.SubwayKeyWords.ERROR_OPTION_UNAVAILABLE; + +public class ExceptionOptionUnavailable extends RuntimeException { + + public ExceptionOptionUnavailable() { + super(ERROR_OPTION_UNAVAILABLE); + } +} diff --git a/src/main/java/subway/exceptions/ExceptionSameStationSubmitted.java b/src/main/java/subway/exceptions/ExceptionSameStationSubmitted.java new file mode 100644 index 000000000..87df4c3a3 --- /dev/null +++ b/src/main/java/subway/exceptions/ExceptionSameStationSubmitted.java @@ -0,0 +1,10 @@ +package subway.exceptions; + +import static subway.SubwayKeyWords.ERROR_STATION_SAME_NAME; + +public class ExceptionSameStationSubmitted extends RuntimeException { + + public ExceptionSameStationSubmitted() { + super(ERROR_STATION_SAME_NAME); + } +} diff --git a/src/main/java/subway/exceptions/ExceptionStationNotExists.java b/src/main/java/subway/exceptions/ExceptionStationNotExists.java new file mode 100644 index 000000000..a1cf21f3d --- /dev/null +++ b/src/main/java/subway/exceptions/ExceptionStationNotExists.java @@ -0,0 +1,11 @@ +package subway.exceptions; + +import static subway.SubwayKeyWords.ERROR_STATION_NOT_EXISTS; + +public class ExceptionStationNotExists extends RuntimeException { + + public ExceptionStationNotExists() { + super(ERROR_STATION_NOT_EXISTS); + } + +} diff --git a/src/main/java/subway/view/InputView.java b/src/main/java/subway/view/InputView.java new file mode 100644 index 000000000..4472b66f3 --- /dev/null +++ b/src/main/java/subway/view/InputView.java @@ -0,0 +1,31 @@ +package subway.view; + +import static subway.SubwayKeyWords.*; + +import java.util.Scanner; + +public class InputView { + + private final Scanner scanner; + + public InputView(Scanner scanner) { + this.scanner = scanner; + } + + + public String chooseOption() { + System.out.println(MESSAGE_CHOOSE_OPTION); + return scanner.nextLine(); + } + + public String ChooseDepartureStation() { + System.out.println(MESSAGE_CHOOSE_DEPARTURE_STATION); + return scanner.nextLine(); + } + + public String ChooseArrivalStation() { + System.out.println(MESSAGE_CHOOSE_ARRIVAL_STATION); + return scanner.nextLine(); + } + +} diff --git a/src/main/java/subway/view/OutputView.java b/src/main/java/subway/view/OutputView.java new file mode 100644 index 000000000..561020110 --- /dev/null +++ b/src/main/java/subway/view/OutputView.java @@ -0,0 +1,52 @@ +package subway.view; + +import static subway.SubwayKeyWords.ERROR_STATION_UNREACHABLE; +import static subway.SubwayKeyWords.INFO_LINE; +import static subway.SubwayKeyWords.INFO_STATION_NAME; +import static subway.SubwayKeyWords.INFO_TOTAL_DISTANCE; +import static subway.SubwayKeyWords.INFO_TOTAL_TIME; +import static subway.SubwayKeyWords.RESULT_ANNOUNCEMENT; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import subway.domain.Station; + +public class OutputView { + + public static void showOptions(String title, TreeMap options) { + System.out.println(title); + Set set = options.entrySet(); + Iterator iterator = set.iterator(); + + while (iterator.hasNext()) { + Map.Entry e = (Map.Entry) iterator.next(); + System.out.printf("%s. %s\n", e.getKey(), e.getValue()); + } + System.out.println(""); + } + + + public static void showErrorMessage(Exception e) { + System.out.println(e.getMessage()); + System.out.println(); + } + + public static void showResult(List shortestPath, int finalDistance, int finalTime) { + System.out.println('\n' + RESULT_ANNOUNCEMENT); + System.out.println(INFO_LINE); + System.out.printf(INFO_TOTAL_DISTANCE, finalDistance); + System.out.printf(INFO_TOTAL_TIME, finalTime); + System.out.println(INFO_LINE); + for (Station station : shortestPath) { + System.out.printf(INFO_STATION_NAME, station); + } + System.out.println(); + } + + public static void showErrorStationUnreachable() { + System.out.println(ERROR_STATION_UNREACHABLE); + } +}