Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 92 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,92 @@
# javascript-subway-final
# 🚇 지하철 노선도 경로 조회 미션

- 등록된 지하철 노선도에서 경로를 조회하는 기능을 구현한다.

### URL

#### https://yungo1846.github.io/javascript-subway-path-precourse/

### 디렉토리 구조

```bash
│ index.html
│ README.md
├─images
│ dijkstra_example.png
│ path_result.gif
│ path_result.jpg
└─src
│ index.js
├─common
│ alertMessage.js
│ checkInput.js
│ constant.js
│ StationInfo.js
├─events
│ FindRoadEvent.js
├─renders
│ renderRoadResult.js
└─utils
Dijkstra.js
```

### 초기 설정

- 프로그램 시작 시 역, 노선, 구간 데이터를 초기 설정 해야 한다.
- 거리와 소요 시간은 양의 정수이며 단위는 km와 분을 의미한다.
- 아무 역과도 연결되어 있지 않은 '아현역' 추가 (예외사항 테스트용)
- 아래의 사전 등록 정보로 반드시 초기 설정을 한다.

```
1. 지하철역으로 교대, 강남, 역삼, 남부터미널, 양재, 양재시민의숲, 매봉 역 정보가 등록되어 있다.
2. 지하철 노선으로 2호선, 3호선, 신분당선이 등록되어 있다.
3. 노선에 역이 아래와 같이 등록되어 있다.(왼쪽 끝이 상행 종점)
- 2호선: 교대 - ( 2km / 3분 ) - 강남 - ( 2km / 3분 ) - 역삼
- 3호선: 교대 - ( 3km / 2분 ) - 남부터미널 - ( 6km / 5분 ) - 양재 - ( 1km / 1분 ) - 매봉
- 신분당선: 강남 - ( 2km / 8분 ) - 양재 - ( 10km / 3분 ) - 양재시민의숲
- 아현역
```

### 경로 조회 기능

<img src="/images/path_result.jpg" width="100%">

- 출발역과 도착역을 입력받아 경로를 조회한다. (완료)
- 경로 조회 시 총 거리, 총 소요 시간을 함께 출력한다. (완료)
- 경로 조회 시 `최단 거리` 또는 `최소 시간` 옵션을 선택할 수 있다. (완료)

### 예외 처리

- 출발역과 도착역은 2글자 이상이어야 한다. (완료)
- 존재하지 않는 역을 출발역 또는 도착역으로 입력할 수 없다. (완료)
- 경로 조회 시 출발역과 도착역이 같을 수 없다. (완료)
- 경로 조회 시 출발역과 도착역이 연결되지 않으면 경로를 조회할 수 없다. (완료)

=> 기본 노선 정보에 아무 역과도 연결되있지 않은 아현역을 입력하여 확인 가능

- 그 외 정상적으로 프로그램이 수행되지 않은 경우 `alert`으로 에러를 출력한다. (완료)

<br>

## 💻 프로그래밍 실행 결과

### 경로 조회

<img src="/images/path_result.gif" width="100%">

## ✅ 프로그래밍 요구사항

### 길찾기 관련 기능

