diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 00000000..955ca5e0
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,13 @@
+{
+ "env": {
+ "browser": true,
+ "es2021": true
+ },
+ "extends": "eslint:recommended",
+ "parserOptions": {
+ "ecmaVersion": 12,
+ "sourceType": "module"
+ },
+ "rules": {},
+ "parser": "babel-eslint"
+}
diff --git a/README.md b/README.md
index 51abd254..3190784e 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,56 @@
-# javascript-subway-final
\ No newline at end of file
+# ๐ ์งํ์ฒ ๋
ธ์ ๋ ๊ฒฝ๋ก ์กฐํ ๋ฏธ์
+
+- ๋ฑ๋ก๋ ์งํ์ฒ ๋
ธ์ ๋์์ ๊ฒฝ๋ก๋ฅผ ์กฐํํ๋ ๊ธฐ๋ฅ์ ๊ตฌํํ๋ค.
+
+## ๊ตฌํ ๊ธฐ๋ฅ ๋ชฉ๋ก
+
+### ์ด๊ธฐ ์ค์
+
+- ํ๋ก๊ทธ๋จ ์์ ์ ์ญ, ๋
ธ์ , ๊ตฌ๊ฐ ๋ฐ์ดํฐ๋ฅผ ์ด๊ธฐ ์ค์ ํด์ผ ํ๋ค.
+- ๊ฑฐ๋ฆฌ : ์์์ ์, ๋จ์ km
+- ์์์๊ฐ ์กฐ๊ฑด: ์์์ ์, ๋จ์ ๋ถ
+- ์๋์ ์ฌ์ ๋ฑ๋ก ์ ๋ณด๋ก ๋ฐ๋์ ์ด๊ธฐ ์ค์ ์ ํ๋ค.
+
+```
+1. ์งํ์ฒ ์ญ์ผ๋ก ๊ต๋, ๊ฐ๋จ, ์ญ์ผ, ๋จ๋ถํฐ๋ฏธ๋, ์์ฌ, ์์ฌ์๋ฏผ์์ฒ, ๋งค๋ด ์ญ ์ ๋ณด๊ฐ ๋ฑ๋ก๋์ด ์๋ค.
+2. ์งํ์ฒ ๋
ธ์ ์ผ๋ก 2ํธ์ , 3ํธ์ , ์ ๋ถ๋น์ ์ด ๋ฑ๋ก๋์ด ์๋ค.
+3. ๋
ธ์ ์ ์ญ์ด ์๋์ ๊ฐ์ด ๋ฑ๋ก๋์ด ์๋ค.(์ผ์ชฝ ๋์ด ์ํ ์ข
์ )
+ - 2ํธ์ : ๊ต๋ - ( 2km / 3๋ถ ) - ๊ฐ๋จ - ( 2km / 3๋ถ ) - ์ญ์ผ
+ - 3ํธ์ : ๊ต๋ - ( 3km / 2๋ถ ) - ๋จ๋ถํฐ๋ฏธ๋ - ( 6km / 5๋ถ ) - ์์ฌ - ( 1km / 1๋ถ ) - ๋งค๋ด
+ - ์ ๋ถ๋น์ : ๊ฐ๋จ - ( 2km / 8๋ถ ) - ์์ฌ - ( 10km / 3๋ถ ) - ์์ฌ์๋ฏผ์์ฒ
+```
+
+
+
+### ๊ฒฝ๋ก ์กฐํ ๊ธฐ๋ฅ
+
+- [x] ์ถ๋ฐ์ญ๊ณผ ๋์ฐฉ์ญ์ ์
๋ ฅ๋ฐ๋๋ค.
+- [x] ์ต๋จ๊ฑฐ๋ฆฌ ๋๋ ์ต์์๊ฐ ์ต์
์ ์ ํํ๋ค.
+- [x] ๊ธธ์ฐพ๊ธฐ ๋ฒํผ์ ๋๋ฌ ์คํํ๋ค.
+
+- ๊ฒฝ๋ก ์กฐํ ๊ธฐ๋ฅ
+
+ - Dijkstra.js ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ์ฉํ๋ค.
+ - ๋ชจ๋ ๊ฐ๋ฅํ ๊ฒฝ๋ก๋ฅผ ์ฐพ๋๋ค.
+ - ๊ฒฝ๋ก ๋ด ์๊ฐ / ๊ฑฐ๋ฆฌ๋ฅผ ๋ชจ๋ ๋ํ๋ค.`.addEdge(์ถ๋ฐ์ญ, ๋์ฐฉ์ญ, ๊ฑฐ๋ฆฌ)`
+ - ์ต๋จ๊ฑฐ๋ฆฌ/์ต์์๊ฐ์ ์ฐพ๋๋ค. `findShortestPath`
+
+- ์ถ๋ ฅํ๋ค: ์ ํํ ์ต์
, ์ด ๊ฑฐ๋ฆฌ, ์ด ์์์๊ฐ, ๊ฒฝ๋ก
+
+### ์
๋ ฅ๊ฐ ์์ธ ์ฒ๋ฆฌ
+
+- ์ถ๋ฐ์ญ๊ณผ ๋์ฐฉ์ญ์ 2๊ธ์ ์ด์์ด์ด์ผ ํ๋ค.
+- ์กด์ฌํ์ง ์๋ ์ญ์ ์
๋ ฅํ ์ ์๋ค.
+- ๊ฒฝ๋ก ์กฐํ ์ ์ถ๋ฐ์ญ๊ณผ ๋์ฐฉ์ญ์ ๋ค๋ฅด๊ฒ ํด์ผํ๋ค.
+- ๊ฒฝ๋ก ์กฐํ ์ ์ถ๋ฐ์ญ๊ณผ ๋์ฐฉ์ญ์ด ์ฐ๊ฒฐ๋์ง ์์ผ๋ฉด ๊ฒฝ๋ก๋ฅผ ์กฐํํ ์ ์๋ค.
+- ๊ทธ ์ธ ์ ์์ ์ผ๋ก ํ๋ก๊ทธ๋จ์ด ์ํ๋์ง ์์ ๊ฒฝ์ฐ `alert`์ผ๋ก ์๋ฌ๋ฅผ ์ถ๋ ฅํ๋ค.
+
+### HTML Element
+
+- ์ถ๋ฐ์ญ์ ์
๋ ฅํ๋ input ํ๊ทธ id: `departure-station-name-input`
+- ๋์ฐฉ์ญ์ ์
๋ ฅํ๋ input ํ๊ทธ id: `arrival-station-name-input`
+- ์ต๋จ๊ฑฐ๋ฆฌ, ์ต์์๊ฐ์ ์ ํํ๋ radio์ name ์์ฑ: `search-type`
+- radio option์ default ๊ฐ์ ์ต๋จ๊ฑฐ๋ฆฌ์ด๋ค.
+- ๊ธธ์ฐพ๊ธฐ ๋ฒํผ id: `search-button`
+- ๊ฒฐ๊ณผ๋ `table`์ ์ด์ฉํ์ฌ ๋ณด์ฌ์ค๋ค.
diff --git a/images/dijkstra_example.png b/images/dijkstra_example.png
new file mode 100644
index 00000000..7c751970
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 00000000..ee394bd1
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 00000000..40a4bedd
Binary files /dev/null and b/images/path_result.jpg differ
diff --git a/index.html b/index.html
new file mode 100644
index 00000000..0409860a
--- /dev/null
+++ b/index.html
@@ -0,0 +1,44 @@
+
+
+
+
+ ์งํ์ฒ ๊ธธ์ฐพ๊ธฐ
+
+
+
+
+
์งํ์ฒ ๊ธธ์ฐพ๊ธฐ
+
+
+
+
+
+
+
์ต๋จ๊ฑฐ๋ฆฌ
+
์ต์์๊ฐ
+
+
+
+
+
+
๊ฒฐ๊ณผ
+
+
+
+
+ | ์ด ๊ฑฐ๋ฆฌ |
+ ์ด ์์ ์๊ฐ |
+
+
+
+
+
+
+
+
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..3d0fb443
--- /dev/null
+++ b/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "javascript-subway-path-precourse",
+ "version": "1.0.0",
+ "description": "- ๋ฑ๋ก๋ ์งํ์ฒ ๋
ธ์ ๋์์ ๊ฒฝ๋ก๋ฅผ ์กฐํํ๋ ๊ธฐ๋ฅ์ ๊ตฌํํ๋ค.",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/woowacourse/javascript-subway-path-precourse.git"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "bugs": {
+ "url": "https://github.com/woowacourse/javascript-subway-path-precourse/issues"
+ },
+ "homepage": "https://github.com/woowacourse/javascript-subway-path-precourse#readme"
+}
diff --git a/src/data/line.js b/src/data/line.js
new file mode 100644
index 00000000..2165bb20
--- /dev/null
+++ b/src/data/line.js
@@ -0,0 +1,66 @@
+export const lines = [
+ {
+ name: "2ํธ์ ",
+ sections: [
+ {
+ id: 0,
+ distance: 2,
+ time: 3,
+ departureStation: "๊ต๋", //id๋ก?
+ arrivalStation: "๊ฐ๋จ",
+ },
+ {
+ id: 1,
+ distance: 2,
+ time: 3,
+ departureStation: "๊ฐ๋จ",
+ arrivalStation: "์ญ์ผ",
+ },
+ ],
+ },
+ {
+ name: "3ํธ์ ",
+ sections: [
+ {
+ id: 0,
+ distance: 3,
+ time: 2,
+ departureStation: "๊ต๋",
+ arrivalStation: "๋จ๋ถํฐ๋ฏธ๋",
+ },
+ {
+ id: 1,
+ distance: 6,
+ time: 5,
+ departureStation: "๋จ๋ถํฐ๋ฏธ๋",
+ arrivalStation: "์์ฌ",
+ },
+ {
+ id: 2,
+ distance: 1,
+ time: 1,
+ departureStation: "์์ฌ",
+ arrivalStation: "๋งค๋ด",
+ },
+ ],
+ },
+ {
+ name: "์ ๋ถ๋น์ ",
+ sections: [
+ {
+ id: 0,
+ distance: 2,
+ time: 8,
+ departureStation: "๊ฐ๋จ",
+ arrivalStation: "์์ฌ",
+ },
+ {
+ id: 1,
+ distance: 10,
+ time: 3,
+ departureStation: "์์ฌ",
+ arrivalStation: "์์ฌ์๋ฏผ์์ฒ",
+ },
+ ],
+ },
+];
diff --git a/src/data/station.js b/src/data/station.js
new file mode 100644
index 00000000..6fd036f3
--- /dev/null
+++ b/src/data/station.js
@@ -0,0 +1,30 @@
+export const stations = [
+ {
+ id: 0,
+ name: "๊ต๋",
+ },
+ {
+ id: 1,
+ name: "๊ฐ๋จ",
+ },
+ {
+ id: 2,
+ name: "์ญ์ผ",
+ },
+ {
+ id: 3,
+ name: "๋จ๋ถํฐ๋ฏธ๋",
+ },
+ {
+ id: 4,
+ name: "์์ฌ",
+ },
+ {
+ id: 5,
+ name: "์์ฌ์๋ฏผ์์ฒ",
+ },
+ {
+ id: 6,
+ name: "๋งค๋ด",
+ },
+];
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 00000000..b923f8ce
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,85 @@
+import { lines } from "./data/line.js";
+import StationService from "./service/station.service.js";
+import SectionService from "./service/section.service.js";
+import Dijkstra from "./utils/Dijkstra.js";
+const dijkstra = new Dijkstra();
+
+export default class App {
+ constructor() {
+ this.sectionService = new SectionService();
+ this.stationService = new StationService();
+
+ this.addClickEvent();
+ }
+
+ getDepartureStationInput() {
+ const depatureStationInputField = document.getElementById("departure-station-name-input");
+ return depatureStationInputField.value;
+ }
+
+ getArrivalStationInput() {
+ const arrivalStationInputField = document.getElementById("arrival-station-name-input");
+ return arrivalStationInputField.value;
+ }
+
+ getSearchTypeInput() {
+ const searchTypeInputField = document.querySelector('input[name="search-type"]:checked');
+ return searchTypeInputField.value;
+ }
+
+ validateStationNameLength(departureStationName, arrivalStationName) {
+ if (
+ departureStationName.length < this.stationService.MIN_STATION_NAME_LENGTH ||
+ arrivalStationName.length < this.stationService.MIN_STATION_NAME_LENGTH
+ ) {
+ throw new Error("์ญ ์ด๋ฆ์ ๋๊ธ์ ์ด์์ด์ด์ผ ํฉ๋๋ค.");
+ }
+ }
+
+ validateStationExist(departureStationName, arrivalStationName) {
+ const isDepartureStationExist = this.stationService.findByName(departureStationName).length;
+ const isArrivalStationExist = this.stationService.findByName(arrivalStationName).length;
+
+ if (!isDepartureStationExist || !isArrivalStationExist) {
+ throw new Error("์กด์ฌํ์ง ์๋ ์ญ์
๋๋ค.");
+ }
+ }
+
+ searchPath() {
+ try {
+ const departureStation = this.getDepartureStationInput();
+ const arrivalStation = this.getArrivalStationInput();
+ const searchType = this.getSearchTypeInput();
+ this.validateStationNameLength(departureStation, arrivalStation);
+ this.validateStationExist(departureStation, arrivalStation);
+
+ const paths = this.sectionService.findShortestPath(departureStation, arrivalStation);
+ this.renderPathTable(paths);
+ } catch (error) {
+ alert(error);
+ }
+ }
+
+ renderPathTable(paths) {
+ const pathRowHTML = `
+
+ | ${paths.length} |
+ |
+
+
+ | ${paths.join("->")} |
+
+ `;
+ const table = document.getElementById("result-table").querySelector("tbody");
+ table.innerHTML = pathRowHTML;
+ }
+
+ addClickEvent() {
+ const button = document.getElementById("search-button");
+ button.addEventListener("click", () => {
+ this.searchPath();
+ });
+ }
+}
+
+const app = new App();
diff --git a/src/service/section.service.js b/src/service/section.service.js
new file mode 100644
index 00000000..15625037
--- /dev/null
+++ b/src/service/section.service.js
@@ -0,0 +1,64 @@
+import { lines } from "../data/line.js";
+import Dijkstra from "../utils/Dijkstra.js";
+
+export default class SectionService {
+ constructor() {
+ this.lines = lines;
+ this.paths = [];
+ }
+
+ findAllSections() {
+ const sections = [];
+ for (let line of lines) {
+ sections.push(...line.sections);
+ }
+ return sections;
+ }
+
+ findPath(departureStation, arrivalStation, paths) {
+ const sections = this.findSectionsByDepartureStation(departureStation);
+
+ for (let section of sections) {
+ //base contidition
+ if (section.arrivalStation === arrivalStation) {
+ paths.push(section);
+ this.paths.push(paths);
+ return paths;
+ }
+ const newPaths = [...paths];
+ newPaths.push(section);
+ this.findPath(section.arrivalStation, arrivalStation, newPaths);
+ }
+ return this.paths;
+ }
+
+ findShortestPath(departureStation, arrivalStation) {
+ this.paths = [];
+ const dijkstra = new Dijkstra();
+ const paths = this.findPath(departureStation, arrivalStation, []);
+ console.log(paths);
+
+ paths.forEach((path) => {
+ if (paths.length === 1) {
+ dijkstra.addEdge(path.departureStation, path.arrivalStation, path.distance);
+ } else {
+ path.forEach((section) => {
+ dijkstra.addEdge(section.departureStation, section.arrivalStation, section.distance);
+ });
+ }
+ });
+
+ const result = dijkstra.findShortestPath(departureStation, arrivalStation);
+ return result;
+ }
+
+ findSectionsByDepartureStation(departureStationName) {
+ const sections = this.findAllSections();
+
+ const targets = sections.filter((section) => {
+ return section.departureStation === departureStationName;
+ });
+
+ return targets;
+ }
+}
diff --git a/src/service/station.service.js b/src/service/station.service.js
new file mode 100644
index 00000000..0340e8f7
--- /dev/null
+++ b/src/service/station.service.js
@@ -0,0 +1,16 @@
+import { stations } from "../data/station.js";
+
+export default class StationService {
+ constructor() {
+ this.stations = stations;
+ this.MIN_STATION_NAME_LENGTH = 2;
+ }
+
+ findByName(stationName) {
+ const station = this.stations.filter((station) => {
+ return station.name === stationName;
+ });
+
+ return station;
+ }
+}
diff --git a/src/utils/Dijkstra.js b/src/utils/Dijkstra.js
new file mode 100644
index 00000000..7c6cfd56
--- /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();
+}