diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 0000000..3a2021c
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,28 @@
+{
+ "env": {
+ "browser": true,
+ "es2021": true,
+ "node": true
+ },
+ "extends": [
+ "eslint:recommended",
+ "airbnb-base",
+ "prettier"
+ ],
+ "parserOptions": {
+ "ecmaVersion": 2015,
+ "sourceType": "module"
+ },
+ "rules": {
+ "no-console": "off",
+ "no-plusplus": "off",
+ "class-methods-use-this": "off",
+ "radix": "off",
+ "no-restricted-properties": "off",
+ "max-depth" : ["error", 2],
+ "no-return-assign" : "off",
+ "no-continue" : "off",
+ "no-param-reassign" : "off",
+ "import/extensions" : "off"
+ }
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..98d3dc8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+.ides
+.DS_Store
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..13a6d05
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,8 @@
+{
+ "singleQuote": true,
+ "printWidth": 100,
+ "tabWidth": 2,
+ "useTabs": false,
+ "semi": true,
+ "arrowParens": "always"
+}
diff --git a/README.md b/README.md
index 51abd25..3fa16a7 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,129 @@
-# javascript-subway-final
\ No newline at end of file
+# ๐ ์งํ์ฒ ๋
ธ์ ๋ ๊ฒฝ๋ก ์กฐํ ๋ฏธ์
+- ๋ฑ๋ก๋ ์งํ์ฒ ๋
ธ์ ๋์์ ๊ฒฝ๋ก๋ฅผ ์กฐํํ๋ ๊ธฐ๋ฅ์ ๊ตฌํํ๋ค.
+
+## ๐ ๊ธฐ๋ฅ ๊ตฌํ ๋ชฉ๋ก
+### ์ด๊ธฐ ์ค์
+0. ๊ณตํต์กฐ๊ฑด : ์ ์์ ์ผ๋ก ํ๋ก๊ทธ๋จ์ด ์ํ๋์ง ์์ ๊ฒฝ์ฐ `alert`์ผ๋ก ์๋ฌ๋ฅผ ์ถ๋ ฅํ๋ค.
+1. ์ฌ์ฉ์๊ฐ ํ๋ก๊ทธ๋จ์ ์์ํ๋ฉด ์ญ, ๋
ธ์ , ๊ตฌ๊ฐ ๋ฐ์ดํฐ๊ฐ ์ด๊ธฐํ๋๋ค.
+ - [์กฐ๊ฑด] ์์ ์๊ฐ๊ณผ ๊ฑฐ๋ฆฌ๋ ์์ ์ ์์ด๋ค.
+```
+1. ์งํ์ฒ ์ญ์ผ๋ก ๊ต๋, ๊ฐ๋จ, ์ญ์ผ, ๋จ๋ถํฐ๋ฏธ๋, ์์ฌ, ์์ฌ์๋ฏผ์์ฒ, ๋งค๋ด ์ญ ์ ๋ณด๊ฐ ๋ฑ๋ก๋์ด ์๋ค.
+2. ์งํ์ฒ ๋
ธ์ ์ผ๋ก 2ํธ์ , 3ํธ์ , ์ ๋ถ๋น์ ์ด ๋ฑ๋ก๋์ด ์๋ค.
+3. ๋
ธ์ ์ ์ญ์ด ์๋์ ๊ฐ์ด ๋ฑ๋ก๋์ด ์๋ค.(์ผ์ชฝ ๋์ด ์ํ ์ข
์ )
+ - 2ํธ์ : ๊ต๋ - ( 2km / 3๋ถ ) - ๊ฐ๋จ - ( 2km / 3๋ถ ) - ์ญ์ผ
+ - 3ํธ์ : ๊ต๋ - ( 3km / 2๋ถ ) - ๋จ๋ถํฐ๋ฏธ๋ - ( 6km / 5๋ถ ) - ์์ฌ - ( 1km / 1๋ถ ) - ๋งค๋ด
+ - ์ ๋ถ๋น์ : ๊ฐ๋จ - ( 2km / 8๋ถ ) - ์์ฌ - ( 10km / 3๋ถ ) - ์์ฌ์๋ฏผ์์ฒ
+```
+2. ์ฌ์ฉ์๋ ์ถ๋ฐ์ญ๊ณผ ๋์ฐฉ์ญ์ ์
๋ ฅํ ์ ์๋ค.
+ - [์์ธ์ฒ๋ฆฌ] ๋ฑ๋ก๋์ด ์์ง ์์ ์ญ์ ์
๋ ฅํ๊ฑด ์๋์ง ๊ฒ์ฆํ๋ค.
+ - [์์ธ์ฒ๋ฆฌ] ์ญ์ ์ด๋ฆ์ ๋ ๊ธ์ ์ด์์ผ๋ก ์ด๋ฃจ์ด์ ธ ์๋ค.
+ - [์์ธ์ฒ๋ฆฌ] ์ถ๋ฐ์ญ๊ณผ ๋์ฐฉ์ญ์ด ๋ค๋ฅธ ์ญ์ธ์ง ๊ฒ์ฆํด์ผ ํ๋ค.
+3. ์ฌ์ฉ์๊ฐ ๊ธธ์ฐพ๊ธฐ ๋ฒํผ์ ๋๋ฅด๋ฉด `์ต๋จ ๊ฑฐ๋ฆฌ` ๋๋ `์ต์ ์๊ฐ` ์ต์
์ ์ ํํ ์ ์๋ค.
+4. ์ต๋จ๊ฑฐ๋ฆฌ, ์ต์์๊ฐ์ ์ ํ์ ๋ฐ๋ผ ์ด ๊ฑฐ๋ฆฌ ๋ฐ ์ด ์์์๊ฐ์ ํจ๊ป ์ถ๋ ฅํ๋ค.
+ - [์์ธ์ฒ๋ฆฌ] ์ถ๋ฐ์ญ๊ณผ ๋์ฐฉ์ญ์ด ์ฐ๊ฒฐ๋์๋์ง ๊ฒ์ฆํ๋ค.
+
+## โ
ํ๋ก๊ทธ๋๋ฐ ์๊ตฌ์ฌํญ
+### ๊ธธ์ฐพ๊ธฐ ๊ด๋ จ ๊ธฐ๋ฅ
+- ์ถ๋ฐ์ญ์ ์
๋ ฅํ๋ input ํ๊ทธ๋ `departure-station-name-input` id ์์ฑ๊ฐ์ ๊ฐ์ง๋ค.
+- ๋์ฐฉ์ญ์ ์
๋ ฅํ๋ input ํ๊ทธ๋ `arrival-station-name-input` id ์์ฑ๊ฐ์ ๊ฐ์ง๋ค.
+- ์ต๋จ๊ฑฐ๋ฆฌ, ์ต์์๊ฐ์ ์ ํํ๋ radio๋ `search-type` name ์์ฑ๊ฐ์ ๊ฐ์ง๋ค.
+ - **radio option์ default ๊ฐ์ ์ต๋จ๊ฑฐ๋ฆฌ์ด๋ค.**
+- ๊ธธ์ฐพ๊ธฐ ๋ฒํผ์ `search-button` id ์์ฑ๊ฐ์ ๊ฐ์ง๋ค.
+- ๐ ๊ฒฐ๊ณผ๋ `table`์ ์ด์ฉํ์ฌ ๋ณด์ฌ์ค๋ค.
+
+## โ๏ธํํธ
+## ๋ฐ์ดํฐ ์ด๊ธฐํ
+์๋ฐ์คํฌ๋ฆฝํธ์์ ๋ฐ์ดํฐ๋ฅผ ์ด๊ธฐํํ๋ ๋ฐฉ๋ฒ ์ค์ ํ๋๋ ์๋์ ๊ฐ์ด data๋ฅผ `export`ํ๊ณ , `import`ํ๋ ๊ฒ์ด๋ค.
+
+```javascript
+export const users = [
+ {
+ name: 'Alt'
+ },
+ {
+ name: 'Jamie'
+ },
+ {
+ name: 'Sony'
+ }
+]
+
+export const courses = [
+ {
+ name: 'frontend',
+ },
+ {
+ name: 'backend',
+ },
+ {
+ name: 'iOS',
+ },
+ {
+ name: 'Android',
+ }
+]
+
+```
+์์ ๊ฐ์ด ๋ฐ์ดํฐ๋ฅผ `export`ํ๋ฉด ์๋์ ๊ฐ์ด ๋ฐ์ดํฐ๋ฅผ `import` ํ์ฌ ์ฌ์ฉํ ์ ์๋ค.
+```javascript
+import { users, courses } from './data.js'
+
+function App() {
+ this.users = users
+ this.courses = courses
+}
+```
+
+## ์ต๋จ ๊ฒฝ๋ก ๋ผ์ด๋ธ๋ฌ๋ฆฌ
+- `utils/Dijkstra.js` ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ์ฉํ๋ฉด ๊ฐํธํ๊ฒ ์ต๋จ๊ฑฐ๋ฆฌ๋ฅผ ์กฐํํ ์ ์๋ค.
+- ์ ์ (Vertex)๊ณผ ๊ฐ์ (Edge), ๊ทธ๋ฆฌ๊ณ ๊ฐ์ค์น ๊ฐ๋
์ ์ด์ฉ
+ - ์ ์ : ์งํ์ฒ ์ญ
+ - ๊ฐ์ : ์งํ์ฒ ์ญ ์ฐ๊ฒฐ์ ๋ณด
+ - ๊ฐ์ค์น: ๊ฑฐ๋ฆฌ or ์์ ์๊ฐ
+- ์ต๋จ ๊ฑฐ๋ฆฌ ๊ธฐ์ค ์กฐํ ์ ๊ฐ์ค์น๋ฅผ ๊ฑฐ๋ฆฌ๋ก ์ค์
+- ์ต์ ์๊ฐ ๊ธฐ์ค ์กฐํ ์ ๊ฐ์ค์น๋ฅผ ์๊ฐ์ผ๋ก ์ค์
+
+```javascript
+import Dijkstra from "./utils/Dijkstra.js";
+const dijkstra = new Dijkstra()
+
+//dijkstra.addEdge("์ถ๋ฐ์ญ", "๋์ฐฉ์ญ", ๊ฑฐ๋ฆฌ ๋๋ ์๊ฐ);
+dijkstra.addEdge("V1", "V2", 2);
+dijkstra.addEdge("V2", "V3", 2);
+dijkstra.addEdge("V1", "V3", 100);
+
+const result = dijkstra.findShortestPath("V1", "V3");
+// result = ["V1", "V2", "V3"]
+```
+
+#### ํ
์คํธ์ค๋ช
+
+
+- ์ญ ์ฌ์ด์ ๊ฑฐ๋ฆฌ๋ฅผ ๊ณ ๋ คํ์ง ์๋ ๊ฒฝ์ฐ V1->V3 ๊ฒฝ๋ก๊ฐ ์ต๋จ ๊ฒฝ๋ก
+- ์ญ ์ฌ์ด์ ๊ฑฐ๋ฆฌ๋ฅผ ๊ณ ๋ คํ ๊ฒฝ์ฐ V1->V3 ๊ฒฝ๋ก์ ๊ฑฐ๋ฆฌ๋ 100km, V1->V2->V3 ๊ฒฝ๋ก์ ๊ฑฐ๋ฆฌ๋ 4km์ด๋ฏ๋ก ์ต๋จ ๊ฒฝ๋ก๋ V1->V2->V3
+
+
+
+### ์๊ตฌ์ฌํญ
+- ์ฌ์ฉ์๊ฐ ์๋ชป๋ ์
๋ ฅ ๊ฐ์ ์์ฑํ ๊ฒฝ์ฐ `alert`์ ์ด์ฉํด ๋ฉ์์ง๋ฅผ ๋ณด์ฌ์ฃผ๊ณ , ์ฌ์
๋ ฅํ ์ ์๊ฒ ํ๋ค.
+- ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ(jQuery, Lodash ๋ฑ)๋ฅผ ์ฌ์ฉํ์ง ์๊ณ , ์์ Vanilla JS๋ก๋ง ๊ตฌํํ๋ค.
+- **์๋ฐ์คํฌ๋ฆฝํธ ์ฝ๋ ์ปจ๋ฒค์
์ ์งํค๋ฉด์ ํ๋ก๊ทธ๋๋ฐ** ํ๋ค
+ - [https://google.github.io/styleguide/jsguide.html](https://google.github.io/styleguide/jsguide.html)
+ - [https://ui.toast.com/fe-guide/ko_CODING-CONVENSION/](https://ui.toast.com/fe-guide/ko_CODING-CONVENTION)
+- **indent(์ธ๋ดํธ, ๋ค์ฌ์ฐ๊ธฐ) depth๋ฅผ 3์ด ๋์ง ์๋๋ก ๊ตฌํํ๋ค. 2๊น์ง๋ง ํ์ฉ**ํ๋ค.
+ - ์๋ฅผ ๋ค์ด while๋ฌธ ์์ if๋ฌธ์ด ์์ผ๋ฉด ๋ค์ฌ์ฐ๊ธฐ๋ 2์ด๋ค.
+ - ํํธ: indent(์ธ๋ดํธ, ๋ค์ฌ์ฐ๊ธฐ) depth๋ฅผ ์ค์ด๋ ์ข์ ๋ฐฉ๋ฒ์ ํจ์(๋๋ ๋ฉ์๋)๋ฅผ ๋ถ๋ฆฌํ๋ฉด ๋๋ค.
+- **ํจ์(๋๋ ๋ฉ์๋)์ ๊ธธ์ด๊ฐ 15๋ผ์ธ์ ๋์ด๊ฐ์ง ์๋๋ก ๊ตฌํํ๋ค.**
+ - ํจ์(๋๋ ๋ฉ์๋)๊ฐ ํ ๊ฐ์ง ์ผ๋ง ์ ํ๋๋ก ๊ตฌํํ๋ค.
+- ๋ณ์ ์ ์ธ์ [var](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/var) ๋ฅผ ์ฌ์ฉํ์ง ์๋๋ค. [const](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/const) ์ [let](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/let) ์ ์ฌ์ฉํ๋ค.
+- [import](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/import) ๋ฌธ์ ์ด์ฉํด ์คํฌ๋ฆฝํธ๋ฅผ ๋ชจ๋ํํ๊ณ ๋ถ๋ฌ์ฌ ์ ์๊ฒ ๋ง๋ ๋ค.
+- [template literal](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Template_literals)์ ์ด์ฉํด ๋ฐ์ดํฐ์ html string์ ๊ฐ๋
์ฑ ์ข๊ฒ ํํํ๋ค.
+
+
+
+## ๐ ๋ฏธ์
์ ์ฅ์ ๋ฐ ์งํ ์๊ตฌ์ฌํญ
+
+- ๋ฏธ์
์ [https://github.com/woowacourse/javascript-subway-path-precourse](https://github.com/woowacourse/javascript-subway-path-precourse) ์ ์ฅ์๋ฅผ fork/cloneํด ์์ํ๋ค.
+- **๊ธฐ๋ฅ์ ๊ตฌํํ๊ธฐ ์ ์ javascript-subway-path-precourse/docs/README.md ํ์ผ์ ๊ตฌํํ ๊ธฐ๋ฅ ๋ชฉ๋ก**์ ์ ๋ฆฌํด ์ถ๊ฐํ๋ค.
+- **git์ commit ๋จ์๋ ์ ๋จ๊ณ์์ README.md ํ์ผ์ ์ ๋ฆฌํ ๊ธฐ๋ฅ ๋ชฉ๋ก ๋จ์๋ก ์ถ๊ฐ**ํ๋ค.
+- [ํ๋ฆฌ์ฝ์ค ๊ณผ์ ์ ์ถ](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) ๋ฌธ์ ์ ์ฐจ๋ฅผ ๋ฐ๋ผ ๋ฏธ์
์ ์ ์ถํ๋ค.
diff --git a/images/dijkstra_example.png b/images/dijkstra_example.png
new file mode 100644
index 0000000..7c75197
Binary files /dev/null and b/images/dijkstra_example.png differ
diff --git a/images/path_result.gif b/images/path_result.gif
new file mode 100644
index 0000000..ee394bd
Binary files /dev/null and b/images/path_result.gif differ
diff --git a/images/path_result.jpg b/images/path_result.jpg
new file mode 100644
index 0000000..40a4bed
Binary files /dev/null and b/images/path_result.jpg differ
diff --git a/index.css b/index.css
new file mode 100644
index 0000000..77314c6
--- /dev/null
+++ b/index.css
@@ -0,0 +1,18 @@
+#input-search-station-container{
+ margin-bottom: 1rem;
+}
+
+.station-input-form-container >input {
+ height: 1.2rem;
+ margin-bottom: 1rem;
+}
+
+.path-selectors {
+ margin-bottom: 1rem;
+}
+
+#path-result-title {
+ font-size: 1.1rem;
+ font-weight: bold;
+ margin-bottom: 1rem;
+}
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..cf5b93e
--- /dev/null
+++ b/index.html
@@ -0,0 +1,19 @@
+
+
+
+
+ ์งํ์ฒ ๊ธธ์ฐพ๊ธฐ
+
+
+
+
+
+ ๐ ์งํ์ฒ ๊ธธ์ฐพ๊ธฐ
+
+
+
+
+
+
+
+
diff --git a/src/controllers/MainController.js b/src/controllers/MainController.js
new file mode 100644
index 0000000..cecdc86
--- /dev/null
+++ b/src/controllers/MainController.js
@@ -0,0 +1,109 @@
+import SearchPathInputFormView from '../views/SearchPathInputFormView.js';
+import ResultSearchPathView from '../views/SearchPathResultView.js';
+
+import StationModel from '../models/StationModel.js';
+import StationDistanceModel from '../models/StationDistanceModel.js';
+import StationTimeModel from '../models/StationTimeModel.js';
+
+import { Lines2, Lines3, LineSinbundang } from '../utils/data.js';
+import { SEARCH_PATH_TYPE, ERROR_MESSAGE } from '../utils/constants.js';
+import stationInputValidator from '../utils/stationInputValidator.js';
+
+export default class MainController {
+ init() {
+ this.initStation();
+
+ this.searchPathInputForm = new SearchPathInputFormView()
+ .setup(document.querySelector('#input-search-station-container'))
+ .on('submitSearchInputValue', (e) => this.onSubmitSearchInputHandler(e.detail))
+ .on('onChangeSelector', (e) => this.onChangeSelectorHandler(e.detail));
+
+ this.resultSearchPath = new ResultSearchPathView()
+ .setup(document.querySelector('#path-search-result-container'))
+ .hide();
+
+ this.selectPathType = SEARCH_PATH_TYPE.MIN_DISTANCE;
+ this.stationTimeModel = new StationTimeModel();
+ this.stationDistanceModel = new StationDistanceModel();
+ }
+
+ initStation() {
+ this.stationmodel = new StationModel();
+
+ this.createStation(Lines2);
+ this.createStation(Lines3);
+ this.createStation(LineSinbundang);
+ }
+
+ createStation(Line) {
+ Line.forEach((station) => {
+ if (!this.stationmodel.getStationByName(station.stationName)) {
+ this.stationmodel.createStation(station.id, station.stationName, station.line);
+ } else {
+ this.stationmodel.addLIne(station.stationName, station.line);
+ }
+ });
+ }
+
+ findPath(departureStationName, arrivalStationName) {
+ this.selectPathType === SEARCH_PATH_TYPE.MIN_DISTANCE
+ ? this.getShortestDistancePath(departureStationName, arrivalStationName)
+ : this.getShortestTimePath(departureStationName, arrivalStationName);
+ }
+
+ getShortestDistancePath(departureStationName, arrivalStationName) {
+ const shortestDistancePath = this.stationDistanceModel.getShortestDistancePath(
+ departureStationName,
+ arrivalStationName
+ );
+ const totalShortestDistance = this.stationDistanceModel.getTotalDistance(shortestDistancePath);
+ const totalTime = this.stationTimeModel.getTotalTime(shortestDistancePath);
+ this.renderResultView(shortestDistancePath, totalShortestDistance, totalTime);
+ }
+
+ getShortestTimePath(departureStationName, arrivalStationName) {
+ const shortestTimePath = this.stationTimeModel.getShortestTimePath(
+ departureStationName,
+ arrivalStationName
+ );
+ console.log(this.stationTimeModel.getTotalTime(shortestTimePath));
+ const totalShortestTime = this.stationTimeModel.getTotalTime(shortestTimePath);
+ const totalDistance = this.stationDistanceModel.getTotalDistance(shortestTimePath);
+ this.renderResultView(shortestTimePath, totalDistance, totalShortestTime);
+ }
+
+ renderResultView(path, totalDistance, totalTime) {
+ this.resultSearchPath.renderSearchPathResult(
+ path,
+ totalDistance,
+ totalTime,
+ this.selectPathType
+ );
+ this.resultSearchPath.show();
+ }
+
+ isValidStation(departureStationName, arrivalStationName) {
+ this.departureStationName = departureStationName.trim();
+ this.arrivalStationName = arrivalStationName.trim();
+
+ stationInputValidator(
+ this.departureStationName,
+ this.arrivalStationName,
+ this.stationmodel.getStationNames()
+ )
+ ? this.findPath(departureStationName, arrivalStationName)
+ : this.searchPathInputForm.resetInputForm();
+ }
+
+ onSubmitSearchInputHandler({ departureStationName, arrivalStationName }) {
+ this.isValidStation(departureStationName, arrivalStationName);
+ }
+
+ onChangeSelectorHandler(targetId) {
+ if (!this.departureStationName || !this.arrivalStationName) {
+ return alert(ERROR_MESSAGE.IS_NOT_INPUT);
+ }
+ this.selectPathType = targetId;
+ this.findPath(this.departureStationName, this.arrivalStationName);
+ }
+}
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 0000000..38001d7
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,5 @@
+import MainController from './controllers/MainController.js';
+
+document.addEventListener('DOMContentLoaded', () => {
+ new MainController().init();
+});
diff --git a/src/models/Station.js b/src/models/Station.js
new file mode 100644
index 0000000..777695c
--- /dev/null
+++ b/src/models/Station.js
@@ -0,0 +1,11 @@
+export default class Station {
+ constructor(id, name) {
+ this.id = id;
+ this.name = name;
+ this.lineNumber = [];
+ }
+
+ setLine(lineNumber) {
+ this.lineNumber.push(lineNumber);
+ }
+}
diff --git a/src/models/StationDistanceModel.js b/src/models/StationDistanceModel.js
new file mode 100644
index 0000000..4a23b80
--- /dev/null
+++ b/src/models/StationDistanceModel.js
@@ -0,0 +1,40 @@
+import { stationDistance } from '../utils/data.js';
+import Dijkstra from '../utils/Dijkstra.js';
+
+export default class StationDistanceModel {
+ constructor() {
+ this.distances = stationDistance;
+ this.dijkstra = new Dijkstra();
+ }
+
+ setDistance() {
+ this.distances.forEach((station) => {
+ this.dijkstra.addEdge(station.departureStation, station.endStation, station.distance);
+ });
+ }
+
+ getShortestDistancePath(departureStation, endStation) {
+ this.setDistance();
+ // todo ์ฐ๊ฒฐ๋์ง ์์ ๊ณณ์ผ ๋ ์์ธ์ฒ๋ฆฌ
+ return this.dijkstra.findShortestPath(departureStation, endStation);
+ }
+
+ getTotalDistance(path) {
+ return path.reduce((totalDistance, station, index) => {
+ if (index !== path.length - 1) {
+ totalDistance += this.getOneDistance(station, path[index + 1]);
+ }
+ return totalDistance;
+ }, 0);
+ }
+
+ getOneDistance(departureStation, endStation) {
+ return parseInt(
+ this.distances.find(
+ (time) =>
+ (time.departureStation === departureStation && time.endStation === endStation) ||
+ (time.departureStation === endStation && time.endStation === departureStation)
+ ).distance
+ );
+ }
+}
diff --git a/src/models/StationModel.js b/src/models/StationModel.js
new file mode 100644
index 0000000..da6ec4f
--- /dev/null
+++ b/src/models/StationModel.js
@@ -0,0 +1,25 @@
+import Station from './Station.js';
+
+export default class StationModel {
+ constructor() {
+ this.stations = [];
+ }
+
+ createStation(id, stationName, lineNumber) {
+ const station = new Station(id, stationName);
+ station.setLine(lineNumber);
+ this.stations.push(station);
+ }
+
+ getStationByName(stationName) {
+ return this.stations.find((station) => station.name === stationName);
+ }
+
+ getStationNames() {
+ return this.stations.map((station) => station.name);
+ }
+
+ addLIne(stationName, stationLine) {
+ this.getStationByName(stationName).setLine(stationLine);
+ }
+}
diff --git a/src/models/StationTimeModel.js b/src/models/StationTimeModel.js
new file mode 100644
index 0000000..a066e73
--- /dev/null
+++ b/src/models/StationTimeModel.js
@@ -0,0 +1,39 @@
+import { stationTime } from '../utils/data.js';
+import Dijkstra from '../utils/Dijkstra.js';
+
+export default class StationTimeModel {
+ constructor() {
+ this.times = stationTime;
+ this.dijkstra = new Dijkstra();
+ }
+
+ setTime() {
+ this.times.forEach((station) => {
+ this.dijkstra.addEdge(station.departureStation, station.endStation, station.time);
+ });
+ }
+
+ getShortestTimePath(departureStation, endStation) {
+ this.setTime();
+ return this.dijkstra.findShortestPath(departureStation, endStation);
+ }
+
+ getTotalTime(path) {
+ return path.reduce((totalTime, station, index) => {
+ if (index !== path.length - 1) {
+ totalTime += this.getOneTime(station, path[index + 1]);
+ }
+ return totalTime;
+ }, 0);
+ }
+
+ getOneTime(departureStation, endStation) {
+ return parseInt(
+ this.times.find(
+ (time) =>
+ (time.departureStation === departureStation && time.endStation === endStation) ||
+ (time.departureStation === endStation && time.endStation === departureStation)
+ ).time
+ );
+ }
+}
diff --git a/src/utils/Dijkstra.js b/src/utils/Dijkstra.js
new file mode 100644
index 0000000..7c6cfd5
--- /dev/null
+++ b/src/utils/Dijkstra.js
@@ -0,0 +1,220 @@
+export default function Dijkstra() {
+ const Node = {
+ init: function (val, priority) {
+ this.val = val;
+ this.priority = priority;
+ },
+ };
+
+ const PriorityQueue = {
+ init: function () {
+ this.values = [];
+ },
+ enqueue: function (val, priority) {
+ const newNode = Object.create(Node);
+ newNode.init(val, priority);
+
+ this.values.push(newNode);
+
+ let idxOfNewNode = this.values.length - 1;
+
+ while (idxOfNewNode > 0) {
+ const idxOfParentNode = Math.floor((idxOfNewNode - 1) / 2);
+
+ const parentNode = this.values[idxOfParentNode];
+
+ if (priority < parentNode.priority) {
+ this.values[idxOfParentNode] = newNode;
+ this.values[idxOfNewNode] = parentNode;
+ idxOfNewNode = idxOfParentNode;
+ continue;
+ }
+ break;
+ }
+ return this.values;
+ },
+ dequeue: function () {
+ if (this.values.length == 0) {
+ return;
+ }
+ const dequeued = this.values.shift();
+ const lastItem = this.values.pop();
+ if (!lastItem) {
+ return dequeued;
+ }
+ this.values.unshift(lastItem);
+
+ let idxOfTarget = 0;
+
+ while (true) {
+ let idxOfLeftChild = idxOfTarget * 2 + 1;
+ let idxOfRightChild = idxOfTarget * 2 + 2;
+ let leftChild = this.values[idxOfLeftChild];
+ let rightChild = this.values[idxOfRightChild];
+
+ function swap(direction) {
+ const idxOfChild =
+ direction == "left" ? idxOfLeftChild : idxOfRightChild;
+ const child = direction == "left" ? leftChild : rightChild;
+ this.values[idxOfChild] = this.values[idxOfTarget];
+ this.values[idxOfTarget] = child;
+ idxOfTarget = idxOfChild;
+ }
+
+ if (!leftChild) {
+ return dequeued;
+ }
+
+ if (!rightChild) {
+ if (leftChild.priority < lastItem.priority) {
+ swap.call(this, "left");
+ continue;
+ }
+ return dequeued;
+ }
+
+ if (leftChild.priority == rightChild.priority) {
+ swap.call(this, "left");
+ continue;
+ }
+
+ if (
+ leftChild.priority < rightChild.priority &&
+ leftChild.priority < lastItem.priority
+ ) {
+ swap.call(this, "left");
+ continue;
+ }
+
+ if (
+ rightChild.priority < leftChild.priority &&
+ rightChild.priority < lastItem.priority
+ ) {
+ swap.call(this, "right");
+ continue;
+ }
+ }
+ },
+ };
+
+ const WeightedGraph = {
+ init: function () {
+ this.adjacencyList = {};
+ this.length = 0;
+ },
+ addVertex: function (vertex) {
+ if (!this.adjacencyList.hasOwnProperty(vertex)) {
+ this.adjacencyList[vertex] = {};
+ this.length++;
+ }
+ },
+ addEdge: function (vertex1, vertex2, weight) {
+ this.addVertex(vertex1);
+ this.addVertex(vertex2);
+ this.adjacencyList[vertex1][vertex2] = weight;
+ this.adjacencyList[vertex2][vertex1] = weight;
+ return this.adjacencyList;
+ },
+ removeEdge: function (vertex1, vertex2) {
+ if (!this.adjacencyList.hasOwnProperty(vertex1)) {
+ return `There's no ${vertex1}`;
+ }
+ if (!this.adjacencyList.hasOwnProperty(vertex2)) {
+ return `There's no ${vertex2}`;
+ }
+
+ function removeHelper(v1, v2) {
+ if (!this.adjacencyList.hasOwnProperty(v1)) {
+ return `There's no edge between ${v1} and ${v2}`;
+ }
+ delete this.adjacencyList[v1][v2];
+ if (Object.keys(this.adjacencyList[v1]).length == 0) {
+ delete this.adjacencyList[v1];
+ }
+ }
+
+ removeHelper.call(this, vertex1, vertex2);
+ removeHelper.call(this, vertex2, vertex1);
+
+ return this.adjacencyList;
+ },
+ removeVertex: function (vertex) {
+ if (!this.adjacencyList.hasOwnProperty(vertex)) {
+ return `There's no ${vertex}`;
+ }
+ const edges = this.adjacencyList[vertex];
+ for (const key in edges) {
+ this.removeEdge(key, vertex);
+ }
+ return this.adjacencyList;
+ },
+ findShortestRoute: function (start, end) {
+ if (!start || !end) {
+ throw Error("์ถ๋ฐ์ง์ ๋์ฐฉ์ง๋ฅผ ๋ชจ๋ ์
๋ ฅํด์ผ ํฉ๋๋ค.");
+ }
+ const distance = {};
+ const previous = {};
+ const pq = Object.create(PriorityQueue);
+ pq.init();
+ pq.enqueue(start, 0);
+ const visited = {};
+
+ const hashOfVertex = this.adjacencyList;
+ for (const vertexName in hashOfVertex) {
+ const priority = vertexName == start ? 0 : Infinity;
+ distance[vertexName] = priority;
+ previous[vertexName] = null;
+ }
+
+ while (true) {
+ let current = pq.dequeue();
+ if (!current?.val) {
+ return;
+ }
+ current = current.val;
+ if (current == end) {
+ break;
+ }
+ const neighbors = hashOfVertex[current];
+
+ for (const vertexName in neighbors) {
+ if (visited.hasOwnProperty(vertexName)) {
+ continue;
+ }
+ const distFromStart = distance[current] + neighbors[vertexName];
+
+ if (distFromStart < distance[vertexName]) {
+ pq.enqueue(vertexName, distFromStart);
+ distance[vertexName] = distFromStart;
+ previous[vertexName] = current;
+ }
+ }
+ visited[current] = true;
+ }
+
+ let node = end;
+
+ const route = [];
+ while (node) {
+ route.unshift(node);
+ node = previous[node];
+ }
+
+ return route;
+ },
+ };
+
+ this.addEdge = (source, target, weight) => {
+ WeightedGraph.addEdge(source, target, weight);
+ };
+
+ this.findShortestPath = (source, target) => {
+ return WeightedGraph.findShortestRoute(source, target);
+ };
+
+ this.addVertex = (vertex) => {
+ WeightedGraph.addVertex(vertex);
+ };
+
+ WeightedGraph.init();
+}
diff --git a/src/utils/constants.js b/src/utils/constants.js
new file mode 100644
index 0000000..61a2ba4
--- /dev/null
+++ b/src/utils/constants.js
@@ -0,0 +1,27 @@
+const MIN_LENGTH_OF_NAME = 2;
+
+const LINE_NUMBER = {
+ LINE2: 2,
+ LINE3: 3,
+ LINE_SINBUNDANG: 4,
+};
+
+const ERROR_MESSAGE = {
+ IS_EMPTY_STRING: '์ญ์ ์
๋ ฅํด์ฃผ์ธ์',
+ IS_SAME_STATION: '์๋ก ๋ค๋ฅธ ์ญ์ ์
๋ ฅํด์ฃผ์ธ์',
+ IS_TOO_SHORT_STRING: '์ญ์ด๋ฆ์ ๋ ๊ธ์ ์ด์์
๋๋ค',
+ IS_NOT_EXIST: '์กด์ฌํ์ง ์๋ ์ญ์
๋๋ค',
+ IS_NOT_INPUT: '์ญ์ ๋จผ์ ์
๋ ฅํด์ฃผ์ธ์',
+};
+
+const SEARCH_PATH_TYPE = {
+ MIN_TIME: '์ต์์๊ฐ',
+ MIN_DISTANCE: '์ต๋จ๊ฑฐ๋ฆฌ',
+};
+
+const UNIT = {
+ DISTANCE: 'km',
+ TIME: '๋ถ',
+};
+
+export { MIN_LENGTH_OF_NAME, ERROR_MESSAGE, LINE_NUMBER, SEARCH_PATH_TYPE, UNIT };
diff --git a/src/utils/data.js b/src/utils/data.js
new file mode 100644
index 0000000..2fa7783
--- /dev/null
+++ b/src/utils/data.js
@@ -0,0 +1,136 @@
+import { LINE_NUMBER } from './constants.js';
+
+export const Lines2 = [
+ {
+ id: 1,
+ stationName: '๊ต๋',
+ line: LINE_NUMBER.LINE2,
+ },
+ {
+ id: 2,
+ stationName: '๊ฐ๋จ',
+ line: LINE_NUMBER.LINE2,
+ },
+ {
+ id: 3,
+ stationName: '์ญ์ผ',
+ line: LINE_NUMBER.LINE2,
+ },
+];
+
+export const Lines3 = [
+ {
+ id: 4,
+ stationName: '๊ต๋',
+ line: LINE_NUMBER.LINE3,
+ },
+ {
+ id: 5,
+ stationName: '๋จ๋ถํฐ๋ฏธ๋',
+ line: LINE_NUMBER.LINE3,
+ },
+ {
+ id: 6,
+ stationName: '์์ฌ',
+ line: LINE_NUMBER.LINE3,
+ },
+ {
+ id: 7,
+ stationName: '๋งค๋ด',
+ line: LINE_NUMBER.LINE3,
+ },
+];
+
+export const LineSinbundang = [
+ {
+ id: 8,
+ stationName: '๊ฐ๋จ',
+ line: LINE_NUMBER.LINE_SINBUNDANG,
+ },
+ {
+ id: 9,
+ stationName: '์์ฌ',
+ line: LINE_NUMBER.LINE_SINBUNDANG,
+ },
+ {
+ id: 10,
+ stationName: '์์ฌ์๋ฏผ์์ฒ',
+ line: LINE_NUMBER.LINE_SINBUNDANG,
+ },
+];
+
+export const stationTime = [
+ {
+ departureStation: '๊ต๋',
+ endStation: '๊ฐ๋จ',
+ time: 3,
+ },
+ {
+ departureStation: '๊ฐ๋จ',
+ endStation: '์ญ์ผ',
+ time: 3,
+ },
+ {
+ departureStation: '๊ต๋',
+ endStation: '๋จ๋ถํฐ๋ฏธ๋',
+ time: 2,
+ },
+ {
+ departureStation: '๋จ๋ถํฐ๋ฏธ๋',
+ endStation: '์์ฌ',
+ time: 5,
+ },
+ {
+ departureStation: '์์ฌ',
+ endStation: '๋งค๋ด',
+ time: 1,
+ },
+ {
+ departureStation: '๊ฐ๋จ',
+ endStation: '์์ฌ',
+ time: 8,
+ },
+ {
+ departureStation: '์์ฌ',
+ endStation: '์์ฌ์๋ฏผ์์ฒ',
+ time: 3,
+ },
+];
+
+export const stationDistance = [
+ {
+ departureStation: '๊ต๋',
+ endStation: '๊ฐ๋จ',
+ distance: 2,
+ },
+ {
+ departureStation: '๊ฐ๋จ',
+ endStation: '์ญ์ผ',
+ distance: 2,
+ },
+ {
+ departureStation: '๊ต๋',
+ endStation: '๋จ๋ถํฐ๋ฏธ๋',
+ distance: 3,
+ },
+ {
+ departureStation: '๋จ๋ถํฐ๋ฏธ๋',
+ endStation: '์์ฌ',
+ distance: 6,
+ },
+ {
+ departureStation: '์์ฌ',
+ endStation: '๋งค๋ด',
+ distance: 1,
+ },
+ {
+ departureStation: '๊ฐ๋จ',
+ endStation: '์์ฌ',
+ distance: 2,
+ },
+ {
+ departureStation: '์์ฌ',
+ endStation: '์์ฌ์๋ฏผ์์ฒ',
+ distance: 10,
+ },
+];
diff --git a/src/utils/stationInputValidator.js b/src/utils/stationInputValidator.js
new file mode 100644
index 0000000..3ea04fe
--- /dev/null
+++ b/src/utils/stationInputValidator.js
@@ -0,0 +1,36 @@
+import { ERROR_MESSAGE } from './constants.js';
+
+const isNotNull = (departureStationName, arrivalStationName) => {
+ return departureStationName === '' || arrivalStationName === ''
+ ? alert(ERROR_MESSAGE.IS_EMPTY_STRING)
+ : true;
+};
+
+const isMoreThanOneString = (departureStationName, arrivalStationName) => {
+ return departureStationName.length > 1 && arrivalStationName.length > 1
+ ? true
+ : alert(ERROR_MESSAGE.IS_TOO_SHORT_STRING);
+};
+
+const isNotSameStation = (departureStationName, arrivalStationName) => {
+ return departureStationName !== arrivalStationName ? true : alert(ERROR_MESSAGE.IS_SAME_STATION);
+};
+
+const isExistStation = (departureStationName, arrivalStationName, stationNames) => {
+ return stationNames.includes(departureStationName) && stationNames.includes(arrivalStationName)
+ ? true
+ : alert(ERROR_MESSAGE.IS_NOT_EXIST);
+};
+
+export default function stationInputValidator(
+ departureStationName,
+ arrivalStationName,
+ stationNames
+) {
+ return (
+ isNotNull(departureStationName, arrivalStationName) &&
+ isMoreThanOneString(departureStationName, arrivalStationName) &&
+ isNotSameStation(departureStationName, arrivalStationName) &&
+ isExistStation(departureStationName, arrivalStationName, stationNames)
+ );
+}
diff --git a/src/views/SearchPathInputFormView.js b/src/views/SearchPathInputFormView.js
new file mode 100644
index 0000000..80e5461
--- /dev/null
+++ b/src/views/SearchPathInputFormView.js
@@ -0,0 +1,76 @@
+import View from './View.js';
+import { SEARCH_PATH_TYPE } from '../utils/constants.js';
+
+export default class SearchPathInputFormView extends View {
+ setup($element) {
+ this.init($element);
+ this.renderSearchInputForm();
+ return this;
+ }
+
+ renderSearchInputForm() {
+ this.$element.innerHTML =
+ this.getInputStationFormHTML() + this.getSelectSearchTypeBtnHTML() + this.getSubmitBtnHTML();
+
+ this.$departureStationInput = this.$element.querySelector('#departure-station-name-input');
+ this.$ArrivalStationInput = this.$element.querySelector('#arrival-station-name-input');
+ this.$pathSelectorContainer = this.$element.querySelector('.path-selectors');
+
+ this.bindInputEvent();
+ this.bindClickEvent();
+ return this;
+ }
+
+ getInputStationFormHTML() {
+ return `
+
+
+
+
+
+
`;
+ }
+
+ getSelectSearchTypeBtnHTML() {
+ return `
+
+
+
`;
+ }
+
+ getSubmitBtnHTML() {
+ return ``;
+ }
+
+ resetInputForm() {
+ this.$departureStationInput.value = '';
+ this.$ArrivalStationInput.value = '';
+ }
+
+ bindInputEvent() {
+ this.$element
+ .querySelector('#search-button')
+ .addEventListener('click', () => this.onSubmitSearchPathHandler());
+ }
+
+ onSubmitSearchPathHandler() {
+ const searchInputValue = {
+ departureStationName: this.$departureStationInput.value,
+ arrivalStationName: this.$ArrivalStationInput.value,
+ };
+
+ this.emit('submitSearchInputValue', searchInputValue);
+ }
+
+ bindClickEvent() {
+ this.$pathSelectorContainer.addEventListener('click', (e) =>
+ this.onChangeSearchPathHandler(e.target.id)
+ );
+ }
+
+ onChangeSearchPathHandler(targetId) {
+ if (targetId === SEARCH_PATH_TYPE.MIN_DISTANCE || targetId === SEARCH_PATH_TYPE.MIN_TIME) {
+ this.emit('onChangeSelector', targetId);
+ }
+ }
+}
diff --git a/src/views/SearchPathResultView.js b/src/views/SearchPathResultView.js
new file mode 100644
index 0000000..220bd6f
--- /dev/null
+++ b/src/views/SearchPathResultView.js
@@ -0,0 +1,46 @@
+import View from './View.js';
+import { UNIT } from '../utils/constants.js';
+
+export default class SearchPathInputFormView extends View {
+ setup($element) {
+ this.init($element);
+ return this;
+ }
+
+ renderSearchPathResult(path, totalDistance, totalTime, searchType) {
+ this.$element.innerHTML = this.getSearchPathResultHTML(
+ path,
+ totalDistance,
+ totalTime,
+ searchType
+ );
+ return this;
+ }
+
+ getSearchPathResultHTML(path, totalDistance, totalTime, searchType) {
+ return `๐ ๊ฒฐ๊ณผ
+
+ ${searchType}
+
+
+ | ์ด ๊ฑฐ๋ฆฌ |
+ ์ด ์์์๊ฐ |
+
+
+
+
+ | ${totalDistance}${UNIT.DISTANCE} |
+ ${totalTime}${UNIT.TIME} |
+
+
+ | ${this.getPathHTML(path)} |
+
+
+
+ `;
+ }
+
+ getPathHTML(path) {
+ return `${path.join('๐๐ป')}`;
+ }
+}
diff --git a/src/views/View.js b/src/views/View.js
new file mode 100644
index 0000000..99f7c03
--- /dev/null
+++ b/src/views/View.js
@@ -0,0 +1,28 @@
+export default class View {
+ init($element) {
+ if (!$element) throw $element;
+ this.$element = $element;
+ return this;
+ }
+
+ on(event, eventHandler) {
+ this.$element.addEventListener(event, eventHandler);
+ return this;
+ }
+
+ emit(event, data) {
+ const newEvent = new CustomEvent(event, { detail: data });
+ this.$element.dispatchEvent(newEvent);
+ return this;
+ }
+
+ hide() {
+ this.$element.style.display = 'none';
+ return this;
+ }
+
+ show() {
+ this.$element.style.display = '';
+ return this;
+ }
+}