- 출발역을 입력하는 input 태그는 `departure-station-name-input` id 속성값을 가진다.
- 도착역을 입력하는 input 태그는 `arrival-station-name-input` id 속성값을 가진다.
- 최단거리, 최소시간을 선택하는 radio는 `search-type` name 속성값을 가진다.
- **radio option의 default 값은 최단거리이다.**
- 길찾기 버튼은 `search-button` id 속성값을 가진다.
- 📝 결과는 `table`을 이용하여 보여준다.
Binary file added images/dijkstra_example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/path_result.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/path_result.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 37 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="kr">
<head>
<meta charset="UTF-8" />
<title>지하철 길찾기</title>
</head>
<body>
<div id="app">
<h1>🚇 지하철 길찾기</h1>
<div class="input-container">
<h4>
<span>출발역</span>
<input type="text" id="departure-station-name-input" />
</h4>
<h4>
<span>도착역</span>
<input type="text" id="arrival-station-name-input" />
</h4>
<h4>
<input
type="radio"
id="shortest-path-radio"
name="search-type"
value="최단거리"
checked="checked"
/>
<label for="shortest-path-radio">최단거리</label>
<input type="radio" id="min-time-path-radio" name="search-type" value="최소시간" />
<label for="min-time-path-radio">최소시간</label>
</h4>
<button id="search-button">길 찾기</button>
</div>
</div>
<div id="result-container"></div>
<script type="module" src="src/index.js"></script>
</body>
</html>
36 changes: 36 additions & 0 deletions src/common/StationInfo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export const stations = [
{ name: "교대" },
{ name: "강남" },
{ name: "역삼" },
{ name: "남부터미널" },
{ name: "양재" },
{ name: "매봉" },
{ name: "양재시민의숲" },
{ name: "아현" }, // island node to test none connected stations
];

// sections: [출발역, 다음역, 거리, 소요시간]
export const lines = [
{
name: "2호선",
sections: [
["교대", "강남", 2, 3],
["강남", "역삼", 2, 3],
],
},
{
name: "3호선",
sections: [
["교대", "남부터미널", 3, 2],
["남부터미널", "양재", 6, 5],
["양재", "매봉", 1, 1],
],
},
{
name: "신분당선",
sections: [
["강남", "양재", 2, 8],
["양재", "양재시민의숲", 10, 3],
],
},
];
9 changes: 9 additions & 0 deletions src/common/alertMessage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { constant } from "./constant.js";

export const alertMessage = {
SHORT_LENGTH_ERROR: `역 이름은 최소 ${constant.minLength} 글자 이상 입력해야 합니다.`,
SAME_DESTINATION_ERROR: `출발역과 도착역이 같습니다`,
NONE_SELECTED_RADIO: `최단거리와 최소시간 중 하나를 선택해주세요!`,
NOT_EXIST_STATION: `존재하지 않는 역이 입력되었습니다.`,
NOT_CONNECTED_LINE: `출발역과 도착역이 서로 연결되어 있지 않습니다.`,
};
18 changes: 18 additions & 0 deletions src/common/checkInput.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { constant } from "./constant.js";
export function isSatisfyLength(len) {
if (String(len).length < constant.minLength) {
return false;
}

return true;
}

export function isExistStation(stations, station) {
for (let i = 0; i < stations.length; i++) {
if (station === stations[i].name) {
return true;
}
}

return false;
}
3 changes: 3 additions & 0 deletions src/common/constant.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const constant = {
minLength: 2,
};
126 changes: 126 additions & 0 deletions src/events/FindRoadEvent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import Dijkstra from "../utils/Dijkstra.js";
import renderRoadResult from "../renders/renderRoadResult.js";
import { alertMessage } from "../common/alertMessage.js";
import { stations, lines } from "../common/StationInfo.js";
import { isSatisfyLength, isExistStation } from "../common/checkInput.js";

