diff --git a/README.md b/README.md index 51abd25..d74c447 100644 --- a/README.md +++ b/README.md @@ -1 +1,74 @@ -# javascript-subway-final \ No newline at end of file +# ๐Ÿš‡ ์ง€ํ•˜์ฒ  ๋…ธ์„ ๋„ ๊ฒฝ๋กœ ์กฐํšŒ ๋ฏธ์…˜ +- ๋“ฑ๋ก๋œ ์ง€ํ•˜์ฒ  ๋…ธ์„ ๋„์—์„œ ๊ฒฝ๋กœ๋ฅผ ์กฐํšŒํ•˜๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•œ๋‹ค. + +## ๐Ÿš€ ๊ธฐ๋Šฅ ์š”๊ตฌ์‚ฌํ•ญ +> ํ”„๋ฆฌ์ฝ”์Šค 3์ฃผ์ฐจ ๋ฏธ์…˜์—์„œ ์‚ฌ์šฉํ•œ ์ฝ”๋“œ๋ฅผ ์ฐธ๊ณ ํ•ด๋„ ๋ฌด๊ด€ํ•˜๋‹ค. +### UI +- [x] index.html ์™„์„ฑ +- [x] '๊ธธ์ฐพ๊ธฐ'๋ฒ„ํŠผ ๋ˆŒ๋ €์„ ๋•Œ ๊ฒฐ๊ณผ์ฐฝ visible +- [x] ์ตœ๋‹จ๊ฑฐ๋ฆฌ, ์ตœ์†Œ์‹œ๊ฐ„ ๋ผ๋””์˜ค๋ฒ„ํŠผ +- [x] ๊ฒฐ๊ณผ์ฐฝ ๊ตฌ์„ฑ + +### ๊ธฐ๋Šฅ +- [x] ์ตœ๋‹จ๊ฑฐ๋ฆฌ ๊ตฌํ•˜๊ธฐ +- [x] ์ตœ์†Œ์‹œ๊ฐ„ ๊ตฌํ•˜๊ธฐ + - [x] ๋ฐ์ดํ„ฐ๋ฅผ import๋กœ ๋ถˆ๋Ÿฌ์™€์„œ ์ „๋ถ€ edge์— ์ถ”๊ฐ€ํ•œ๋‹ค. + - [x] ๋‹ค์ต์ŠคํŠธ๋ผ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ด์šฉํ•ด **type์— ๋”ฐ๋ผ** ์ตœ๋‹จ๊ฑฐ๋ฆฌ๋ฅผ ๋ฆฌํ„ด +- [x] ํ”„๋กœ๊ทธ๋žจ ์‹œ์ž‘ ์‹œ App๊ฐ์ฒด ์ƒ์„ฑ + - [x] ์ƒ์„ฑํ•  ๋•Œ data.js ๋ฅผ ๊ฐ์ฒด์˜ ์†์„ฑ์œผ๋กœ ์ง€์ • + - [x] '๊ธธ์ฐพ๊ธฐ' ๋ฒ„ํŠผ ๋ˆ„๋ฅผ ๋•Œ๋งˆ๋‹ค ๊ฒฝ๋กœ๋ฅผ ๊ฐ์ฒด์˜ ์†์„ฑ์œผ๋กœ ์ง€์ • + +------------ + +### ๊ฒฝ๋กœ ์กฐํšŒ ๊ธฐ๋Šฅ + + +- [x]]์ถœ๋ฐœ์—ญ๊ณผ ๋„์ฐฉ์—ญ์„ ์ž…๋ ฅ๋ฐ›์•„ ๊ฒฝ๋กœ๋ฅผ ์กฐํšŒํ•œ๋‹ค. +- [x]๊ฒฝ๋กœ ์กฐํšŒ ์‹œ ์ด ๊ฑฐ๋ฆฌ, ์ด ์†Œ์š” ์‹œ๊ฐ„์„ ํ•จ๊ป˜ ์ถœ๋ ฅํ•œ๋‹ค. +- [x]๊ฒฝ๋กœ ์กฐํšŒ ์‹œ `์ตœ๋‹จ ๊ฑฐ๋ฆฌ` ๋˜๋Š” `์ตœ์†Œ ์‹œ๊ฐ„` ์˜ต์…˜์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋‹ค. + +### ์˜ˆ์™ธ ์ฒ˜๋ฆฌ +- [x]์ถœ๋ฐœ์—ญ๊ณผ ๋„์ฐฉ์—ญ์€ 2๊ธ€์ž ์ด์ƒ์ด์–ด์•ผ ํ•œ๋‹ค. - '์ด๋ฆ„๊ด€๋ จ' +- [x]์กด์žฌํ•˜์ง€ ์•Š๋Š” ์—ญ์„ ์ถœ๋ฐœ์—ญ ๋˜๋Š” ๋„์ฐฉ์—ญ์œผ๋กœ ์ž…๋ ฅํ•  ์ˆ˜ ์—†๋‹ค. - '์ด๋ฆ„๊ด€๋ จ' +- [x]๊ฒฝ๋กœ ์กฐํšŒ ์‹œ ์ถœ๋ฐœ์—ญ๊ณผ ๋„์ฐฉ์—ญ์ด ๊ฐ™์„ ์ˆ˜ ์—†๋‹ค. - '๋‘ ์—ญ ๊ด€๋ จ' +- [x]๊ฒฝ๋กœ ์กฐํšŒ ์‹œ ์ถœ๋ฐœ์—ญ๊ณผ ๋„์ฐฉ์—ญ์ด ์—ฐ๊ฒฐ๋˜์ง€ ์•Š์œผ๋ฉด ๊ฒฝ๋กœ๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์—†๋‹ค. - '๋‘ ์—ญ ๊ด€๋ จ' +- [x]๊ทธ ์™ธ ์ •์ƒ์ ์œผ๋กœ ํ”„๋กœ๊ทธ๋žจ์ด ์ˆ˜ํ–‰๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ `alert`์œผ๋กœ ์—๋Ÿฌ๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค. + +### ์ดˆ๊ธฐ ์„ค์ • +- [x]ํ”„๋กœ๊ทธ๋žจ ์‹œ์ž‘ ์‹œ ์—ญ, ๋…ธ์„ , ๊ตฌ๊ฐ„ ๋ฐ์ดํ„ฐ๋ฅผ ์ดˆ๊ธฐ ์„ค์ • ํ•ด์•ผ ํ•œ๋‹ค. +- [x]๊ฑฐ๋ฆฌ์™€ ์†Œ์š” ์‹œ๊ฐ„์€ ์–‘์˜ ์ •์ˆ˜์ด๋ฉฐ ๋‹จ์œ„๋Š” km์™€ ๋ถ„์„ ์˜๋ฏธํ•œ๋‹ค. +- [x]์•„๋ž˜์˜ ์‚ฌ์ „ ๋“ฑ๋ก ์ •๋ณด๋กœ ๋ฐ˜๋“œ์‹œ ์ดˆ๊ธฐ ์„ค์ •์„ ํ•œ๋‹ค. + +``` +1. ์ง€ํ•˜์ฒ ์—ญ์œผ๋กœ ๊ต๋Œ€, ๊ฐ•๋‚จ, ์—ญ์‚ผ, ๋‚จ๋ถ€ํ„ฐ๋ฏธ๋„, ์–‘์žฌ, ์–‘์žฌ์‹œ๋ฏผ์˜์ˆฒ, ๋งค๋ด‰ ์—ญ ์ •๋ณด๊ฐ€ ๋“ฑ๋ก๋˜์–ด ์žˆ๋‹ค. +2. ์ง€ํ•˜์ฒ  ๋…ธ์„ ์œผ๋กœ 2ํ˜ธ์„ , 3ํ˜ธ์„ , ์‹ ๋ถ„๋‹น์„ ์ด ๋“ฑ๋ก๋˜์–ด ์žˆ๋‹ค. +3. ๋…ธ์„ ์— ์—ญ์ด ์•„๋ž˜์™€ ๊ฐ™์ด ๋“ฑ๋ก๋˜์–ด ์žˆ๋‹ค.(์™ผ์ชฝ ๋์ด ์ƒํ–‰ ์ข…์ ) + - 2ํ˜ธ์„ : ๊ต๋Œ€ - ( 2km / 3๋ถ„ ) - ๊ฐ•๋‚จ - ( 2km / 3๋ถ„ ) - ์—ญ์‚ผ + - 3ํ˜ธ์„ : ๊ต๋Œ€ - ( 3km / 2๋ถ„ ) - ๋‚จ๋ถ€ํ„ฐ๋ฏธ๋„ - ( 6km / 5๋ถ„ ) - ์–‘์žฌ - ( 1km / 1๋ถ„ ) - ๋งค๋ด‰ + - ์‹ ๋ถ„๋‹น์„ : ๊ฐ•๋‚จ - ( 2km / 8๋ถ„ ) - ์–‘์žฌ - ( 10km / 3๋ถ„ ) - ์–‘์žฌ์‹œ๋ฏผ์˜์ˆฒ +``` + +
+ +## โœ… ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์š”๊ตฌ์‚ฌํ•ญ +### ๊ธธ์ฐพ๊ธฐ ๊ด€๋ จ ๊ธฐ๋Šฅ +- [x]์ถœ๋ฐœ์—ญ์„ ์ž…๋ ฅํ•˜๋Š” input ํƒœ๊ทธ๋Š” `departure-station-name-input` id ์†์„ฑ๊ฐ’์„ ๊ฐ€์ง„๋‹ค. +- [x]๋„์ฐฉ์—ญ์„ ์ž…๋ ฅํ•˜๋Š” input ํƒœ๊ทธ๋Š” `arrival-station-name-input` id ์†์„ฑ๊ฐ’์„ ๊ฐ€์ง„๋‹ค. +- [x]์ตœ๋‹จ๊ฑฐ๋ฆฌ, ์ตœ์†Œ์‹œ๊ฐ„์„ ์„ ํƒํ•˜๋Š” radio๋Š” `search-type` name ์†์„ฑ๊ฐ’์„ ๊ฐ€์ง„๋‹ค. + - **radio option์˜ default ๊ฐ’์€ ์ตœ๋‹จ๊ฑฐ๋ฆฌ์ด๋‹ค.** +- [x]๊ธธ์ฐพ๊ธฐ ๋ฒ„ํŠผ์€ `search-button` id ์†์„ฑ๊ฐ’์„ ๊ฐ€์ง„๋‹ค. +- [x]๐Ÿ“ ๊ฒฐ๊ณผ๋Š” `table`์„ ์ด์šฉํ•˜์—ฌ ๋ณด์—ฌ์ค€๋‹ค. + +## โ—๏ธํžŒํŠธ +## ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™” +- [x] ์ดˆ๊ธฐํ™” ๋ฐ์ดํ„ฐ DB ๋งŒ๋“ค์–ด์„œ ์—ฐ๊ฒฐ + +## ์ตœ๋‹จ ๊ฒฝ๋กœ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ +- [x] dijkstra ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„ + +#### ํ…Œ์ŠคํŠธ์„ค๋ช… + + +- ์—ญ ์‚ฌ์ด์˜ ๊ฑฐ๋ฆฌ๋ฅผ ๊ณ ๋ คํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ V1->V3 ๊ฒฝ๋กœ๊ฐ€ ์ตœ๋‹จ ๊ฒฝ๋กœ +- ์—ญ ์‚ฌ์ด์˜ ๊ฑฐ๋ฆฌ๋ฅผ ๊ณ ๋ คํ•  ๊ฒฝ์šฐ V1->V3 ๊ฒฝ๋กœ์˜ ๊ฑฐ๋ฆฌ๋Š” 100km, V1->V2->V3 ๊ฒฝ๋กœ์˜ ๊ฑฐ๋ฆฌ๋Š” 4km์ด๋ฏ€๋กœ ์ตœ๋‹จ ๊ฒฝ๋กœ๋Š” V1->V2->V3 + 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.html b/index.html new file mode 100644 index 0000000..b8bf847 --- /dev/null +++ b/index.html @@ -0,0 +1,35 @@ + + + + + ์ง€ํ•˜์ฒ  ๊ธธ์ฐพ๊ธฐ + + +
+ +

