diff --git a/.vscode/settings.json b/.vscode/settings.json index de4ff35..376d887 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,5 +6,5 @@ "source.organizeImports.biome": "explicit" }, "cSpell.language": "en,de-DE", - "cSpell.words": ["amira", "bella", "fibonacci"] + "cSpell.words": ["amira", "bella", "distanz", "fibonacci"] } diff --git a/Problems/Dependable-Jobs-Schedule/solver.ts b/Problems/Dependable-Jobs-Schedule/solver.ts index 02468d8..59eb59a 100644 --- a/Problems/Dependable-Jobs-Schedule/solver.ts +++ b/Problems/Dependable-Jobs-Schedule/solver.ts @@ -14,8 +14,6 @@ export default function finishAll(taskAmount: number, dependencies: number[][]): } } - console.log(dep); - for (let i = 0; i < dependencies.length; i++) { for (const d of Array.from(dep.entries())) { if (!canBeFinished.get(d[0])) { diff --git a/Problems/Shortes-Path-Dijkstra/README.md b/Problems/Shortes-Path-Dijkstra/README.md new file mode 100644 index 0000000..f0858cd --- /dev/null +++ b/Problems/Shortes-Path-Dijkstra/README.md @@ -0,0 +1,16 @@ +# Shortes Path Dijkstra-Algorithmus + +This problem is about finding the shortest path between given destinations and the connections between them, where each connection has a specific length. + +## Documentation + +### Solution Idea + +I implemented the Dijkstra-Algorithmus. +You can see [here](https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm) how this algorithm works. + +--- + +### [Implementation](./solver.ts) + +--- diff --git a/Problems/Shortes-Path-Dijkstra/shortes-path-dijkstra.test.ts b/Problems/Shortes-Path-Dijkstra/shortes-path-dijkstra.test.ts new file mode 100644 index 0000000..ed7ddf9 --- /dev/null +++ b/Problems/Shortes-Path-Dijkstra/shortes-path-dijkstra.test.ts @@ -0,0 +1,450 @@ +import { describe, expect, it } from "vitest"; +import findShortesPaths from "./solver"; + +describe("Returns the shortes path from 'A' to all other Destinations", () => { + const stations1 = ["A", "B", "C", "D", "E", "F", "G"]; + const streets1 = [ + { + A: "A", + B: "B", + length: 1, + }, + { + A: "A", + B: "C", + length: 4, + }, + { + A: "A", + B: "G", + length: 3, + }, + { + A: "B", + B: "C", + length: 2, + }, + { + A: "C", + B: "D", + length: 7, + }, + { + A: "D", + B: "E", + length: 3, + }, + { + A: "F", + B: "G", + length: 5, + }, + { + A: "G", + B: "C", + length: 2, + }, + { + A: "E", + B: "F", + length: 1, + }, + ]; + const result1 = [ + { + length: 0, + path: ["A"], + }, + { + length: 1, + path: ["A", "B"], + }, + { + length: 3, + path: ["A", "B", "C"], + }, + { + length: 10, + path: ["A", "B", "C", "D"], + }, + { + length: 9, + path: ["A", "G", "F", "E"], + }, + { + length: 8, + path: ["A", "G", "F"], + }, + { + length: 3, + path: ["A", "G"], + }, + ]; + + it("should return all paths for 7 destinations", () => { + expect(findShortesPaths("A", stations1, streets1)).toEqual(result1); + }); + + const stations2 = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"]; + const streets2 = [ + { + A: "A", + B: "B", + length: 5, + }, + { + A: "A", + B: "C", + length: 2, + }, + { + A: "A", + B: "D", + length: 6, + }, + { + A: "B", + B: "C", + length: 9, + }, + { + A: "B", + B: "D", + length: 8, + }, + { + A: "C", + B: "F", + length: 20, + }, + { + A: "C", + B: "D", + length: 15, + }, + { + A: "D", + B: "F", + length: 13, + }, + { + A: "D", + B: "G", + length: 2, + }, + { + A: "E", + B: "F", + length: 6, + }, + { + A: "E", + B: "H", + length: 9, + }, + { + A: "E", + B: "I", + length: 7, + }, + { + A: "F", + B: "H", + length: 8, + }, + { + A: "G", + B: "H", + length: 8, + }, + { + A: "G", + B: "J", + length: 6, + }, + { + A: "I", + B: "J", + length: 6, + }, + { + A: "H", + B: "J", + length: 10, + }, + ]; + const result2 = [ + { + length: 0, + path: ["A"], + }, + { + length: 5, + path: ["A", "B"], + }, + { + length: 2, + path: ["A", "C"], + }, + { + length: 6, + path: ["A", "D"], + }, + { + length: 25, + path: ["A", "D", "G", "H", "E"], + }, + { + length: 19, + path: ["A", "D", "F"], + }, + { + length: 8, + path: ["A", "D", "G"], + }, + { + length: 16, + path: ["A", "D", "G", "H"], + }, + { + length: 20, + path: ["A", "D", "G", "J", "I"], + }, + { + length: 14, + path: ["A", "D", "G", "J"], + }, + ]; + + it("should return all paths for 10 destinations", () => { + expect(findShortesPaths("A", stations2, streets2)).toEqual(result2); + }); + + const stations3 = ["A", "B", "C", "D", "E", "F", "G", "H", "Z"]; + const streets3 = [ + { A: "A", B: "B", length: 2 }, + { A: "A", B: "C", length: 2 }, + { A: "A", B: "D", length: 3 }, + + { A: "B", B: "C", length: 1 }, + { A: "B", B: "F", length: 2 }, + + { A: "C", B: "G", length: 8 }, + { A: "C", B: "E", length: 9 }, + + { A: "D", B: "E", length: 1 }, + { A: "D", B: "H", length: 2 }, + + { A: "E", B: "G", length: 5 }, + { A: "E", B: "H", length: 2 }, + + { A: "F", B: "G", length: 2 }, + { A: "F", B: "Z", length: 4 }, + + { A: "G", B: "Z", length: 1 }, + + { A: "H", B: "Z", length: 2 }, + ]; + const result3 = [ + { + length: 0, + path: ["A"], + }, + { + length: 2, + path: ["A", "B"], + }, + { + length: 2, + path: ["A", "C"], + }, + { + length: 3, + path: ["A", "D"], + }, + { + length: 4, + path: ["A", "D", "E"], + }, + { + length: 4, + path: ["A", "B", "F"], + }, + { + length: 6, + path: ["A", "B", "F", "G"], + }, + { + length: 5, + path: ["A", "D", "H"], + }, + { + length: 7, + path: ["A", "D", "H", "Z"], + }, + ]; + + it("should return all paths for 9 destinations", () => { + expect(findShortesPaths("A", stations3, streets3)).toEqual(result3); + }); + + const stations4 = [ + "A", + "AK1", + "Lu", + "Ma", + "Vie", + "We", + "AK2", + "Hd", + "Sch", + "Sp", + "Nb", + "SIL", + "Br", + "Ka", + "Wo", + "Kan", + "LD", + "Neu", + "Ger", + ]; + const streets4 = [ + { A: "A", B: "AK1", length: 12 }, + { A: "AK1", B: "Lu", length: 10 }, + { A: "Lu", B: "Ma", length: 3 }, + { A: "Ma", B: "Vie", length: 9 }, + { A: "Vie", B: "We", length: 8 }, + + { A: "Ma", B: "AK2", length: 16 }, + { A: "AK2", B: "Hd", length: 9 }, + { A: "Hd", B: "We", length: 19 }, + + { A: "AK2", B: "Sch", length: 8 }, + { A: "Sch", B: "Hd", length: 8 }, + + { A: "AK1", B: "Sp", length: 20 }, + { A: "Lu", B: "Sp", length: 15 }, + + { A: "Sp", B: "Nb", length: 11 }, + { A: "Nb", B: "SIL", length: 8 }, + + { A: "SIL", B: "Hd", length: 15 }, + { A: "SIL", B: "Br", length: 16 }, + + { A: "Br", B: "We", length: 12 }, + { A: "Br", B: "Ka", length: 22 }, + + { A: "Ka", B: "We", length: 14 }, + { A: "Ka", B: "Wo", length: 12 }, + + { A: "Wo", B: "Kan", length: 6 }, + { A: "Kan", B: "LD", length: 11 }, + + { A: "LD", B: "Neu", length: 20 }, + { A: "Neu", B: "AK1", length: 14 }, + + { A: "Neu", B: "Sp", length: 21 }, + { A: "LD", B: "Sp", length: 28 }, + + { A: "Sp", B: "Ger", length: 18 }, + { A: "Ger", B: "Br", length: 20 }, + { A: "Ger", B: "Wo", length: 23 }, + ]; + const result4 = [ + { + length: 0, + path: ["A"], + }, + { + length: 12, + path: ["A", "AK1"], + }, + { + length: 22, + path: ["A", "AK1", "Lu"], + }, + { + length: 25, + path: ["A", "AK1", "Lu", "Ma"], + }, + { + length: 34, + path: ["A", "AK1", "Lu", "Ma", "Vie"], + }, + { + length: 42, + path: ["A", "AK1", "Lu", "Ma", "Vie", "We"], + }, + { + length: 41, + path: ["A", "AK1", "Lu", "Ma", "AK2"], + }, + { + length: 50, + path: ["A", "AK1", "Lu", "Ma", "AK2", "Hd"], + }, + { + length: 49, + path: ["A", "AK1", "Lu", "Ma", "AK2", "Sch"], + }, + { + length: 32, + path: ["A", "AK1", "Sp"], + }, + { + length: 43, + path: ["A", "AK1", "Sp", "Nb"], + }, + { + length: 51, + path: ["A", "AK1", "Sp", "Nb", "SIL"], + }, + { + length: 54, + path: ["A", "AK1", "Lu", "Ma", "Vie", "We", "Br"], + }, + { + length: 56, + path: ["A", "AK1", "Lu", "Ma", "Vie", "We", "Ka"], + }, + { + length: 63, + path: ["A", "AK1", "Neu", "LD", "Kan", "Wo"], + }, + { + length: 57, + path: ["A", "AK1", "Neu", "LD", "Kan"], + }, + { + length: 46, + path: ["A", "AK1", "Neu", "LD"], + }, + { + length: 26, + path: ["A", "AK1", "Neu"], + }, + { + length: 50, + path: ["A", "AK1", "Sp", "Ger"], + }, + ]; + + it("should return all paths for 19 destinations", () => { + expect(findShortesPaths("A", stations4, streets4)).toEqual(result4); + }); +}); + +describe("When the given Stations are invalid, the function should throw the correct error!", () => { + it("should throw an Error because the start isn't connected to other stations", () => { + expect(() => findShortesPaths("A", ["A", "B", "C"], [{ A: "B", B: "C", length: 15 }])).toThrowError( + `The station ${"A"} has no connection to any other station!` + ); + }); + + it("should throw an Error because some stations aren't connected to other stations", () => { + expect(() => findShortesPaths("A", ["A", "B", "C"], [{ A: "A", B: "C", length: 15 }])).toThrowError( + `It is not possible to calculate a path for station '${"B"}'. It probably hasn't a connection to other stations!` + ); + }); +}); diff --git a/Problems/Shortes-Path-Dijkstra/solver.ts b/Problems/Shortes-Path-Dijkstra/solver.ts new file mode 100644 index 0000000..b214415 --- /dev/null +++ b/Problems/Shortes-Path-Dijkstra/solver.ts @@ -0,0 +1,145 @@ +interface Street { + A: string; // An id of a station + B: string; // An id of a station + length: number; // The length, how much time it will take to proceed +} + +type Output = { + path: string[]; + length: number; +}[]; + +// Tiny min-heap (priority queue) for { station, length } +type PQItem = { station: string; length: number }; + +class MinHeap { + private data: PQItem[] = []; + + size() { + return this.data.length; + } + + push(item: PQItem) { + this.data.push(item); + this.bubbleUp(this.data.length - 1); + } + + pop(): PQItem | undefined { + if (this.data.length === 0) return undefined; + const top = this.data[0]; + const last = this.data.pop()!; + if (this.data.length > 0) { + this.data[0] = last; + this.bubbleDown(0); + } + return top; + } + + private bubbleUp(i: number) { + while (i > 0) { + const p = Math.floor((i - 1) / 2); + if (this.data[p].length <= this.data[i].length) break; + [this.data[p], this.data[i]] = [this.data[i], this.data[p]]; + i = p; + } + } + + private bubbleDown(i: number) { + for (;;) { + const l = i * 2 + 1; + const r = i * 2 + 2; + let smallest = i; + + if (l < this.data.length && this.data[l].length < this.data[smallest].length) { + smallest = l; + } + if (r < this.data.length && this.data[r].length < this.data[smallest].length) { + smallest = r; + } + if (smallest === i) break; + + [this.data[i], this.data[smallest]] = [this.data[smallest], this.data[i]]; + i = smallest; + } + } +} + +export default function findShortesPaths(start: string, stations: string[], streets: Street[]): Output { + const resultMap: Map = new Map(); + const connections: Map> = new Map(); // Map> + + // Init resultMap + for (const station of stations) { + resultMap.set(station, { + length: station === start ? 0 : Infinity, + previousStation: "", + }); + } + + // Build adjacency map (undirected) + for (const street of streets) { + const add = (from: string, to: string, len: number) => { + if (!connections.has(from)) connections.set(from, new Map()); + connections.get(from)!.set(to, len); + }; + add(street.A, street.B, street.length); + add(street.B, street.A, street.length); + } + + // Dijkstra with a priority queue (no findSmallest scan) + const visited = new Set(); + const pq = new MinHeap(); + pq.push({ station: start, length: 0 }); + + while (pq.size() > 0) { + const item = pq.pop()!; + const current = item.station; + const currentLength = resultMap.get(current)!.length; + + // Skip outdated queue entries (common trick when no decrease-key) + if (item.length !== currentLength) continue; + + // If we already finalized this node, skip + if (visited.has(current)) continue; + visited.add(current); + + const neighbors = connections.get(current); + if (!neighbors) throw new Error(`The station ${current} has no connection to any other station!`); + + for (const [next, dist] of neighbors.entries()) { + if (visited.has(next)) continue; + + const newLen = currentLength + dist; + if (newLen < resultMap.get(next)!.length) { + resultMap.set(next, { length: newLen, previousStation: current }); + pq.push({ station: next, length: newLen }); + } + } + } + + const outputs: Output = []; + + for (const station of stations) { + if (resultMap.get(station)!.length >= Infinity) + throw new Error( + `It is not possible to calculate a path for station '${station}'. It probably hasn't a connection to other stations!` + ); + + const path: string[] = []; + let current = station; + + while (current !== "") { + path.push(current); + current = resultMap.get(current)!.previousStation; + } + + path.reverse(); + + outputs.push({ + path, + length: resultMap.get(station)!.length, + }); + } + + return outputs; +}