export default function FindRoadEvent() {
const dijkstra = new Dijkstra();
const startStation = document.getElementById("departure-station-name-input").value;
const endStation = document.getElementById("arrival-station-name-input").value;
const information = whichRadioChecked();
if (!isValidInput()) {
return;
}
const shortestPath = getShortestPath(startStation, endStation);
if (!isStationsConneted()) {
return;
}
const totalWeight = getShortestWeight(shortestPath);
const totalDistance = totalWeight[0];
const totalTime = totalWeight[1];
renderRoadResult(information, shortestPath, totalDistance, totalTime);

function isValidInput() {
if (!isSatisfyLength(startStation) || !isSatisfyLength(endStation)) {
alert(alertMessage.SHORT_LENGTH_ERROR);
return false;
} else if (startStation === endStation) {
alert(alertMessage.SAME_DESTINATION_ERROR);
return false;
} else if (!isExistStation(stations, startStation) || !isExistStation(stations, endStation)) {
alert(alertMessage.NOT_EXIST_STATION);
return false;
} else if (information === undefined) {
alert(alertMessage.NONE_SELECTED_RADIO);
return false;
}
return true;
}

function whichRadioChecked() {
const shortestPathRadio = document.getElementById("shortest-path-radio");
const minTimeRadio = document.getElementById("min-time-path-radio");

if (shortestPathRadio.checked) {
return shortestPathRadio.value;
} else if (minTimeRadio.checked) {
return minTimeRadio.value;
} else {
return undefined;
}
}

function isStationsConneted() {
if (shortestPath === undefined) {
alert(alertMessage.NOT_CONNECTED_LINE);
return false;
}
return true;
}

function _getShortestPath(i, j, index) {
for (let k = 0; k < lines[j].sections.length; k++) {
if (stations[i].name === lines[j].sections[k][0]) {
dijkstra.addEdge(
lines[j].sections[k][0],
lines[j].sections[k][1],
lines[j].sections[k][index]
);
}
}
}

function getShortestPath(start, end) {
let index;
if (information == "최단거리") {
index = 2;
} else if (information == "최소시간") {
index = 3;
}
for (let i = 0; i < stations.length; i++) {
for (let j = 0; j < lines.length; j++) {
_getShortestPath(i, j, index);
}
}

return dijkstra.findShortestPath(start, end);
}

function _getShortestWeight(shortestPath, i, j) {
let _distance = 0;
let _time = 0;
for (let k = 0; k < lines[j].sections.length; k++) {
if (
lines[j].sections[k][0] === shortestPath[i] &&
lines[j].sections[k][1] === shortestPath[i + 1]
) {
_distance += lines[j].sections[k][2];
_time += lines[j].sections[k][3];
} else if (
lines[j].sections[k][1] === shortestPath[i] &&
lines[j].sections[k][0] === shortestPath[i + 1]
) {
_distance += lines[j].sections[k][2];
_time += lines[j].sections[k][3];
}
}

return [_distance, _time];
}

function getShortestWeight(shortestPath) {
let distance = 0;
let time = 0;
let temp;
for (let i = 0; i < shortestPath.length; i++) {
for (let j = 0; j < lines.length; j++) {
temp = _getShortestWeight(shortestPath, i, j);
distance += temp[0];
time += temp[1];
}
}

return [distance, time];
}
}
8 changes: 8 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import FindRoadEvent from "./events/FindRoadEvent.js";

export default function SubwayNavigation() {
const inputButton = document.getElementById("search-button");
inputButton.addEventListener("click", FindRoadEvent);
}

new SubwayNavigation();
46 changes: 46 additions & 0 deletions src/renders/renderRoadResult.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
function resultContainerTemplate(information, result) {
return `<h2>📑결과<h3>
<h3>${information}<h3>
<table id="result-table" border="1" style="text-align: center" >
<tr>
<th>총 거리</th>
<th>총 소요 시간</th>
</tr>
</table>`;
}

function initResultContainer(information) {
const resultContainer = document.getElementById("result-container");
resultContainer.innerHTML = resultContainerTemplate(information);
}

function resultListTemplate(shortestPath, totalDistance, totalTime) {
return `<tr>
<td>
${totalDistance}Km
</td>
<td>
${totalTime}분
</td>
</tr>
<tr>
<td colspan="2">
${shortestPath
.map((station, i) => (i !== shortestPath.length - 1 ? station + "=>" : station))
.join("")}
</td>
</tr>`;
}

function initResultList(shortestPath, totalDistance, totalTime) {
const resultTable = document.getElementById("result-table");
resultTable.insertAdjacentHTML(
"beforeend",
resultListTemplate(shortestPath, totalDistance, totalTime)
);
}

export default function renderRoadResult(information, shortestPath, totalDistance, totalTime) {
initResultContainer(information);
initResultList(shortestPath, totalDistance, totalTime);
}
Loading