๐Ÿš‡์ง€ํ•˜์ฒ  ๊ธธ์ฐพ๊ธฐ

+ ์ถœ๋ฐœ์—ญ

+ ๋„์ฐฉ์—ญ

+ ์ตœ๋‹จ๊ฑฐ๋ฆฌ + ์ตœ์†Œ์‹œ๊ฐ„

+ + +
+ + diff --git a/src/constants.js b/src/constants.js new file mode 100644 index 0000000..1e53feb --- /dev/null +++ b/src/constants.js @@ -0,0 +1,22 @@ +export const SEARCH = { + BUTTON: document.getElementById('search-button'), + TYPE: document.getElementsByName('search-type'), +}; + +export const INPUT = { + DEPARTURE: document.getElementById('departure-station-name-input'), + ARRIVAL: document.getElementById('arrival-station-name-input'), +}; + +export const SEARCH_TYPE = { + PATH: '์ตœ๋‹จ๊ฑฐ๋ฆฌ', + TIME: '์ตœ์†Œ์‹œ๊ฐ„', +}; + +export const RESULT = { + DISPLAY: document.getElementById('result'), + TYPE: document.getElementById('result-type'), + DISTANCE: document.getElementById('total-distance'), + TIME: document.getElementById('total-time'), + PATH: document.getElementById('result-path'), +}; diff --git a/src/createMessages.js b/src/createMessages.js new file mode 100644 index 0000000..b592eca --- /dev/null +++ b/src/createMessages.js @@ -0,0 +1,11 @@ +export function createResultPathMessage(pathArray) { + return pathArray.join('->'); +} + +export function createDistanceMessage(distance) { + return `${distance}km`; +} + +export function createTimeMessage(time) { + return `${time}๋ถ„`; +} diff --git a/src/data.js b/src/data.js new file mode 100644 index 0000000..7204bbe --- /dev/null +++ b/src/data.js @@ -0,0 +1,34 @@ +export const lines = [ + { + name: '2ํ˜ธ์„ ', + stations: [ + '๊ต๋Œ€', + {distance: 2, time: 3}, + '๊ฐ•๋‚จ', + {distance: 2, time: 3}, + '์—ญ์‚ผ', + ], + }, + { + name: '3ํ˜ธ์„ ', + stations: [ + '๊ต๋Œ€', + {distance: 3, time: 2}, + '๋‚จ๋ถ€ํ„ฐ๋ฏธ๋„', + {distance: 6, time: 5}, + '์–‘์žฌ', + {distance: 1, time: 1}, + '๋งค๋ด‰', + ], + }, + { + name: '์‹ ๋ถ„๋‹น์„ ', + stations: [ + '๊ฐ•๋‚จ', + {distance: 2, time: 8}, + '์–‘์žฌ', + {distance: 10, time: 3}, + '์–‘์žฌ์‹œ๋ฏผ์˜์ˆฒ', + ], + }, +]; diff --git a/src/display.js b/src/display.js new file mode 100644 index 0000000..f52fbcd --- /dev/null +++ b/src/display.js @@ -0,0 +1,27 @@ +import {RESULT} from './constants.js'; + +export function appendDistanceToTable(distance) { + RESULT.DISTANCE.innerText = distance; +} + +export function appendTimeToTable(time) { + RESULT.TIME.innerText = time; +} + +export function appendPathToTable(path) { + RESULT.PATH.innerText = path; +} + +export function changeTypeTitle(type) { + if (type==='distance') { + RESULT.TYPE.innerText = '์ตœ๋‹จ๊ฑฐ๋ฆฌ'; + } + if (type==='time') { + RESULT.TYPE.innerText = '์ตœ์†Œ์‹œ๊ฐ„'; + } +} + +export function display(object) { + changeTypeTitle(object.type); + RESULT.DISPLAY.style.display = 'block'; +} diff --git a/src/getMinimum.js b/src/getMinimum.js new file mode 100644 index 0000000..9cd4aba --- /dev/null +++ b/src/getMinimum.js @@ -0,0 +1,17 @@ +import Dijkstra from './utils/Dijkstra.js'; + +export function minPath(data, departure, arrival, type) { + const dijkstra = new Dijkstra(); + importDataToDijkstra(data, dijkstra, type); + + const result = dijkstra.findShortestPath(departure, arrival); + return result; +} +function importDataToDijkstra(data, dijkstra, type) { + for (let i=0; i { + display(this); + this.type = changeType(); + isValid(this); + this.path = minPath(this.data, INPUT.DEPARTURE.value, INPUT.ARRIVAL.value, this.type.value,); + if (!areStationsLinked(this.path)) { + window.alert('์—ฐ๊ฒฐ๋œ ์—ญ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”!'); + return; + } + appendDistanceToTable(createDistanceMessage(totalBetweenStations(this.data, this.path, 'distance'))); + appendTimeToTable(createTimeMessage(totalBetweenStations(this.data, this.path, 'time'))); + appendPathToTable(createResultPathMessage(this.path)); + }); +} + +function changeType() { + let type; + if (SEARCH.TYPE[0].checked == true) { + type = SEARCH.TYPE[0]; + } + if (SEARCH.TYPE[1].checked == true) { + type = SEARCH.TYPE[1]; + } + return type; +} + +function isValid(object) { + if (isNameShort(INPUT.DEPARTURE.value) || isNameShort(INPUT.ARRIVAL.value)) { + window.alert('์—ญ ์ด๋ฆ„์€ 2๊ธ€์ž ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค!'); + return; + } else if (!isStationAvaliable(INPUT.DEPARTURE.value, object.data) || !isStationAvaliable(INPUT.DEPARTURE.value, object.data)) { + window.alert('์กด์žฌํ•˜์ง€ ์•Š๋Š” ์—ญ์ž…๋‹ˆ๋‹ค!'); + return; + } else if (!areStationsDifferent(INPUT.DEPARTURE.value, INPUT.ARRIVAL.value)) { + window.alert('์„œ๋กœ ๋‹ค๋ฅธ ์—ญ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”!'); + return; + } +} + +new App(); diff --git a/src/inputValid.js b/src/inputValid.js new file mode 100644 index 0000000..13582dd --- /dev/null +++ b/src/inputValid.js @@ -0,0 +1,22 @@ +export function isNameShort(name) { + return (name.length<2); +} + +export function isStationAvaliable(name, data) { + let result = false; + for (let i=0; i 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(); +}