Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 78616fc

Browse files
committed
Rewrote "Rat in a maze" algorithm
It's based on the previous implementation but offers a better API and is now properly testable, too
1 parent 15835ed commit 78616fc

File tree

2 files changed

+202
-49
lines changed

2 files changed

+202
-49
lines changed

‎Backtracking/RatInAMaze.js‎

Lines changed: 118 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,136 @@
11
/*
22
* Problem Statement:
3-
* - Given a NxN grid, find whether rat in cell (0,0) can reach target(N-1,N-1);
4-
* - In grid "0" represent blocked cell and "1" represent empty cell
3+
* - Given a NxN grid, find whether rat in cell [0, 0] can reach the target in cell [N-1, N-1]
4+
* - The grid is represented as an array of rows. Each row is represented as an array of 0 or 1 values.
5+
* - A cell with value 0 can not be moved through. Value 1 means the rat can move here.
6+
* - The rat can not move diagonally.
57
*
6-
* Reference for this problem:
7-
* - https://www.geeksforgeeks.org/rat-in-a-maze-backtracking-2/
8+
* Reference for this problem: https://www.geeksforgeeks.org/rat-in-a-maze-backtracking-2/
9+
*/
10+
11+
/**
12+
* Checks if the given grid is valid.
813
*
14+
* A grid needs to satisfy these conditions:
15+
* - must not be empty
16+
* - must be a square
17+
* - must not contain values other than {@code 0} and {@code 1}
18+
*
19+
* @param grid The grid to check.
20+
* @throws TypeError When the given grid is invalid.
921
*/
22+
function validateGrid (grid) {
23+
if (!Array.isArray(grid) || grid.length === 0) throw new TypeError('Grid must be a non-empty array')
1024

11-
// Helper function to find if current cell is out of boundary
12-
constoutOfBoundary=(grid,currentRow,currentColumn)=>{
13-
if(currentRow<0||currentColumn<0||currentRow>=grid.length||currentColumn>=grid[0].length){
14-
returntrue
15-
}else{
16-
returnfalse
17-
}
25+
constallRowsHaveCorrectLength=grid.every(row=>row.length===grid.length)
26+
if(!allRowsHaveCorrectLength)thrownewTypeError('Grid must be a square')
27+
28+
constallCellsHaveValidValues=grid.every(row=>{
29+
returnrow.every(cell=>cell===0||cell===1)
30+
})
31+
if(!allCellsHaveValidValues)thrownewTypeError('Grid must only contain 0s and 1s')
1832
}
1933

20-
const printPath = (grid, currentRow, currentColumn, path) => {
21-
// If cell is out of boundary, we can't proceed
22-
if (outOfBoundary(grid, currentRow, currentColumn)) return false
34+
function isSafe (grid, x, y) {
35+
const n = grid.length
36+
return x >= 0 && x < n && y >= 0 && y < n && grid[y][x] === 1
37+
}
2338

24-
// If cell is blocked then you can't go ahead
25-
if (grid[currentRow][currentColumn] === 0) return false
39+
/**
40+
* Attempts to calculate the remaining path to the target.
41+
*
42+
* @param grid The full grid.
43+
* @param x The current X coordinate.
44+
* @param y The current Y coordinate.
45+
* @param solution The current solution matrix.
46+
* @param path The path we took to get from the source cell to the current location.
47+
* @returns {string|boolean} Either the path to the target cell or false.
48+
*/
49+
function getPathPart (grid, x, y, solution, path) {
50+
const n = grid.length
2651

27-
// If we reached target cell, then print path
28-
if (currentRow === targetRow&& currentColumn === targetColumn) {
29-
console.log(path)
30-
return true
52+
// are we there yet?
53+
if (x === n-1&& y === n-1&&grid[y][x]===1) {
54+
solution[y][x]=1
55+
return path
3156
}
3257

33-
// R,L,D,U are directions `Right, Left, Down, Up`
34-
const directions = [
35-
[1, 0, 'D'],
36-
[-1, 0, 'U'],
37-
[0, 1, 'R'],
38-
[0, -1, 'L']
39-
]
40-
41-
for (let i = 0; i < directions.length; i++) {
42-
const nextRow = currentRow + directions[i][0]
43-
const nextColumn = currentColumn + directions[i][1]
44-
const updatedPath = path + directions[i][2]
45-
46-
grid[currentRow][currentColumn] = 0
47-
if (printPath(grid, nextRow, nextColumn, updatedPath)) return true
48-
grid[currentRow][currentColumn] = 1
49-
}
58+
// did we step on a 0 cell or outside the grid?
59+
if (!isSafe(grid, x, y)) return false
60+
61+
// are we walking onto an already-marked solution coordinate?
62+
if (solution[y][x] === 1) return false
63+
64+
// none of the above? let's dig deeper!
65+
66+
// mark the current coordinates on the solution matrix
67+
solution[y][x] = 1
68+
69+
// attempt to move right
70+
const right = getPathPart(grid, x + 1, y, solution, path + 'R')
71+
if (right) return right
72+
73+
// right didn't work: attempt to move down
74+
const down = getPathPart(grid, x, y + 1, solution, path + 'D')
75+
if (down) return down
76+
77+
// down didn't work: attempt to move up
78+
const up = getPathPart(grid, x, y - 1, solution, path + 'U')
79+
if (up) return up
80+
81+
// up didn't work: attempt to move left
82+
const left = getPathPart(grid, x - 1, y, solution, path + 'L')
83+
if (left) return left
84+
85+
// no direction was successful: remove this cell from the solution matrix and backtrack
86+
solution[y][x] = 0
5087
return false
5188
}
5289

53-
// Driver Code
90+
function getPath (grid) {
91+
// grid dimensions
92+
const n = grid.length
93+
94+
// prepare solution matrix
95+
const solution = []
96+
for (let i = 0; i < n; i++) {
97+
const row = Array(n)
98+
row.fill(0)
99+
solution[i] = row
100+
}
101+
102+
return getPathPart(grid, 0, 0, solution, '')
103+
}
104+
105+
/**
106+
* Creates an instance of the "rat in a maze" based on a given grid (maze).
107+
*/
108+
export class RatInAMaze {
109+
110+
/** Path from the source [0, 0] to the target [N-1, N-1]. */
111+
#_path = ''
54112

55-
const grid = [
56-
[1, 1, 1, 1],
57-
[1, 0, 0, 1],
58-
[0, 0, 1, 1],
59-
[1, 1, 0, 1]
60-
]
113+
#_solved = false
61114

62-
const targetRow = grid.length - 1
63-
const targetColumn = grid[0].length - 1
115+
constructor (grid) {
116+
// first, let's do some error checking on the input
117+
validateGrid(grid)
64118

65-
// Variation 2 : print a possible path to reach from (0, 0) to (N-1, N-1)
66-
// If there is no path possible then it will print "Not Possible"
67-
!printPath(grid, 0, 0, '') && console.log('Not Possible')
119+
// attempt to solve the maze now - all public methods only query the result state later
120+
const solution = getPath(grid)
121+
122+
if (solution !== false) {
123+
this.#_path = solution
124+
this.#_solved = true
125+
}
126+
}
127+
128+
get solved () {
129+
return this.#_solved
130+
}
131+
132+
get path () {
133+
return this.#_path
134+
}
135+
136+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { RatInAMaze } from '../RatInAMaze'
2+
3+
describe('RatInAMaze', () => {
4+
it('should fail for non-arrays', () => {
5+
const values = [undefined, null, {}, 42, 'hello, world']
6+
7+
for (const value of values) {
8+
expect(() => {new RatInAMaze(value)}).toThrow()
9+
}
10+
})
11+
12+
it('should fail for an empty array', () => {
13+
expect(() => {new RatInAMaze([])}).toThrow()
14+
})
15+
16+
it('should fail for a non-square array', () => {
17+
const array = [
18+
[0, 0, 0],
19+
[0, 0]
20+
]
21+
22+
expect(() => {new RatInAMaze(array)}).toThrow()
23+
})
24+
25+
it('should fail for arrays containing invalid values', () => {
26+
const values = [[[2]], [['a']]]
27+
28+
for (const value of values) {
29+
expect(() => {new RatInAMaze(value)}).toThrow()
30+
}
31+
})
32+
33+
it('should work for a single-cell maze', () => {
34+
const maze = new RatInAMaze([[1]])
35+
expect(maze.solved).toBe(true)
36+
expect(maze.path).toBe('')
37+
})
38+
39+
it('should work for a single-cell maze that can not be solved', () => {
40+
const maze = new RatInAMaze([[0]])
41+
expect(maze.solved).toBe(false)
42+
expect(maze.path).toBe('')
43+
})
44+
45+
it('should work for a simple 3x3 maze', () => {
46+
const maze = new RatInAMaze([[1, 1, 0], [0, 1, 0], [0, 1, 1]])
47+
expect(maze.solved).toBe(true)
48+
expect(maze.path).toBe('RDDR')
49+
})
50+
51+
it('should work for a simple 2x2 that can not be solved', () => {
52+
const maze = new RatInAMaze([[1, 0], [0, 1]])
53+
expect(maze.solved).toBe(false)
54+
expect(maze.path).toBe('')
55+
})
56+
57+
it('should work for a more complex maze', () => {
58+
const maze = new RatInAMaze([
59+
[1, 1, 1, 1, 1, 0, 0],
60+
[0, 0, 0, 0, 1, 0, 0],
61+
[1, 1, 1, 0, 1, 0, 0],
62+
[1, 0, 1, 0, 1, 0, 0],
63+
[1, 0, 1, 1, 1, 0, 0],
64+
[1, 0, 0, 0, 0, 0, 0],
65+
[1, 1, 1, 1, 1, 1, 1]
66+
])
67+
expect(maze.solved).toBe(true)
68+
expect(maze.path).toBe('RRRRDDDDLLUULLDDDDRRRRRR')
69+
})
70+
71+
it('should work for a more complex maze that can not be solved', () => {
72+
const maze = new RatInAMaze([
73+
[1, 1, 1, 1, 1, 0, 0],
74+
[0, 0, 0, 0, 1, 0, 0],
75+
[1, 1, 1, 0, 1, 0, 0],
76+
[1, 0, 1, 0, 1, 0, 0],
77+
[1, 0, 1, 0, 1, 0, 0],
78+
[1, 0, 0, 0, 0, 0, 0],
79+
[1, 1, 1, 1, 1, 1, 1]
80+
])
81+
expect(maze.solved).toBe(false)
82+
expect(maze.path).toBe('')
83+
})
84+
})

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /