X Tutup
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 108 additions & 49 deletions Backtracking/RatInAMaze.js
Original file line number Diff line number Diff line change
@@ -1,67 +1,126 @@
/*
* Problem Statement:
* - Given a NxN grid, find whether rat in cell (0,0) can reach target(N-1,N-1);
* - In grid "0" represent blocked cell and "1" represent empty cell
* - Given a NxN grid, find whether rat in cell [0, 0] can reach the target in cell [N-1, N-1]
* - The grid is represented as an array of rows. Each row is represented as an array of 0 or 1 values.
* - A cell with value 0 can not be moved through. Value 1 means the rat can move here.
* - The rat can not move diagonally.
*
* Reference for this problem:
* - https://www.geeksforgeeks.org/rat-in-a-maze-backtracking-2/
* Reference for this problem: https://www.geeksforgeeks.org/rat-in-a-maze-backtracking-2/
*
* Based on the original implementation contributed by Chiranjeev Thapliyal (https://github.com/chiranjeev-thapliyal).
*/

// Helper function to find if current cell is out of boundary
const outOfBoundary = (grid, currentRow, currentColumn) => {
if (currentRow < 0 || currentColumn < 0 || currentRow >= grid.length || currentColumn >= grid[0].length) {
return true
} else {
return false
}
/**
* Checks if the given grid is valid.
*
* A grid needs to satisfy these conditions:
* - must not be empty
* - must be a square
* - must not contain values other than {@code 0} and {@code 1}
*
* @param grid The grid to check.
* @throws TypeError When the given grid is invalid.
*/
function validateGrid (grid) {
if (!Array.isArray(grid) || grid.length === 0) throw new TypeError('Grid must be a non-empty array')

const allRowsHaveCorrectLength = grid.every(row => row.length === grid.length)
if (!allRowsHaveCorrectLength) throw new TypeError('Grid must be a square')

const allCellsHaveValidValues = grid.every(row => {
return row.every(cell => cell === 0 || cell === 1)
})
if (!allCellsHaveValidValues) throw new TypeError('Grid must only contain 0s and 1s')
}

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

// If cell is blocked then you can't go ahead
if (grid[currentRow][currentColumn] === 0) return false
/**
* Attempts to calculate the remaining path to the target.
*
* @param grid The full grid.
* @param x The current X coordinate.
* @param y The current Y coordinate.
* @param solution The current solution matrix.
* @param path The path we took to get from the source cell to the current location.
* @returns {string|boolean} Either the path to the target cell or false.
*/
function getPathPart (grid, x, y, solution, path) {
const n = grid.length

// If we reached target cell, then print path
if (currentRow === targetRow && currentColumn === targetColumn) {
console.log(path)
return true
// are we there yet?
if (x === n - 1 && y === n - 1 && grid[y][x] === 1) {
solution[y][x] = 1
return path
}

// R,L,D,U are directions `Right, Left, Down, Up`
const directions = [
[1, 0, 'D'],
[-1, 0, 'U'],
[0, 1, 'R'],
[0, -1, 'L']
]

for (let i = 0; i < directions.length; i++) {
const nextRow = currentRow + directions[i][0]
const nextColumn = currentColumn + directions[i][1]
const updatedPath = path + directions[i][2]

grid[currentRow][currentColumn] = 0
if (printPath(grid, nextRow, nextColumn, updatedPath)) return true
grid[currentRow][currentColumn] = 1
}
// did we step on a 0 cell or outside the grid?
if (!isSafe(grid, x, y)) return false

// are we walking onto an already-marked solution coordinate?
if (solution[y][x] === 1) return false

// none of the above? let's dig deeper!

// mark the current coordinates on the solution matrix
solution[y][x] = 1

// attempt to move right
const right = getPathPart(grid, x + 1, y, solution, path + 'R')
if (right) return right

// right didn't work: attempt to move down
const down = getPathPart(grid, x, y + 1, solution, path + 'D')
if (down) return down

// down didn't work: attempt to move up
const up = getPathPart(grid, x, y - 1, solution, path + 'U')
if (up) return up

// up didn't work: attempt to move left
const left = getPathPart(grid, x - 1, y, solution, path + 'L')
if (left) return left

// no direction was successful: remove this cell from the solution matrix and backtrack
solution[y][x] = 0
return false
}

// Driver Code
function getPath (grid) {
// grid dimensions
const n = grid.length

// prepare solution matrix
const solution = []
for (let i = 0; i < n; i++) {
const row = Array(n)
row.fill(0)
solution[i] = row
}

return getPathPart(grid, 0, 0, solution, '')
}

const grid = [
[1, 1, 1, 1],
[1, 0, 0, 1],
[0, 0, 1, 1],
[1, 1, 0, 1]
]
/**
* Creates an instance of the "rat in a maze" based on a given grid (maze).
*/
export class RatInAMaze {
constructor (grid) {
// first, let's do some error checking on the input
validateGrid(grid)

const targetRow = grid.length - 1
const targetColumn = grid[0].length - 1
// attempt to solve the maze now - all public methods only query the result state later
const solution = getPath(grid)

// Variation 2 : print a possible path to reach from (0, 0) to (N-1, N-1)
// If there is no path possible then it will print "Not Possible"
!printPath(grid, 0, 0, '') && console.log('Not Possible')
if (solution !== false) {
this.path = solution
this.solved = true
} else {
this.path = ''
this.solved = false
}
}
}
92 changes: 92 additions & 0 deletions Backtracking/tests/RatInAMaze.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { RatInAMaze } from '../RatInAMaze'

describe('RatInAMaze', () => {
it('should fail for non-arrays', () => {
const values = [undefined, null, {}, 42, 'hello, world']

for (const value of values) {
// we deliberately want to check whether this constructor call fails or not
// eslint-disable-next-line no-new
expect(() => { new RatInAMaze(value) }).toThrow()
}
})

it('should fail for an empty array', () => {
// we deliberately want to check whether this constructor call fails or not
// eslint-disable-next-line no-new
expect(() => { new RatInAMaze([]) }).toThrow()
})

it('should fail for a non-square array', () => {
const array = [
[0, 0, 0],
[0, 0]
]

// we deliberately want to check whether this constructor call fails or not
// eslint-disable-next-line no-new
expect(() => { new RatInAMaze(array) }).toThrow()
})

it('should fail for arrays containing invalid values', () => {
const values = [[[2]], [['a']]]

for (const value of values) {
// we deliberately want to check whether this constructor call fails or not
// eslint-disable-next-line no-new
expect(() => { new RatInAMaze(value) }).toThrow()
}
})

it('should work for a single-cell maze', () => {
const maze = new RatInAMaze([[1]])
expect(maze.solved).toBe(true)
expect(maze.path).toBe('')
})

it('should work for a single-cell maze that can not be solved', () => {
const maze = new RatInAMaze([[0]])
expect(maze.solved).toBe(false)
expect(maze.path).toBe('')
})

it('should work for a simple 3x3 maze', () => {
const maze = new RatInAMaze([[1, 1, 0], [0, 1, 0], [0, 1, 1]])
expect(maze.solved).toBe(true)
expect(maze.path).toBe('RDDR')
})

it('should work for a simple 2x2 that can not be solved', () => {
const maze = new RatInAMaze([[1, 0], [0, 1]])
expect(maze.solved).toBe(false)
expect(maze.path).toBe('')
})

it('should work for a more complex maze', () => {
const maze = new RatInAMaze([
[1, 1, 1, 1, 1, 0, 0],
[0, 0, 0, 0, 1, 0, 0],
[1, 1, 1, 0, 1, 0, 0],
[1, 0, 1, 0, 1, 0, 0],
[1, 0, 1, 1, 1, 0, 0],
[1, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1]
])
expect(maze.solved).toBe(true)
expect(maze.path).toBe('RRRRDDDDLLUULLDDDDRRRRRR')
})

it('should work for a more complex maze that can not be solved', () => {
const maze = new RatInAMaze([
[1, 1, 1, 1, 1, 0, 1],
[0, 0, 0, 0, 1, 0, 1],
[1, 1, 1, 0, 1, 0, 1],
[1, 0, 1, 0, 1, 0, 1],
[1, 0, 1, 0, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1]
])
expect(maze.solved).toBe(false)
expect(maze.path).toBe('')
})
})
X Tutup