diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..7b845f736 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,37 @@ +# 지하철 노선도 경로 조회 미션 +우테코 3기 2차 코딩테스트(2020-12-19 13:00 ~ 18:00) + +## 기능 구현 목록 +* **입력** + + [메인 화면] + - [x] 원하는 기능을 입력한다. + + [경로 조회] + - [x] 원하는 기능을 입력한다. + - [x] 출발역과 도착역을 입력한다. +* **처리** + + [초기화] + + [x] 지하철(Station) 초기 정보를 저장한다. + + [x] 노선(Line) 초기 정보를 저장한다. + + [ ] 노선에 연결된 역 초기 정보를 저장한다. + + [메인 화면] + - [x] 메인 화면의 메뉴를 선택한다. + - [예외 처리] 선택 가능한 기능인지 검증한다. + + [경로 조회] + + [x] 경로 기준을 선택한다. + - [예외 처리] 선택 가능한 기능인지 검증한다. + + [x] 등록된 역을 검색한다. + + [ ] 경로를 조회한다. + - [예외 처리] 출발역이 존재하는지 검증한다. + - [예외 처리] 도착역이 존재하는지 검증한다. + - [예외 처리] 출발역과 도착역이 같은지 검증한다. + - [예외 처리] 출발역과 도착역이 연결되어있는지 검증한다. + + [x] 경로의 총 거리를 계산한다. + + [x] 경로의 총 소요 시간을 계산한다. +* **출력** + + [메인 화면] + - [x] 메인 화면을 출력한다. + + [경로 조회] + - [x] 경로 조회 화면을 출력한다. + - [x] 총 거리를 출력한다 + - [x] 총 소요 시간을 출력한다. + - [x] 출발역 부터 도착역까지 역을 출력한다. \ No newline at end of file diff --git a/src/main/java/subway/Application.java b/src/main/java/subway/Application.java index 0bcf786cc..53306cdc3 100644 --- a/src/main/java/subway/Application.java +++ b/src/main/java/subway/Application.java @@ -1,10 +1,14 @@ package subway; +import subway.controller.SubwayController; + import java.util.Scanner; public class Application { public static void main(String[] args) { final Scanner scanner = new Scanner(System.in); // TODO: 프로그램 구현 + SubwayController subway = new SubwayController(); + subway.start(scanner); } -} +} \ No newline at end of file diff --git a/src/main/java/subway/controller/SubwayController.java b/src/main/java/subway/controller/SubwayController.java new file mode 100644 index 000000000..65a864cff --- /dev/null +++ b/src/main/java/subway/controller/SubwayController.java @@ -0,0 +1,28 @@ +package subway.controller; + +import subway.domain.MenuType; +import subway.service.InputService; +import subway.view.OutputView; +import subway.service.SubwayService; + +import java.util.Scanner; + +import static subway.domain.MenuType.MAIN_MENU_RANGE; + +public class SubwayController extends InputService { + private final SubwayService subwayService = new SubwayService(); + + public void start(Scanner scanner) { + String menu = ""; + try { + while (!MenuType.MAIN_EXIT.isKeyEquals(menu)) { + OutputView.printMainMenu(); + menu = inputSelectMenu(scanner, MAIN_MENU_RANGE); + subwayService.selectMainMenu(scanner, menu); + } + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + start(scanner); + } + } +} \ No newline at end of file diff --git a/src/main/java/subway/domain/LineStationRepository.java b/src/main/java/subway/domain/LineStationRepository.java new file mode 100644 index 000000000..e3fa6dc81 --- /dev/null +++ b/src/main/java/subway/domain/LineStationRepository.java @@ -0,0 +1,36 @@ +package subway.domain; + +import org.jgrapht.GraphPath; +import org.jgrapht.alg.shortestpath.DijkstraShortestPath; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.WeightedMultigraph; + +import static subway.domain.SubwayPathFactory.creatPathFirstGraph; +import static subway.domain.SubwayPathFactory.creatTimeFirstGraph; + +public class LineStationRepository { + private static WeightedMultigraph lineStation + = new WeightedMultigraph<>(DefaultWeightedEdge.class); + + public static void addLineStationOfSection(String startStation, String endStation, int weight) { + lineStation.setEdgeWeight(lineStation.addEdge(startStation, endStation), weight); + } + + public static void addVertex(String startStation) { + lineStation.addVertex(startStation); + } + + public static GraphPath findShortestPath(String start, String end) { + SubwayPathFactory.init(); + creatPathFirstGraph(); + DijkstraShortestPath dijkstraShortestPath = new DijkstraShortestPath(lineStation); + return dijkstraShortestPath.getPath(start, end); + } + + public static GraphPath findShortestTime(String start, String end) { + SubwayPathFactory.init(); + creatTimeFirstGraph(); + DijkstraShortestPath dijkstraShortestPath = new DijkstraShortestPath(lineStation); + return dijkstraShortestPath.getPath(start, end); + } +} \ No newline at end of file diff --git a/src/main/java/subway/domain/MenuType.java b/src/main/java/subway/domain/MenuType.java new file mode 100644 index 000000000..14c985ba8 --- /dev/null +++ b/src/main/java/subway/domain/MenuType.java @@ -0,0 +1,36 @@ +package subway.domain; + +import java.util.Arrays; +import java.util.List; + +public enum MenuType { + MAIN_MENU_RANGE(Arrays.asList("1","2","Q")), + MAIN_SEARCH_PATH("1"), + MAIN_EXIT("Q"), + SEARCH_PATH_MENU_RANGE(Arrays.asList("1","2","B")), + SEARCH_SHORTEST_PATH("1"), + SEARCH_SHORTEST_TIME("2"), + BACK("B"); + + private String key; + private List keys; + + private MenuType(String key) { + this.key = key; + } + + private MenuType(List keys) { + this.keys = keys; + } + + public boolean isKeyEquals(String menu) { + return this.key.equals(menu); + } + + public void validateMenuRange(String menu) { + boolean isContains = this.keys.contains(menu); + if (!isContains) { + throw new IllegalArgumentException("\n[ERROR] 선택할 수 없는 기능입니다."); + } + } +} \ No newline at end of file diff --git a/src/main/java/subway/domain/Section.java b/src/main/java/subway/domain/Section.java new file mode 100644 index 000000000..c00f1db06 --- /dev/null +++ b/src/main/java/subway/domain/Section.java @@ -0,0 +1,29 @@ +package subway.domain; + +import org.jgrapht.graph.DefaultWeightedEdge; + +public class Section extends DefaultWeightedEdge { + private Station startStation; + private Station endStation; + private int time; + private int distance; + + public Section(Station startStation, Station endStation, int time, int distance) { + this.startStation = startStation; + this.endStation = endStation; + this.time = time; + this.distance = distance; + } + + public int getTime() { + return time; + } + + public int getDistance() { + return distance; + } + + public static Section of(Station start, Station end, int time, int distance) { + return new Section(start, end, time, distance); + } +} \ No newline at end of file diff --git a/src/main/java/subway/domain/Station.java b/src/main/java/subway/domain/Station.java index bdb142590..1b21d0173 100644 --- a/src/main/java/subway/domain/Station.java +++ b/src/main/java/subway/domain/Station.java @@ -12,4 +12,8 @@ public String getName() { } // 추가 기능 구현 + @Override + public String toString() { + return "[INFO] " + name + "\n"; + } } diff --git a/src/main/java/subway/domain/StationRepository.java b/src/main/java/subway/domain/StationRepository.java index 8ed9d103f..b5fc386ca 100644 --- a/src/main/java/subway/domain/StationRepository.java +++ b/src/main/java/subway/domain/StationRepository.java @@ -23,4 +23,14 @@ public static boolean deleteStation(String name) { public static void deleteAll() { stations.clear(); } + + public static void findStationByName(String stationName) { + Station station = stations.stream() + .filter(s -> s.getName().equals(stationName)) + .findAny() + .orElse(null); + if (station.equals(null)) { + throw new IllegalArgumentException("\n[ERROR] 해당 역은 존재하지 않습니다."); + } + } } diff --git a/src/main/java/subway/domain/SubwayPathFactory.java b/src/main/java/subway/domain/SubwayPathFactory.java new file mode 100644 index 000000000..c4fc6f17b --- /dev/null +++ b/src/main/java/subway/domain/SubwayPathFactory.java @@ -0,0 +1,57 @@ +package subway.domain; + +import static subway.domain.LineStationRepository.*; +import static subway.domain.StationRepository.findStationByName; + +public class SubwayPathFactory { + private static String[] startStations = {"교대역", "강남역", "강남역", "역삼역", "교대역", "남부터미널역", "남부터미널역", "양재역", + "양재역", "매봉역", "강남역", "양재역", "양재역", "양재시민의숲역"}; + private static String[] endStations = {"강남역", "교대역", "역삼역", "강남역", "남부터미널역", "교대역", "양재역", "남부터미널역", + "매봉역", "양재역", "양재역", "강남역", "양재시민의숲역", "양재역"}; + private static int[] distances = {2, 2, 2, 2, 3, 3, 6, 6, 1, 1, 1, 1, 10, 10}; + private static int[] times = {3, 3, 3, 3, 2, 2, 5, 5, 1, 1, 8, 8, 3, 3}; + + public static void init() { + initStation(); + initLine(); + initGraphVertex(); + } + + public static void creatPathFirstGraph() { + for (int i = 0; i < startStations.length; i++) { + addLineStationOfSection(startStations[i], endStations[i], distances[i]); + } + } + + public static void creatTimeFirstGraph() { + for (int i = 0; i < startStations.length; i++) { + addLineStationOfSection(startStations[i], endStations[i], times[i]); + } + } + + private static void initStation() { + StationRepository.addStation(new Station("교대역")); + StationRepository.addStation(new Station("강남역")); + StationRepository.addStation(new Station("역삼역")); + StationRepository.addStation(new Station("남부터미널역")); + StationRepository.addStation(new Station("양재역")); + StationRepository.addStation(new Station("양재시민의숲역")); + StationRepository.addStation(new Station("매봉역")); + } + + private static void initLine() { + LineRepository.addLine(new Line("2호선")); + LineRepository.addLine(new Line("3호선")); + LineRepository.addLine(new Line("신분당선")); + } + + private static void initGraphVertex() { + addVertex("교대역"); + addVertex("강남역"); + addVertex("역삼역"); + addVertex("남부터미널역"); + addVertex("양재역"); + addVertex("양재시민의숲역"); + addVertex("매봉역"); + } +} \ No newline at end of file diff --git a/src/main/java/subway/service/InputService.java b/src/main/java/subway/service/InputService.java new file mode 100644 index 000000000..aad035ff6 --- /dev/null +++ b/src/main/java/subway/service/InputService.java @@ -0,0 +1,28 @@ +package subway.service; + +import subway.domain.MenuType; +import subway.view.InputView; + +import java.util.Scanner; + +public class InputService extends InputView { + + public String inputSelectMenu(Scanner scanner, MenuType menuType) { + inputSelectMenuMessage(); + String menu = scanner.nextLine(); + menuType.validateMenuRange(menu); + return menu; + } + + public String inputStartStationName(Scanner scanner) { + inputStartStationNameMessage(); + String stationName = scanner.nextLine(); + return stationName; + } + + public String inputEndStationName(Scanner scanner) { + inputEndStationNameMessage(); + String stationName = scanner.nextLine(); + return stationName; + } +} \ No newline at end of file diff --git a/src/main/java/subway/service/SearchPathService.java b/src/main/java/subway/service/SearchPathService.java new file mode 100644 index 000000000..83007a521 --- /dev/null +++ b/src/main/java/subway/service/SearchPathService.java @@ -0,0 +1,73 @@ +package subway.service; + +import org.jgrapht.GraphPath; +import subway.domain.LineStationRepository; +import subway.view.OutputView; + +import java.util.Scanner; + +import static subway.domain.MenuType.*; +import static subway.domain.StationRepository.findStationByName; +import static subway.view.OutputView.printResult; + +public class SearchPathService extends InputService { + + public boolean selectSearchPathMenu(Scanner scanner, String menu) { + if (SEARCH_SHORTEST_PATH.isKeyEquals(menu)) { + return searchShortestPath(scanner); + } + if (SEARCH_SHORTEST_TIME.isKeyEquals(menu)) { + return searchShortestTime(scanner); + } + if (BACK.isKeyEquals(menu)) { + return true; + } + return false; + } + + private boolean searchShortestPath(Scanner scanner) { + try { + String startStationName = inputStartStationName(scanner); + String endStationName = inputEndStationName(scanner); + calculationShortestPath(startStationName, endStationName); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + return false; + } + return true; + } + + private boolean searchShortestTime(Scanner scanner) { + try { + String startStationName = inputStartStationName(scanner); + String endStationName = inputEndStationName(scanner); + calculationShortestTime(startStationName, endStationName); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + return false; + } + return true; + } + + private void calculationShortestPath(String startStationName, String endStationName) { + if (startStationName.equals(endStationName)) { + throw new IllegalArgumentException("[ERROR] 출발역과 도착역이 동일합니다."); + } + findStationByName(startStationName); + findStationByName(endStationName); + GraphPath shortestPath = LineStationRepository.findShortestPath(startStationName, endStationName); + GraphPath shortestTime = LineStationRepository.findShortestTime(startStationName, endStationName); + printResult(shortestPath, shortestTime); + } + + private void calculationShortestTime(String startStationName, String endStationName) { + if (startStationName.equals(endStationName)) { + throw new IllegalArgumentException("[ERROR] 출발역과 도착역이 동일합니다."); + } + findStationByName(startStationName); + findStationByName(endStationName); + GraphPath shortestPath = LineStationRepository.findShortestPath(startStationName, endStationName); + GraphPath shortestTime = LineStationRepository.findShortestTime(startStationName, endStationName); + printResult(shortestPath, shortestTime); + } +} \ No newline at end of file diff --git a/src/main/java/subway/service/SubwayService.java b/src/main/java/subway/service/SubwayService.java new file mode 100644 index 000000000..12a15c901 --- /dev/null +++ b/src/main/java/subway/service/SubwayService.java @@ -0,0 +1,30 @@ +package subway.service; + +import java.util.Scanner; + +import static subway.domain.MenuType.MAIN_SEARCH_PATH; +import static subway.domain.MenuType.SEARCH_PATH_MENU_RANGE; +import static subway.view.OutputView.printSearchPathMenu; + +public class SubwayService extends InputService { + private final SearchPathService searchPathService = new SearchPathService(); + + public void selectMainMenu(Scanner scanner, String menu) { + if (MAIN_SEARCH_PATH.isKeyEquals(menu)) { + searchPathManagement(scanner); + } + } + + private void searchPathManagement(Scanner scanner) { + try { + printSearchPathMenu(); + String menu = inputSelectMenu(scanner, SEARCH_PATH_MENU_RANGE); + if (!searchPathService.selectSearchPathMenu(scanner, menu)) { + searchPathManagement(scanner); + } + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + searchPathManagement(scanner); + } + } +} \ No newline at end of file diff --git a/src/main/java/subway/view/InputView.java b/src/main/java/subway/view/InputView.java new file mode 100644 index 000000000..abc8ccba5 --- /dev/null +++ b/src/main/java/subway/view/InputView.java @@ -0,0 +1,17 @@ +package subway.view; + +public class InputView { + private static final char NEW_LINE = '\n'; + + public void inputSelectMenuMessage() { + System.out.println(NEW_LINE+"## 원하는 기능을 선택하세요."); + } + + public void inputStartStationNameMessage() { + System.out.println(NEW_LINE+"## 출발역을 입력하세요."); + } + + public void inputEndStationNameMessage() { + System.out.println(NEW_LINE+"## 도착역을 입력하세요."); + } +} \ No newline at end of file diff --git a/src/main/java/subway/view/OutputView.java b/src/main/java/subway/view/OutputView.java new file mode 100644 index 000000000..2f36706c6 --- /dev/null +++ b/src/main/java/subway/view/OutputView.java @@ -0,0 +1,28 @@ +package subway.view; + +import org.jgrapht.GraphPath; + +public class OutputView { + private static final char NEW_LINE = '\n'; + + public static void printMainMenu() { + System.out.println(NEW_LINE+"## 메인 화면"); + System.out.println("1. 경로 조회"); + System.out.println("Q. 종료"); + } + + public static void printSearchPathMenu() { + System.out.println(NEW_LINE+"## 경로 기준"); + System.out.println("1. 최단 거리"); + System.out.println("2. 최소 시간"); + System.out.println("B. 돌아가기"); + } + + public static void printResult(GraphPath graphPath, GraphPath graphTime) { + System.out.println("\n## 조회 결과"); + System.out.println("[INFO] 총 거리: " + graphPath.getWeight()); + System.out.println("[INFO] 총 소요 시간: " + graphTime.getWeight()); + System.out.println("[INFO] ---"); + System.out.println(graphPath.getEdgeList()); + } +} \ No newline at end of file diff --git a/src/test/java/subway/domain/LineStationTest.java b/src/test/java/subway/domain/LineStationTest.java new file mode 100644 index 000000000..baf53ff48 --- /dev/null +++ b/src/test/java/subway/domain/LineStationTest.java @@ -0,0 +1,37 @@ +package subway.domain; + +import org.jgrapht.GraphPath; +import org.jgrapht.alg.shortestpath.DijkstraShortestPath; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.WeightedMultigraph; +import org.junit.jupiter.api.Test; + +class LineStationTest { + + @Test + public void 구간_정보를_등록한다() throws Exception { + //given + WeightedMultigraph lineStation + = new WeightedMultigraph<>(DefaultWeightedEdge.class); + Station s1 = new Station("강남역"); + Station s2 = new Station("양재역"); + Station s3 = new Station("홍대역"); + + lineStation.addVertex(s1); + lineStation.addVertex(s2); + lineStation.addVertex(s3); + lineStation.setEdgeWeight(lineStation.addEdge(s1,s2), 2); + lineStation.setEdgeWeight(lineStation.addEdge(s2,s3), 2); + lineStation.setEdgeWeight(lineStation.addEdge(s1,s3), 100); + + //when + DijkstraShortestPath result = new DijkstraShortestPath(lineStation); + GraphPath path = result.getPath(s1, s3); + System.out.println(path.getWeight()); + //then + for (Object station : path.getEdgeList()) { + System.out.println(station.toString()); + } + System.out.println(path.toString()); + } +} \ No newline at end of file