diff --git a/README.md b/README.md index 51abd254..49a1dbe0 100644 --- a/README.md +++ b/README.md @@ -1 +1,50 @@ -# javascript-subway-final \ No newline at end of file +# ๐Ÿš‡ ์ง€ํ•˜์ฒ  ๋…ธ์„ ๋„ ๊ฒฝ๋กœ ์กฐํšŒ ๋ฏธ์…˜ +- ๋“ฑ๋ก๋œ ์ง€ํ•˜์ฒ  ๋…ธ์„ ๋„์—์„œ ์ตœ๋‹จ ๊ฑฐ๋ฆฌ์™€ ์ตœ๋‹จ ์‹œ๊ฐ„ ๊ฒฝ๋กœ๋ฅผ ์กฐํšŒํ•˜๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•œ๋‹ค. +- **ํ•ด๋‹น ํ”„๋กœ๊ทธ๋žจ์€ ๋ฐ”๋‹๋ผ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋กœ ๊ตฌํ˜„ํ•˜์˜€์Šต๋‹ˆ๋‹ค.** + +## ๐Ÿ’ป ๋น ๋ฅธ ์‹œ์ž‘ + +``` +git clone https://github.com/bwyoo1229/javascript-subway-map-precourse.git +``` + +
+ +## ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐ ์„ค์ • ์กฐ๊ฑด +``` +1. ์ง€ํ•˜์ฒ ์—ญ์œผ๋กœ ๊ต๋Œ€, ๊ฐ•๋‚จ, ์—ญ์‚ผ, ๋‚จ๋ถ€ํ„ฐ๋ฏธ๋„, ์–‘์žฌ, ์–‘์žฌ์‹œ๋ฏผ์˜์ˆฒ, ๋งค๋ด‰ ์—ญ ์ •๋ณด๊ฐ€ ๋“ฑ๋ก๋˜์–ด ์žˆ๋‹ค. +2. ์ง€ํ•˜์ฒ  ๋…ธ์„ ์œผ๋กœ 2ํ˜ธ์„ , 3ํ˜ธ์„ , ์‹ ๋ถ„๋‹น์„ ์ด ๋“ฑ๋ก๋˜์–ด ์žˆ๋‹ค. +3. ๋…ธ์„ ์— ์—ญ์ด ์•„๋ž˜์™€ ๊ฐ™์ด ๋“ฑ๋ก๋˜์–ด ์žˆ๋‹ค.(์™ผ์ชฝ ๋์ด ์ƒํ–‰ ์ข…์ ) + - 2ํ˜ธ์„ : ๊ต๋Œ€ - ( 2km / 3๋ถ„ ) - ๊ฐ•๋‚จ - ( 2km / 3๋ถ„ ) - ์—ญ์‚ผ + - 3ํ˜ธ์„ : ๊ต๋Œ€ - ( 3km / 2๋ถ„ ) - ๋‚จ๋ถ€ํ„ฐ๋ฏธ๋„ - ( 6km / 5๋ถ„ ) - ์–‘์žฌ - ( 1km / 1๋ถ„ ) - ๋งค๋ด‰ + - ์‹ ๋ถ„๋‹น์„ : ๊ฐ•๋‚จ - ( 2km / 8๋ถ„ ) - ์–‘์žฌ - ( 10km / 3๋ถ„ ) - ์–‘์žฌ์‹œ๋ฏผ์˜์ˆฒ +``` + +## ๐Ÿ’ป ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์‹คํ–‰ ๊ฒฐ๊ณผ +### ๊ฒฝ๋กœ ์กฐํšŒ + + +## ๐Ÿ–‹ ๊ตฌํ˜„ํ•  ๊ธฐ๋Šฅ ๋ชฉ๋ก +#### ์—ญ, ๋…ธ์„ , ๊ตฌ๊ฐ„ ๋ฐ์ดํ„ฐ +- [x] ์—ญ, ๋…ธ์„ , ๊ตฌ๊ฐ„ ๋ฐ์ดํ„ฐ๋ฅผ ๋งŒ๋“ค์–ด์„œ ๊ธฐ๋ณธ ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™” ํ•œ๋‹ค. + - ๊ตฌ๊ฐ„์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์–‘๋ฐฉํ–ฅ์œผ๋กœ ๊ณ ๋ คํ•ด์•ผ ํ•œ๋‹ค. +- [x] ์ดˆ๊ธฐํ™”๋œ ์—ญ, ๋…ธ์„ , ๊ตฌ๊ฐ„ ๋ฐ์ดํ„ฐ๋ฅผ ์ด์šฉํ•ด ์ตœ๋‹จ๊ฑฐ๋ฆฌ๋ฅผ ๊ตฌํ•  ๋…ธ๋“œ์™€ ๊ฐ„์„ ๋“ค์„ ์ƒ์„ฑํ•œ๋‹ค. +- [x] ์ดˆ๊ธฐํ™”๋œ ์—ญ, ๋…ธ์„ , ๊ตฌ๊ฐ„ ๋ฐ์ดํ„ฐ๋ฅผ ์ด์šฉํ•ด ์ตœ์†Œ์‹œ๊ฐ„์„ ๊ตฌํ•  ๋…ธ๋“œ์™€ ๊ฐ„์„ ๋“ค์„ ์ƒ์„ฑํ•œ๋‹ค. +#### ์ธํ’‹ +- [x] ์ถœ๋ฐœ์—ญ, ๋„์ฐฉ์—ญ์˜ ์ž…๋ ฅ์„ ๋ฐ›์•„์•ผํ•œ๋‹ค + - ์˜ˆ์™ธ์ฒ˜๋ฆฌ๋Š” `alert`๋กœ ์•Œ๋ ค์ค€๋‹ค. + - [x] ์˜ˆ์™ธ: ์ถœ๋ฐœ์—ญ๊ณผ ๋„์ฐฉ์—ญ์€ 2๊ธ€์ž ์ด์ƒ์ด๋‹ค. + - [x] ์˜ˆ์™ธ: ์ถœ๋ฐœ์—ญ๊ณผ ๋„์ฐฉ์—ญ์ด ๊ฐ™์„ ์ˆ˜ ์—†๋‹ค. + - [x] ์˜ˆ์™ธ: ๋…ธ์„ ์— ์ด๋ฏธ ๋“ฑ๋ก๋œ ์—ญ๋งŒ ์ž…๋ ฅ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค. +- [x] ์ตœ๋‹จ๊ฑฐ๋ฆฌ, ์ตœ์†Œ๊ฑฐ๋ฆฌ ๋ผ๋””์˜ค ์ž…๋ ฅ์„ ๋ฐ›์•„์•ผ ํ•œ๋‹ค. +#### ์•„์›ƒํ’‹ +- [x] ์ดˆ๊ธฐ ์ง€ํ•˜์ฒ  ๊ธธ์ฐพ๊ธฐ ํ™”๋ฉด์„ ๋งŒ๋“ค์–ด ๋ณด์—ฌ์ค˜์•ผ ํ•œ๋‹ค. +- [x] ๊ฒฐ๊ณผ ํ…Œ์ด๋ธ”์„ ๋งŒ๋“ค์–ด์„œ ๋ณด์—ฌ์ค˜์•ผ ํ•œ๋‹ค. + - [x] ์ƒˆ๋กœ์šด ๊ฐ’์ด ์ž…๋ ฅ๋ ๋•Œ ๋งˆ๋‹ค ์ƒˆ๋กœ์šด ํ…Œ์ด๋ธ”์„ ๋งŒ๋“ค์–ด์„œ ์ƒˆ๋กญ๊ฒŒ ๋ณด์—ฌ์ค˜์•ผ ํ•œ๋‹ค. +#### ์ตœ๋‹จ๊ฑฐ๋ฆฌ +- [x] ์ตœ๋‹จ ๊ฒฝ๋กœ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ด์šฉํ•ด์„œ ๋…ธ๋“œ์™€ ๊ฑฐ๋ฆฌ ์ •๋ณด๋ฅผ ๋ฐ›์€ ํ›„ ์ตœ๋‹จ ๊ฑฐ๋ฆฌ๋ฅผ ๊ตฌํ•œ๋‹ค. +- [x] ์ตœ๋‹จ ๊ฑฐ๋ฆฌ์˜ ์ด ์‹œ๊ฐ„์„ ๊ตฌํ•œ๋‹ค. +#### ์ตœ์†Œ์‹œ๊ฐ„ +- [x] ์ตœ๋‹จ ๊ฒฝ๋กœ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ด์šฉํ•ด์„œ ๋…ธ๋“œ์™€ ์‹œ๊ฐ„ ์ •๋ณด๋ฅผ ๋ฐ›์€ ํ›„ ์ตœ์†Œ ์‹œ๊ฐ„์„ ๊ตฌํ•œ๋‹ค. +- [x] ์ตœ์†Œ ์‹œ๊ฐ„์˜ ์ด ๊ฑฐ๋ฆฌ๋ฅผ ๊ตฌํ•œ๋‹ค. + 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.css b/index.css new file mode 100644 index 00000000..c5fe5b2d --- /dev/null +++ b/index.css @@ -0,0 +1,8 @@ +body { + font-family: sans-serif; +} + +table, th, td { + border: 1px solid black; + text-align: center; +} \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 00000000..a930e81f --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + ์ง€ํ•˜์ฒ  ๊ธธ์ฐพ๊ธฐ + + + +
+ + + diff --git a/src/controllers/constants.js b/src/controllers/constants.js new file mode 100644 index 00000000..149cadeb --- /dev/null +++ b/src/controllers/constants.js @@ -0,0 +1,4 @@ +export const RADIO_SHORTEST_DISTANCE_VALUE = 'shortest-distance'; +export const RADIO_SHORTEST_TIME_VALUE = 'shortest-time'; +export const SHORTEST_DISTANCE_KOR = '์ตœ๋‹จ๊ฑฐ๋ฆฌ'; +export const SHORTEST_TIME_KOR = '์ตœ์†Œ์‹œ๊ฐ„'; diff --git a/src/controllers/subway-path-controller.js b/src/controllers/subway-path-controller.js new file mode 100644 index 00000000..21fbee54 --- /dev/null +++ b/src/controllers/subway-path-controller.js @@ -0,0 +1,78 @@ +import SubwayDistanceMap from '../models/subway-distance-map-model.js'; +import SubwayTimeMap from '../models/subway-time-map-model.js'; +import SubwayPathInput from '../views/subway-path-input.js'; +import SubwayPathOutput from '../views/Subway-path-output.js'; +import {isNotValidInput} from '../services/validation.js'; +import { + RADIO_SHORTEST_DISTANCE_VALUE, + RADIO_SHORTEST_TIME_VALUE, + SHORTEST_TIME_KOR, + SHORTEST_DISTANCE_KOR + } from './constants.js'; + +export default class SubwayPathController { + constructor() { + this.subwayPathOutput = new SubwayPathOutput(); + this.subwayPathInput = new SubwayPathInput(); + this.subwayDistanceMap = new SubwayDistanceMap(); + this.subwayTimeMap = new SubwayTimeMap(); + + this.handleSearchInputs(); + } + + handleSearchInputs = () => { + this.subwayPathInput.bindSearchButton(this.getResultData); + } + + getResultData = searchInputs => { + let departureStationName; + let arrivalStationName; + let radioSelect; + + [departureStationName, arrivalStationName, radioSelect] = [...searchInputs]; + + if (isNotValidInput(departureStationName, arrivalStationName)) { + return; + } + + this.getSelectedRadioData(radioSelect, departureStationName, arrivalStationName); + } + + getSelectedRadioData = (radioSelect, departureStationName, arrivalStationName) => { + if (radioSelect == RADIO_SHORTEST_DISTANCE_VALUE) { + this.getShortestDistanceData(departureStationName, arrivalStationName) + } else if (radioSelect == RADIO_SHORTEST_TIME_VALUE) { + this.getShortestTimeData(departureStationName, arrivalStationName); + } + } + + getShortestDistanceData = (departureStationName, arrivalStationName) => { + const radioSelect = SHORTEST_DISTANCE_KOR; + const shortestDistancePath = this.subwayDistanceMap.getShortestDistancePath(departureStationName, arrivalStationName); + let shortestTotalDistance; + let totalTime; + + [shortestTotalDistance, totalTime] = this.subwayDistanceMap.getShortestTotalDistanceAndTotalTime(shortestDistancePath); + + this.renderShortestDistanceResult([radioSelect, shortestTotalDistance, totalTime, shortestDistancePath]); + } + + renderShortestDistanceResult = resultTableData => { + this.subwayPathOutput.renderResult(resultTableData); + } + + getShortestTimeData = (departureStationName, arrivalStationName) => { + const radioSelect = SHORTEST_TIME_KOR; + const shortestTimePath = this.subwayTimeMap.getShortestTimePath(departureStationName, arrivalStationName); + let shortestTotalTime; + let totalDistance; + + [shortestTotalTime, totalDistance] = this.subwayTimeMap.getShortestTotalTimeAndTotalDistance(shortestTimePath); + + this.renderShortestDistanceResult([radioSelect, totalDistance, shortestTotalTime, shortestTimePath]); + } + + renderShortestTimeResult = resultTableData => { + this.subwayPathOutput.renderResult(resultTableData); + } +} \ No newline at end of file diff --git a/src/data/line-data.js b/src/data/line-data.js new file mode 100644 index 00000000..f915d91b --- /dev/null +++ b/src/data/line-data.js @@ -0,0 +1,14 @@ +export const lines = [ + { + name: '2ํ˜ธ์„ ', + stations: ['๊ต๋Œ€', '๊ฐ•๋‚จ', '์—ญ์‚ผ'] + }, + { + name: '3ํ˜ธ์„ ', + stations: ['๊ต๋Œ€', '๋‚จ๋ถ€ํ„ฐ๋ฏธ๋„', '์–‘์žฌ', '๋งค๋ด‰'] + }, + { + name: '์‹ ๋ถ„๋‹น์„ ', + stations: ['๊ฐ•๋‚จ', '์–‘์žฌ', '์–‘์žฌ์‹œ๋ฏผ์˜์ˆฒ'] + }, +] \ No newline at end of file diff --git a/src/data/section-data.js b/src/data/section-data.js new file mode 100644 index 00000000..088de0af --- /dev/null +++ b/src/data/section-data.js @@ -0,0 +1,37 @@ +export const sections = [ + { + connectedStations :['๊ต๋Œ€', '๊ฐ•๋‚จ'], + distance: 2, + time: 3 + }, + { + connectedStations :['๊ฐ•๋‚จ', '์—ญ์‚ผ'], + distance: 2, + time: 3 + }, + { + connectedStations :['๊ต๋Œ€', '๋‚จ๋ถ€ํ„ฐ๋ฏธ๋„'], + distance: 3, + time: 2 + }, + { + connectedStations :['๋‚จ๋ถ€ํ„ฐ๋ฏธ๋„', '์–‘์žฌ'], + distance: 6, + time: 5 + }, + { + connectedStations :['์–‘์žฌ', '๋งค๋ด‰'], + distance: 1, + time: 1 + }, + { + connectedStations :['๊ฐ•๋‚จ', '์–‘์žฌ'], + distance: 2, + time: 8 + }, + { + connectedStations :['์–‘์žฌ', '์–‘์žฌ์‹œ๋ฏผ์˜์ˆฒ'], + distance: 10, + time: 3 + }, +] \ No newline at end of file diff --git a/src/data/station-data.js b/src/data/station-data.js new file mode 100644 index 00000000..f170f565 --- /dev/null +++ b/src/data/station-data.js @@ -0,0 +1,23 @@ +export const stations = [ + { + name: '๊ต๋Œ€' + }, + { + name: '๊ฐ•๋‚จ' + }, + { + name: '์—ญ์‚ผ' + }, + { + name: '๋‚จ๋ถ€ํ„ฐ๋ฏธ๋„' + }, + { + name: '์–‘์žฌ' + }, + { + name: '๋งค๋ด‰' + }, + { + name: '์–‘์žฌ์‹œ๋ฏผ์˜ ์ˆฒ' + }, +] \ No newline at end of file diff --git a/src/index.js b/src/index.js new file mode 100644 index 00000000..9b9f7768 --- /dev/null +++ b/src/index.js @@ -0,0 +1,9 @@ +import SubwayPathController from './controllers/subway-path-controller.js'; + +export default class SubwayPathApp { + constructor() { + this.subwayPathController = new SubwayPathController(); + } +} + +new SubwayPathApp(); \ No newline at end of file diff --git a/src/models/subway-distance-map-model.js b/src/models/subway-distance-map-model.js new file mode 100644 index 00000000..2579fb5a --- /dev/null +++ b/src/models/subway-distance-map-model.js @@ -0,0 +1,65 @@ +import {stations} from '../data/station-data.js'; +import {lines} from '../data/line-data.js'; +import {sections} from '../data/section-data.js'; +import Dijkstra from '../utils/Dijkstra.js'; +import {isIncludesBothStations} from '../services/validation.js'; + +export default class SubwayDistanceMap { + constructor() { + this.subwayDistanceMap = new Dijkstra(); + + this.setSubwayStations(); + this.setSubwayDistances(); + } + + setSubwayStations = () => { + for (let station of stations) { + this.subwayDistanceMap.addVertex(station.name); + } + } + + setSubwayDistances = () => { + for (let section of sections) { + this.subwayDistanceMap.addEdge(section.connectedStations[0], section.connectedStations[1], section.distance); + } + } + + getShortestDistancePath = (departureStation, arrivalStation) => { + const shortestDistancePath = this.subwayDistanceMap.findShortestPath(departureStation, arrivalStation); + + return shortestDistancePath; + } + + getShortestTotalDistanceAndTotalTime = shortestDistancePath => { + let shortestTotalSectionDistance = 0; + let totalSectionTime = 0; + + for (let stationIndex = 1; stationIndex < shortestDistancePath.length; stationIndex++) { + let startStation = shortestDistancePath[stationIndex - 1]; + let endStation = shortestDistancePath[stationIndex]; + let sectionDistance = this.getSectionDistance(sections, startStation, endStation); + let sectionTime = this.getSectionTime(sections, startStation, endStation); + + shortestTotalSectionDistance += sectionDistance; + totalSectionTime += sectionTime; + } + + return [shortestTotalSectionDistance, totalSectionTime]; + } + + getSectionDistance = (sections, startStation, endStation) => { + for (let section of sections) { + if (isIncludesBothStations(section.connectedStations, startStation, endStation)) { + return section.distance; + } + } + } + + getSectionTime = (sections, startStation, endStation) => { + for (let section of sections) { + if (isIncludesBothStations(section.connectedStations, startStation, endStation)) { + return section.time; + } + } + } +} \ No newline at end of file diff --git a/src/models/subway-time-map-model.js b/src/models/subway-time-map-model.js new file mode 100644 index 00000000..73fe3299 --- /dev/null +++ b/src/models/subway-time-map-model.js @@ -0,0 +1,66 @@ +import {stations} from '../data/station-data.js'; +import {lines} from '../data/line-data.js'; +import {sections} from '../data/section-data.js'; +import Dijkstra from '../utils/Dijkstra.js'; +import { isIncludesBothStations } from '../services/validation.js'; + +export default class SubwayTimeMap { + constructor() { + this.subwayTimeMap = new Dijkstra(); + + this.setSubwayStations(); + this.setSubwayTimes(); + } + + setSubwayStations = () => { + for (let station of stations) { + this.subwayTimeMap.addVertex(station.name); + } + } + + setSubwayTimes = () => { + for (let section of sections) { + this.subwayTimeMap.addEdge(section.connectedStations[0], section.connectedStations[1], section.time); + } + } + + getShortestTimePath = (departureStation, arrivalStation) => { + const shortestTimePath = this.subwayTimeMap.findShortestPath(departureStation, arrivalStation); + console.log(this.subwayTimeMap); + + return shortestTimePath; + } + + getShortestTotalTimeAndTotalDistance = shortestTimePath => { + let shortestTotalSectionTime = 0; + let totalSectionDistance = 0; + + for (let stationIndex = 1; stationIndex < shortestTimePath.length; stationIndex++) { + let startStation = shortestTimePath[stationIndex - 1]; + let endStation = shortestTimePath[stationIndex]; + let sectionTime = this.getSectionTime(sections, startStation, endStation); + let sectionDistance = this.getSectionDistance(sections, startStation, endStation); + + shortestTotalSectionTime += sectionTime; + totalSectionDistance += sectionDistance; + } + + return [shortestTotalSectionTime, totalSectionDistance] + } + + getSectionTime = (sections, startStation, endStation) => { + for (let section of sections) { + if (isIncludesBothStations(section.connectedStations, startStation, endStation)) { + return section.time; + } + } + } + + getSectionDistance = (sections, startStation, endStation) => { + for (let section of sections) { + if (isIncludesBothStations(section.connectedStations, startStation, endStation)) { + return section.distance; + } + } + } +} \ No newline at end of file diff --git a/src/services/constants.js b/src/services/constants.js new file mode 100644 index 00000000..2012862d --- /dev/null +++ b/src/services/constants.js @@ -0,0 +1,4 @@ +export const MINIMUM_STATION_NAME_LENGTH = 2; +export const MINIMUM_STATION_NAME_LENGTH_ALERT = `${MINIMUM_STATION_NAME_LENGTH}์ž ์ด์ƒ์˜ ์ด๋ฆ„์„ ์ž…๋ คํ•ด์ฃผ์„ธ์š”.`; +export const SAME_STATION_ALERT = '์‹œ์ž‘ ์—ญ๊ณผ ๋„์ฐฉ์—ญ์˜ ์ด๋ฆ„์ด ๊ฐ™์Šต๋‹ˆ๋‹ค.'; +export const NOT_ON_LINE_ALERT = '๋“ฑ๋ก๋œ ์—ญ์ด ์•„๋‹™๋‹ˆ๋‹ค.'; diff --git a/src/services/validation.js b/src/services/validation.js new file mode 100644 index 00000000..739f2027 --- /dev/null +++ b/src/services/validation.js @@ -0,0 +1,62 @@ +import { + MINIMUM_STATION_NAME_LENGTH, + MINIMUM_STATION_NAME_LENGTH_ALERT, + SAME_STATION_ALERT, + NOT_ON_LINE_ALERT, +} from './constants.js'; +import {stations} from '../data/station-data.js'; + +const includesBothStations = (connectedStations, startStation, endStation) => { + if (connectedStations.includes(startStation) && connectedStations.includes(endStation)) { + return true; + } +} + +export const isIncludesBothStations = (connectedStations, startStation, endStation) => { + return includesBothStations(connectedStations, startStation, endStation); +} + +const isNotInStations = (departureStationName, arrivalStationName) => { + for (let station of stations) { + if (station.name === departureStationName) { + return; + } + } + + for (let station of stations) { + if (station.name === arrivalStationName) { + return; + } + } + alert(NOT_ON_LINE_ALERT); + return true; +} + +const isSameDepartureNameAndArrivalName = (departureStationName, arrivalStationName) => { + if (departureStationName === arrivalStationName) { + alert(SAME_STATION_ALERT); + return true; + } +} + +const isInputLengthTooShort = (departureStationName, arrivalStationName) => { + if (departureStationName.length < MINIMUM_STATION_NAME_LENGTH || + arrivalStationName.length < MINIMUM_STATION_NAME_LENGTH) { + alert(MINIMUM_STATION_NAME_LENGTH_ALERT); + return true; + } +} + +const notValidInput = (departureStationName, arrivalStationName) => { + if ( + isInputLengthTooShort(departureStationName, arrivalStationName) || + isSameDepartureNameAndArrivalName(departureStationName, arrivalStationName) || + isNotInStations(departureStationName, arrivalStationName) + ) { + return true; + } +} + +export const isNotValidInput = (departureStationName, arrivalStationName) => { + return notValidInput(departureStationName, arrivalStationName); +} \ No newline at end of file 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(); +} diff --git a/src/views/constants.js b/src/views/constants.js new file mode 100644 index 00000000..69240623 --- /dev/null +++ b/src/views/constants.js @@ -0,0 +1,6 @@ +export const KILOMETER = 'km'; +export const MINUTE = '๋ถ„'; +export const ARROW = 'โ†’'; + +export const FIRST_RADIO = 0; +export const SECOND_RADIO = 1; diff --git a/src/views/subway-path-input.js b/src/views/subway-path-input.js new file mode 100644 index 00000000..6b063ab9 --- /dev/null +++ b/src/views/subway-path-input.js @@ -0,0 +1,41 @@ +import {FIRST_RADIO, SECOND_RADIO} from './constants.js'; + +export default class SubwayPathInput { + bindSearchButton = searchButtonHandler => { + const searchButton = document.getElementById('search-button'); + + searchButton.addEventListener('click', () => searchButtonHandler(this.getSearchInputs())); + } + + getSearchInputs = () => { + const departureStationName = this.getDepartureStationName(); + const arrivalStationName = this.getArrivalStationName(); + const radioSelect = this.getRadioSelect(); + + return [departureStationName, arrivalStationName, radioSelect]; + } + + getDepartureStationName = () => { + const departureStationNameInput = document.getElementById('departure-station-name-input'); + + return departureStationNameInput.value; + } + + getArrivalStationName = () => { + const arrivalStationNameInput = document.getElementById('arrival-station-name-input'); + + return arrivalStationNameInput.value; + } + + getRadioSelect = () => { + const radioSelectInput = document.getElementsByName('search-type'); + let radioSelect; + if (radioSelectInput[FIRST_RADIO].checked) { + radioSelect = radioSelectInput[FIRST_RADIO].value; + } else if (radioSelectInput[SECOND_RADIO].checked) { + radioSelect = radioSelectInput[SECOND_RADIO].value; + } + + return radioSelect; + } +} \ No newline at end of file diff --git a/src/views/subway-path-output.js b/src/views/subway-path-output.js new file mode 100644 index 00000000..cc319e54 --- /dev/null +++ b/src/views/subway-path-output.js @@ -0,0 +1,90 @@ +import {KILOMETER, MINUTE, ARROW} from './constants.js'; + +export default class SubwayPathOutput { + constructor() { + this.subwayPathAppContainer = document.getElementById('app') + + this.renderInputFieldContainer(); + } + + renderInputFieldContainer = () => { + const subwayPathInputContainer = document.createElement('div'); + + subwayPathInputContainer.setAttribute('id', 'subway-path-input-container'); + this.subwayPathAppContainer.appendChild(subwayPathInputContainer); + + this.createInputField(subwayPathInputContainer); + } + + createInputField = subwayPathInputContainer => { + subwayPathInputContainer.innerHTML = + ` +

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

+ ์ถœ๋ฐœ์—ญ
+ ๋„์ฐฉ์—ญ
+ + ์ตœ๋‹จ๊ฑฐ๋ฆฌ + + ์ตœ์†Œ์‹œ๊ฐ„
+ + `; + } + + renderResult = resultTableData => { + this.clearTableContainer(); + const resultTableContainer = document.createElement('div'); + resultTableContainer.setAttribute('id', 'result-table-container'); + this.subwayPathAppContainer.appendChild(resultTableContainer); + + this.renderResultTable(resultTableContainer, resultTableData); + } + + clearTableContainer = () => { + const tableContainer = document.getElementById('result-table-container'); + if (tableContainer !== null) { + tableContainer.remove(); + } + } + + renderResultTable = (resultTableContainer, resultTableData) => { + let radioSelect; + let totalDistance; + let totalTime; + let totalPath; + + [radioSelect, totalDistance, totalTime, totalPath] = [...resultTableData]; + + resultTableContainer.innerHTML = this.createResultType(radioSelect) + this.createResultTable(totalDistance, totalTime, totalPath); + } + + createResultType = radioSelect => { + const resultType = + ` +

๐Ÿ“ ๊ฒฐ๊ณผ

+

${radioSelect}

+ `; + + return resultType; + } + + createResultTable = (totalDistance, totalTime, totalPath) => { + const resultTable = + ` + + + + + + + + + + + + +
์ด ๊ฑฐ๋ฆฌ์ด ์†Œ์š” ์‹œ๊ฐ„
${totalDistance + KILOMETER}${totalTime + MINUTE}
${totalPath.join(ARROW)}
+ `; + + return resultTable; + } +} \ No newline at end of file