diff --git a/README.md b/README.md index d6299154c..b5ea0a556 100644 --- a/README.md +++ b/README.md @@ -200,52 +200,19 @@ public class StationRepository {
-## ❗️힌트 -### 최단 경로 라이브러리 -- jgrapht 라이브러리를 활용하면 간편하게 최단거리를 조회할 수 있음 -- Dijkstra 알고리즘을 반드시 이해할 필요는 없고 미션에 적용할 정도로만 이해하면 됨 -- JGraphtTest 클래스의 테스트를 활용하여 미션에 필요한 라이브러리의 기능을 학습할 수 있음 -- 정점(vertex)과 간선(edge), 그리고 가중치 개념을 이용 - - 정점: 지하철역 - - 간선: 지하철역 연결정보 - - 가중치: 거리 or 소요 시간 -- 최단 거리 기준 조회 시 가중치를 거리로 설정 +## 기능 구현 목록 +- Line 안에 Station list 변수 생성 및 추가 기능 구현 +- 구간 객체 생성 + - JGrapht 활용하여 시간, 거리 개념의 구간 정보 가짐 + - 최단거리, 최단시간 도출해내는 기능 구현 +- Line 객체가 구간 거리, 구간 시간 정보가지도록 구현 +- 초기화 작업 + - Manager 디렉토리 생성하여 초기화 작업하는 기능 구현 +- Controller 생성하고 프로그램 실행 기능 구현 +- Menu 생성하여 버튼 동작 기능 구현 +- view 담당 디렉토리 생성 + - Input, Output, Error -```java -@Test -public void getDijkstraShortestPath() { - WeightedMultigraph graph - = new WeightedMultigraph(DefaultWeightedEdge.class); - graph.addVertex("v1"); - graph.addVertex("v2"); - graph.addVertex("v3"); - graph.setEdgeWeight(graph.addEdge("v1", "v2"), 2); - graph.setEdgeWeight(graph.addEdge("v2", "v3"), 2); - graph.setEdgeWeight(graph.addEdge("v1", "v3"), 100); - - DijkstraShortestPath dijkstraShortestPath = new DijkstraShortestPath(graph); - List shortestPath = dijkstraShortestPath.getPath("v3", "v1").getVertexList(); - - assertThat(shortestPath.size()).isEqualTo(3); -} -``` - -#### 테스트 설명 - - - -- 역 사이의 거리를 고려하지 않는 경우 V1->V3 경로가 최단 경로 -- 역 사이의 거리를 고려할 경우 V1->V3 경로의 거리는 100km, V1->V2->V3 경로의 거리는 4km이므로 최단 경로는 V1->V2->V3 - -
- -## 📈 진행 요구사항 -- 미션은 [java-subway-path-precourse 저장소](https://github.com/woowacourse/java-subway-path-precourse) 를 fork/clone해 시작한다. -- 기능을 구현하기 전에 java-subway-path-precourse/docs/README.md 파일에 구현할 기능 목록을 정리해 추가한다. -- git의 commit 단위는 앞 단계에서 README.md 파일에 정리한 기능 목록 단위로 추가한다. - - [AngularJS Commit Message Conventions](https://gist.github.com/stephenparish/9941e89d80e2bc58a153) 참고해 commit log를 남긴다. -- [프리코스 과제 제출 문서](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) 절차를 따라 미션을 제출한다. - - [프리코스 과제 FAQ](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse/faq) 문서를 참고하여 진행할 수 있다.
## 📝 License diff --git a/src/main/java/subway/Application.java b/src/main/java/subway/Application.java index 0bcf786cc..bc47c1233 100644 --- a/src/main/java/subway/Application.java +++ b/src/main/java/subway/Application.java @@ -1,10 +1,9 @@ package subway; -import java.util.Scanner; +import subway.controller.UserController; public class Application { public static void main(String[] args) { - final Scanner scanner = new Scanner(System.in); - // TODO: 프로그램 구현 + UserController.start(); } } diff --git a/src/main/java/subway/controller/InitializingManager.java b/src/main/java/subway/controller/InitializingManager.java new file mode 100644 index 000000000..135ad0d1b --- /dev/null +++ b/src/main/java/subway/controller/InitializingManager.java @@ -0,0 +1,42 @@ +package subway.controller; + +import subway.domain.Line; +import subway.domain.LineRepository; +import subway.domain.Station; +import subway.domain.StationRepository; + +public class InitializingManager { + private static final String[] defaultStations = {"교대역", "강남역", "역삼역", "남부터미널역", "양재역", "양재시민의숲역", "매봉역"}; + private static final String[] defaultLines = {"2호선", "3호선", "신분당선"}; + private static final String[][] defaultInLineStations = {{"교대역", "강남역", "역삼역"}, {"교대역", "남부터미널역", "양재역", "매봉역"}, + {"강남역", "양재역", "양재시민의숲역"}}; + + private static final int[][] defaultLineDistance = {{2, 2}, {3, 6, 1}, {2, 10}}; + private static final int[][] defaultLineTime = {{3, 3}, {2, 5, 1}, {8, 3}}; + + public static void initialize() { + initiateStation(); + initiateLine(); + initiateInterval(); + } + + private static void initiateStation() { + for (String station : defaultStations) { + StationRepository.addStation(new Station(station)); + } + } + + private static void initiateLine() { + for (int i = 0; i < defaultLines.length; i++) { + LineRepository.addLine( + new Line(defaultLines[i], defaultInLineStations[i], defaultLineDistance[i], defaultLineTime[i])); + } + } + + private static void initiateInterval() { + for (Line line : LineRepository.lines()) { + line.registerInterval(); + } + } + +} diff --git a/src/main/java/subway/controller/UserController.java b/src/main/java/subway/controller/UserController.java new file mode 100644 index 000000000..143152c0a --- /dev/null +++ b/src/main/java/subway/controller/UserController.java @@ -0,0 +1,32 @@ +package subway.controller; + +import subway.domain.MenuRepository; +import subway.userinterface.ErrorOutput; +import subway.userinterface.Input; + +public class UserController { + public static void start() { + InitializingManager.initialize(); + boolean runStatus = true; + while (runStatus) { + Input.printMainMenu(); + runStatus = startMainMenu(); + } + Input.closeScanner(); + } + + private static boolean startMainMenu() { + String input = Input.newInput().toUpperCase(); + if (ErrorOutput.isWrongMainMenuInput(input)) { + return true; + } + + for (String key : MenuRepository.mainMenu.keySet()) { + if (input.equals(key)) { + MenuRepository.mainMenu.get(key).run(); + return true; + } + } + return false; + } +} diff --git a/src/main/java/subway/domain/Interval.java b/src/main/java/subway/domain/Interval.java new file mode 100644 index 000000000..40e19d6cf --- /dev/null +++ b/src/main/java/subway/domain/Interval.java @@ -0,0 +1,50 @@ +package subway.domain; + +import org.jgrapht.alg.shortestpath.DijkstraShortestPath; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.WeightedMultigraph; + +import java.util.List; + +public class Interval { + public static WeightedMultigraph distanceInterval = new WeightedMultigraph(DefaultWeightedEdge.class); + public static WeightedMultigraph timeInterval = new WeightedMultigraph(DefaultWeightedEdge.class); + + public static void registerIntervals(List stationInLine, List distance, List time) { + for (Station station : stationInLine) { + distanceInterval.addVertex(station); + timeInterval.addVertex(station); + } + + for (int i = 0; i < stationInLine.size() - 1; i++) { + distanceInterval.setEdgeWeight(distanceInterval.addEdge(stationInLine.get(i), stationInLine.get(i + 1)), distance.get(i)); + timeInterval.setEdgeWeight(timeInterval.addEdge(stationInLine.get(i), stationInLine.get(i + 1)), time.get(i)); + } + } + + public static List shortestDistancePath(Station start, Station end) { + DijkstraShortestPath dijkstraShortestPath = new DijkstraShortestPath(distanceInterval); + return dijkstraShortestPath.getPath(start, end).getVertexList(); + } + + public static List shortestTimePath(Station start, Station end) { + DijkstraShortestPath dijkstraShortestPath = new DijkstraShortestPath(timeInterval); + return dijkstraShortestPath.getPath(start, end).getVertexList(); + } + + public static int getTotalDistance(List path) { + int total = 0; + for (int i = 0; i < path.size() - 1; i++) { + total += distanceInterval.getEdgeWeight(distanceInterval.getEdge(path.get(i), path.get(i + 1))); + } + return total; + } + + public static int getTotalTime(List path) { + int total = 0; + for (int i = 0; i < path.size() - 1; i++) { + total += timeInterval.getEdgeWeight(timeInterval.getEdge(path.get(i), path.get(i + 1))); + } + return total; + } +} diff --git a/src/main/java/subway/domain/Line.java b/src/main/java/subway/domain/Line.java index f4d738d5a..48b0ea099 100644 --- a/src/main/java/subway/domain/Line.java +++ b/src/main/java/subway/domain/Line.java @@ -1,15 +1,48 @@ package subway.domain; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + public class Line { private String name; + private final List stationInLine = new ArrayList<>(); + private final List distanceInterval = new ArrayList<>(); + private final List timeInterval = new ArrayList<>(); - public Line(String name) { + public Line(String name, String[] stations, int[] distance, int[] time) { this.name = name; + for (String station : stations) { + addStationInLine(StationRepository.findStationByName(station)); + } + for (int i = 0; i < distance.length; i++) { + addDistanceInterval(distance[i]); + addTimeInterval(time[i]); + } + } + + private void addStationInLine(Station station) { + stationInLine.add(station); + } + + private void addDistanceInterval(int distance) { + distanceInterval.add(distance); + } + + private void addTimeInterval(int time) { + timeInterval.add(time); + } + + public void registerInterval() { + Interval.registerIntervals(stationInLine, distanceInterval, timeInterval); } public String getName() { return name; } - // 추가 기능 구현 + public List stationsInLine() { + return Collections.unmodifiableList(stationInLine); + } + } diff --git a/src/main/java/subway/domain/MenuRepository.java b/src/main/java/subway/domain/MenuRepository.java new file mode 100644 index 000000000..237a9cf02 --- /dev/null +++ b/src/main/java/subway/domain/MenuRepository.java @@ -0,0 +1,32 @@ +package subway.domain; + +import subway.menu.mainmenu.MainMenu; +import subway.menu.mainmenu.QuitMenu; +import subway.menu.mainmenu.SearchPathMenu; +import subway.menu.searchmenu.ExitMenu; +import subway.menu.searchmenu.SearchMenu; +import subway.menu.searchmenu.SearchShortestDistanceMenu; +import subway.menu.searchmenu.SearchShortestTimeMenu; + +import java.util.*; + +public class MenuRepository { + public static final List mainMenuButtons = new ArrayList<>(Arrays.asList(SearchPathMenu.MENU_BUTTON, QuitMenu.MENU_BUTTON)); + public static final List searchMenuButtons = new ArrayList<>( + Arrays.asList(SearchShortestDistanceMenu.MENU_BUTTON, SearchShortestTimeMenu.MENU_BUTTON, ExitMenu.MENU_BUTTON)); + + public static final List mainMenuNames = new ArrayList<>(Arrays.asList(SearchPathMenu.MENU_NAME, QuitMenu.MENU_NAME)); + public static final List searchMenuNames = new ArrayList<>( + Arrays.asList(SearchShortestDistanceMenu.MENU_NAME, SearchShortestTimeMenu.MENU_NAME, ExitMenu.MENU_NAME)); + + public static final Map mainMenu = new HashMap<>(); + public static final Map searchMenu = new HashMap<>(); + + static { + mainMenu.put(SearchPathMenu.MENU_BUTTON, new SearchPathMenu()); + + searchMenu.put(SearchShortestDistanceMenu.MENU_BUTTON, new SearchShortestDistanceMenu()); + searchMenu.put(SearchShortestTimeMenu.MENU_BUTTON, new SearchShortestTimeMenu()); + } + +} diff --git a/src/main/java/subway/domain/StationRepository.java b/src/main/java/subway/domain/StationRepository.java index 8ed9d103f..f15aa4820 100644 --- a/src/main/java/subway/domain/StationRepository.java +++ b/src/main/java/subway/domain/StationRepository.java @@ -16,10 +16,21 @@ public static void addStation(Station station) { stations.add(station); } + public static Station findStationByName(String name) { + return stations.stream() + .filter(station -> station.getName().equals(name)) + .findFirst() + .get(); + } + public static boolean deleteStation(String name) { return stations.removeIf(station -> Objects.equals(station.getName(), name)); } + public static boolean isStationPresent(String name) { + return stations.stream().anyMatch(station -> station.getName().equals(name)); + } + public static void deleteAll() { stations.clear(); } diff --git a/src/main/java/subway/menu/mainmenu/MainMenu.java b/src/main/java/subway/menu/mainmenu/MainMenu.java new file mode 100644 index 000000000..c41e80628 --- /dev/null +++ b/src/main/java/subway/menu/mainmenu/MainMenu.java @@ -0,0 +1,5 @@ +package subway.menu.mainmenu; + +public interface MainMenu { + void run(); +} diff --git a/src/main/java/subway/menu/mainmenu/QuitMenu.java b/src/main/java/subway/menu/mainmenu/QuitMenu.java new file mode 100644 index 000000000..fe78d575a --- /dev/null +++ b/src/main/java/subway/menu/mainmenu/QuitMenu.java @@ -0,0 +1,6 @@ +package subway.menu.mainmenu; + +public class QuitMenu { + public static final String MENU_BUTTON = "Q"; + public static final String MENU_NAME = "종료"; +} diff --git a/src/main/java/subway/menu/mainmenu/SearchPathMenu.java b/src/main/java/subway/menu/mainmenu/SearchPathMenu.java new file mode 100644 index 000000000..dc4d03f14 --- /dev/null +++ b/src/main/java/subway/menu/mainmenu/SearchPathMenu.java @@ -0,0 +1,33 @@ +package subway.menu.mainmenu; + +import subway.domain.MenuRepository; +import subway.userinterface.ErrorOutput; +import subway.userinterface.Input; + +public class SearchPathMenu implements MainMenu{ + public static final String MENU_BUTTON = "1"; + public static final String MENU_NAME = "경로 조회"; + + public void run() { + boolean runStatus = true; + while(runStatus) { + Input.printSearchMenu(); + runStatus = runSearchMenu(); + } + } + + private boolean runSearchMenu() { + String input = Input.newInput().toUpperCase(); + if (ErrorOutput.isWrongSearchMenuInput(input)) { + return true; + } + + for (String key : MenuRepository.searchMenu.keySet()) { + if (input.equals(key)) { + MenuRepository.searchMenu.get(key).run(); + return true; + } + } + return false; + } +} diff --git a/src/main/java/subway/menu/searchmenu/ExitMenu.java b/src/main/java/subway/menu/searchmenu/ExitMenu.java new file mode 100644 index 000000000..4af9e50e6 --- /dev/null +++ b/src/main/java/subway/menu/searchmenu/ExitMenu.java @@ -0,0 +1,7 @@ +package subway.menu.searchmenu; + +public class ExitMenu { + public static final String MENU_BUTTON = "B"; + public static final String MENU_NAME = "돌아가기"; + +} diff --git a/src/main/java/subway/menu/searchmenu/SearchMenu.java b/src/main/java/subway/menu/searchmenu/SearchMenu.java new file mode 100644 index 000000000..2eddf4774 --- /dev/null +++ b/src/main/java/subway/menu/searchmenu/SearchMenu.java @@ -0,0 +1,5 @@ +package subway.menu.searchmenu; + +public interface SearchMenu { + void run(); +} diff --git a/src/main/java/subway/menu/searchmenu/SearchShortestDistanceMenu.java b/src/main/java/subway/menu/searchmenu/SearchShortestDistanceMenu.java new file mode 100644 index 000000000..863cdf291 --- /dev/null +++ b/src/main/java/subway/menu/searchmenu/SearchShortestDistanceMenu.java @@ -0,0 +1,49 @@ +package subway.menu.searchmenu; + +import subway.domain.Interval; +import subway.domain.Station; +import subway.domain.StationRepository; +import subway.userinterface.ErrorOutput; +import subway.userinterface.InfoOutput; +import subway.userinterface.Input; + +import java.util.List; + +public class SearchShortestDistanceMenu implements SearchMenu { + public static final String MENU_BUTTON = "1"; + public static final String MENU_NAME = "최단 거리"; + + @Override + public void run() { + checkStationInput(); + } + + private void checkStationInput() { + Input.printStartStation(); + String startInput = Input.newInput(); + if (ErrorOutput.printWrongStationInput(startInput)) { + return; + } + + Input.printEndStation(); + String endInput = Input.newInput(); + if (ErrorOutput.printWrongStationInput(endInput) || ErrorOutput.printSameStationNameError(startInput, endInput)) { + return; + } + + searchPath(startInput, endInput); + } + + private void searchPath(String startInput, String endInput) { + try { + Station startStation = StationRepository.findStationByName(startInput); + Station endStation = StationRepository.findStationByName(endInput); + List shortestPath = Interval.shortestDistancePath(startStation, endStation); + + InfoOutput.printSearchResult(Interval.getTotalDistance(shortestPath), Interval.getTotalTime(shortestPath), shortestPath); + } catch (Exception e) { + ErrorOutput.printNotConnectedError(); + } + + } +} diff --git a/src/main/java/subway/menu/searchmenu/SearchShortestTimeMenu.java b/src/main/java/subway/menu/searchmenu/SearchShortestTimeMenu.java new file mode 100644 index 000000000..6eb54fcd0 --- /dev/null +++ b/src/main/java/subway/menu/searchmenu/SearchShortestTimeMenu.java @@ -0,0 +1,49 @@ +package subway.menu.searchmenu; + +import subway.domain.Interval; +import subway.domain.Station; +import subway.domain.StationRepository; +import subway.userinterface.ErrorOutput; +import subway.userinterface.InfoOutput; +import subway.userinterface.Input; + +import java.util.List; + +public class SearchShortestTimeMenu implements SearchMenu{ + public static final String MENU_BUTTON = "2"; + public static final String MENU_NAME = "최단 시간"; + + + @Override + public void run() { + checkStationInput(); + } + + private void checkStationInput() { + Input.printStartStation(); + String startInput = Input.newInput(); + if (ErrorOutput.printWrongStationInput(startInput)) { + return; + } + + Input.printEndStation(); + String endInput = Input.newInput(); + if (ErrorOutput.printWrongStationInput(endInput) || ErrorOutput.printSameStationNameError(startInput, endInput)) { + return; + } + + searchPath(startInput, endInput); + } + + private void searchPath(String startInput, String endInput) { + try { + Station startStation = StationRepository.findStationByName(startInput); + Station endStation = StationRepository.findStationByName(endInput); + List shortestPath = Interval.shortestTimePath(startStation, endStation); + + InfoOutput.printSearchResult(Interval.getTotalDistance(shortestPath), Interval.getTotalTime(shortestPath), shortestPath); + } catch (Exception e) { + ErrorOutput.printNotConnectedError(); + } + } +} diff --git a/src/main/java/subway/userinterface/ErrorOutput.java b/src/main/java/subway/userinterface/ErrorOutput.java new file mode 100644 index 000000000..d60d7b62e --- /dev/null +++ b/src/main/java/subway/userinterface/ErrorOutput.java @@ -0,0 +1,51 @@ +package subway.userinterface; + +import subway.domain.MenuRepository; +import subway.domain.StationRepository; + +public class ErrorOutput { + private static final String ERROR = "\n[ERROR] "; + private static final String NOT_EXISTING_STATION = "존재하지 않는 역 이름입니다."; + private static final String SAME_START_END_INPUT = "출발역과 도착역이 동일합니다."; + private static final String START_END_NOT_CONNECTED = "출발역과 도착역이 서로 연결되어있지 않습니다."; + private static final String INPUT_ERROR = "선택할 수 없는 기능입니다."; + + public static boolean returnStatus(boolean status) { + if (status) { + System.out.println(ERROR + INPUT_ERROR); + } + return status; + } + + public static boolean isWrongMainMenuInput(String input) { + boolean status = MenuRepository.mainMenuButtons.stream().noneMatch(button -> button.equals(input)); + return returnStatus(status); + } + + public static boolean isWrongSearchMenuInput(String input) { + boolean status = MenuRepository.searchMenuButtons.stream().noneMatch(button -> button.equals(input)); + return returnStatus(status); + } + + public static boolean printWrongStationInput(String input) { + if (!StationRepository.isStationPresent(input)) { + System.out.println(ERROR + NOT_EXISTING_STATION); + return true; + } + return false; + } + + public static boolean printSameStationNameError(String start, String end) { + if (start.equals(end)) { + System.out.println(ERROR + SAME_START_END_INPUT); + return true; + } + return false; + } + + public static void printNotConnectedError() { + System.out.println(ERROR + START_END_NOT_CONNECTED); + } + + +} diff --git a/src/main/java/subway/userinterface/InfoOutput.java b/src/main/java/subway/userinterface/InfoOutput.java new file mode 100644 index 000000000..6c36ee2b5 --- /dev/null +++ b/src/main/java/subway/userinterface/InfoOutput.java @@ -0,0 +1,25 @@ +package subway.userinterface; + +import subway.domain.Station; + +import java.util.List; + +public class InfoOutput { + private static final String INFO = "[INFO] "; + private static final String LINE_DIVISION = "---"; + private static final String TOTAL_DISTANCE = "총 거리 : "; + private static final String TOTAL_TIME = "총 시간 : "; + private static final String DISTANCE_UNIT = "km"; + private static final String TIME_UNIT = "분"; + + public static void printSearchResult(int distance, int time, List stations) { + Input.printResultNotification(); + System.out.println(LINE_DIVISION); + System.out.println(INFO + TOTAL_DISTANCE + distance + DISTANCE_UNIT); + System.out.println(INFO + TOTAL_TIME + time + TIME_UNIT); + System.out.println(LINE_DIVISION); + for (Station station : stations) { + System.out.println(INFO + station.getName()); + } + } +} diff --git a/src/main/java/subway/userinterface/Input.java b/src/main/java/subway/userinterface/Input.java new file mode 100644 index 000000000..b5c7913f1 --- /dev/null +++ b/src/main/java/subway/userinterface/Input.java @@ -0,0 +1,53 @@ +package subway.userinterface; + +import subway.domain.MenuRepository; + +import java.util.Scanner; + +public class Input { + private static Scanner scanner = new Scanner(System.in); + + private static final String PREFIX = "\n## "; + private static final String RESULT_NOTIFICATION = "조회 결과"; + private static final String MAIN_MENU = "메인 화면"; + private static final String SEARCH_MENU = "경로 기준"; + private static final String START_STATION_INPUT = "출발역을 입력하세요"; + private static final String END_STATION_INPUT = "도착역을 입력하세요"; + private static final String CHOOSE_MENU = PREFIX + "원하는 기능을 선택하세요."; + + public static String newInput() { + return scanner.nextLine(); + } + + public static void printResultNotification() { + System.out.println(PREFIX + RESULT_NOTIFICATION); + } + + public static void printMainMenu() { + System.out.println(PREFIX + MAIN_MENU); + for (int i = 0; i < MenuRepository.mainMenuButtons.size(); i++) { + System.out.println(MenuRepository.mainMenuButtons.get(i) + ". " + MenuRepository.mainMenuNames.get(i)); + } + System.out.println(CHOOSE_MENU); + } + + public static void printSearchMenu() { + System.out.println(PREFIX + SEARCH_MENU); + for (int i = 0; i < MenuRepository.searchMenuButtons.size(); i++) { + System.out.println(MenuRepository.searchMenuButtons.get(i) + ". " + MenuRepository.searchMenuNames.get(i)); + } + System.out.println(CHOOSE_MENU); + } + + public static void printStartStation() { + System.out.println(PREFIX + START_STATION_INPUT); + } + + public static void printEndStation() { + System.out.println(PREFIX + END_STATION_INPUT); + } + + public static void closeScanner() { + scanner.close(); + } +}