From 44b0a99a803a88c0e51c0a5b37884de49e7e409a Mon Sep 17 00:00:00 2001 From: Oleksii Trekhleb Date: Mon, 14 May 2018 08:56:44 +0300 Subject: [PATCH] Add Tower of Hanoi. --- README.md | 2 + .../uncategorized/hanoi-tower/README.md | 29 ++++++ .../hanoi-tower/__test__/hanoiTower.test.js | 39 ++++++++ .../uncategorized/hanoi-tower/hanoiTower.js | 94 +++++++++++++++++++ src/data-structures/stack/Stack.js | 2 +- 5 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 src/algorithms/uncategorized/hanoi-tower/README.md create mode 100644 src/algorithms/uncategorized/hanoi-tower/__test__/hanoiTower.test.js create mode 100644 src/algorithms/uncategorized/hanoi-tower/hanoiTower.js diff --git a/README.md b/README.md index 577ebc94..dd2ab9fe 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ * [Eulerian Path and Eulerian Circuit](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/eulerian-path) - Fleury's algorithm * [Strongly Connected Components](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/strongly-connected-components) - Kosaraju's algorithm * **Uncategorized** + * [Tower of Hanoi](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/uncategorized/hanoi-tower) * Union-Find * Maze * Sudoku @@ -89,6 +90,7 @@ * [Prim’s Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/prim) - finding Minimum Spanning Tree (MST) for weighted undirected graph * [Kruskal’s Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/kruskal) - finding Minimum Spanning Tree (MST) for weighted undirected graph * **Divide and Conquer** + * [Tower of Hanoi](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/uncategorized/hanoi-tower) * [Euclidean Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/math/euclidean-algorithm) - calculate the Greatest Common Divisor (GCD) * [Permutations](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/permutations) (with and without repetitions) * [Combinations](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/combinations) (with and without repetitions) diff --git a/src/algorithms/uncategorized/hanoi-tower/README.md b/src/algorithms/uncategorized/hanoi-tower/README.md new file mode 100644 index 00000000..581d3c19 --- /dev/null +++ b/src/algorithms/uncategorized/hanoi-tower/README.md @@ -0,0 +1,29 @@ +# Tower of Hanoi + +The Tower of Hanoi (also called the Tower of Brahma or Lucas' +Tower and sometimes pluralized) is a mathematical game or puzzle. +It consists of three rods and a number of disks of different sizes, +which can slide onto any rod. The puzzle starts with the disks in +a neat stack in ascending order of size on one rod, the smallest +at the top, thus making a conical shape. + +The objective of the puzzle is to move the entire stack to another +rod, obeying the following simple rules: + +- Only one disk can be moved at a time. +- Each move consists of taking the upper disk from one of the +stacks and placing it on top of another stack or on an empty rod. +- No disk may be placed on top of a smaller disk. + +![Hanoi Tower](https://upload.wikimedia.org/wikipedia/commons/8/8d/Iterative_algorithm_solving_a_6_disks_Tower_of_Hanoi.gif) + +Animation of an iterative algorithm solving 6-disk problem + +With `3` disks, the puzzle can be solved in `7` moves. The minimal +number of moves required to solve a Tower of Hanoi puzzle +is `2n − 1`, where `n` is the number of disks. + +## References + +- [Wikipedia](https://en.wikipedia.org/wiki/Tower_of_Hanoi) +- [HackerEarth](https://www.hackerearth.com/blog/algorithms/tower-hanoi-recursion-game-algorithm-explained/) diff --git a/src/algorithms/uncategorized/hanoi-tower/__test__/hanoiTower.test.js b/src/algorithms/uncategorized/hanoi-tower/__test__/hanoiTower.test.js new file mode 100644 index 00000000..f6c9a788 --- /dev/null +++ b/src/algorithms/uncategorized/hanoi-tower/__test__/hanoiTower.test.js @@ -0,0 +1,39 @@ +import hanoiTower from '../hanoiTower'; + +describe('hanoiTower', () => { + it('should solve tower of hanoi puzzle with 2 discs', () => { + const moveCallbackMock = jest.fn(); + + hanoiTower(2, moveCallbackMock); + + expect(moveCallbackMock).toHaveBeenCalledTimes(3); + + expect(moveCallbackMock.mock.calls[0][0]).toBe(1); + expect(moveCallbackMock.mock.calls[0][1]).toEqual([1, 2]); + expect(moveCallbackMock.mock.calls[0][2]).toEqual([]); + + expect(moveCallbackMock.mock.calls[1][0]).toBe(2); + expect(moveCallbackMock.mock.calls[1][1]).toEqual([2]); + expect(moveCallbackMock.mock.calls[1][2]).toEqual([]); + + expect(moveCallbackMock.mock.calls[2][0]).toBe(1); + expect(moveCallbackMock.mock.calls[2][1]).toEqual([1]); + expect(moveCallbackMock.mock.calls[2][2]).toEqual([2]); + }); + + it('should solve tower of hanoi puzzle with 3 discs', () => { + const moveCallbackMock = jest.fn(); + + hanoiTower(3, moveCallbackMock); + + expect(moveCallbackMock).toHaveBeenCalledTimes(7); + }); + + it('should solve tower of hanoi puzzle with 6 discs', () => { + const moveCallbackMock = jest.fn(); + + hanoiTower(6, moveCallbackMock); + + expect(moveCallbackMock).toHaveBeenCalledTimes(63); + }); +}); diff --git a/src/algorithms/uncategorized/hanoi-tower/hanoiTower.js b/src/algorithms/uncategorized/hanoi-tower/hanoiTower.js new file mode 100644 index 00000000..8406bf29 --- /dev/null +++ b/src/algorithms/uncategorized/hanoi-tower/hanoiTower.js @@ -0,0 +1,94 @@ +import Stack from '../../../data-structures/stack/Stack'; + +/** + * @param {Stack} fromPole + * @param {Stack} toPole + * @param {function(disc: number, fromPole: number[], toPole: number[])} moveCallback + */ +function moveDisc(fromPole, toPole, moveCallback) { + moveCallback(fromPole.peek(), fromPole.toArray(), toPole.toArray()); + + const disc = fromPole.pop(); + toPole.push(disc); +} + +/** + * @param {number} numberOfDiscs + * @param {Stack} fromPole + * @param {Stack} withPole + * @param {Stack} toPole + * @param {function(disc: number, fromPole: number[], toPole: number[])} moveCallback + */ +function hanoiTowerRecursive({ + numberOfDiscs, + fromPole, + withPole, + toPole, + moveCallback, +}) { + if (numberOfDiscs === 1) { + // Base case with just one disc. + moveDisc(fromPole, toPole, moveCallback); + } else { + // In case if there are more discs then move them recursively. + + // Expose the bottom disc on fromPole stack. + hanoiTowerRecursive({ + numberOfDiscs: numberOfDiscs - 1, + fromPole, + withPole: toPole, + toPole: withPole, + moveCallback, + }); + + // Move the disc that was exposed to its final destination. + hanoiTowerRecursive({ + numberOfDiscs: 1, + fromPole, + withPole, + toPole, + moveCallback, + }); + + // Move temporary tower from auxiliary pole to its final destination. + hanoiTowerRecursive({ + numberOfDiscs: numberOfDiscs - 1, + fromPole: withPole, + withPole: fromPole, + toPole, + moveCallback, + }); + } +} + +/** + * @param {number} numberOfDiscs + * @param {function(disc: number, fromPole: number[], toPole: number[])} moveCallback + */ +export default function hanoiTower(numberOfDiscs, moveCallback) { + // Each of three poles of Tower of Hanoi puzzle is represented as a stack + // that might contain elements (discs). Each disc is represented as a number. + // Larger discs have bigger number equivalent. + + // The pole from where the discs should be moved. + const fromPole = new Stack(); + + // The middle pole that should be used as a helper. + const withPole = new Stack(); + + // The destination pole where all discs need to be moved. + const toPole = new Stack(); + + // Let's create the discs and put them to the fromPole. + for (let discSize = numberOfDiscs; discSize > 0; discSize -= 1) { + fromPole.push(discSize); + } + + hanoiTowerRecursive({ + numberOfDiscs, + fromPole, + withPole, + toPole, + moveCallback, + }); +} diff --git a/src/data-structures/stack/Stack.js b/src/data-structures/stack/Stack.js index 0cd95b07..daa5d939 100644 --- a/src/data-structures/stack/Stack.js +++ b/src/data-structures/stack/Stack.js @@ -13,7 +13,7 @@ export default class Stack { } /** - * @return {LinkedListNode} + * @return {*} */ peek() { if (!this.linkedList.tail) {