I recently got a Scala developer position, so I'd like to get more comfortable with Scala on my own. Here is a maze solver that I wrote (previously in other languages) that I translated to Scala. Any tips or suggestions on coding style is greatly appreciated.
import scala.collection.immutable.Queue
object MazeSolver {
type Grid[A] = Array[Array[A]]
type Indices = (Int, Int)
type Opt2[A] = Option[Option[A]]
type IndexGrid = Grid[Opt2[Indices]]
type Predicate[A] = A => Boolean
def validAndTraversable[A](isTraversable: Predicate[A], grid: Grid[A], xy: Indices): Boolean = {
val (x, y) = xy
val xbound = grid.length
val ybound = grid(0).length
val withinBounds = (x >= 0) && (x < xbound) && (y >= 0) && (y < ybound)
withinBounds && isTraversable(grid(x)(y))
}
def getPath(grid: IndexGrid, end: Indices) = {
def pathAccumulator(grid: IndexGrid, path: List[Indices], xy: Indices): List[Indices] = {
val (x, y) = xy
grid(x)(y) match {
case Some(Some(prevXY)) => pathAccumulator(grid, xy :: path, prevXY)
case Some(None) => xy :: path
case None => Nil
}
}
pathAccumulator(grid, Nil, end)
}
def mazeSolverLoop[A](isFinish: (Indices, A) => Boolean,
isTraversable: Predicate[A],
grid: Grid[A],
queue: Queue[Indices],
indexGrid: IndexGrid): List[Indices] = if (queue.isEmpty) Nil else {
val (currentXY, rest) = queue.dequeue
val (x, y) = currentXY
if (isFinish(currentXY, grid(x)(y))) {
getPath(indexGrid, currentXY)
}
else {
val neighbors = List((x + 1, y), (x, y + 1), (x - 1, y), (x, y - 1))
val unvisited = neighbors.filter { case ij @ (i, j) => validAndTraversable(isTraversable, grid, ij) && indexGrid(i)(j).isEmpty }
for ( (i, j) <- unvisited ) {
indexGrid(i)(j) = Some(Some(currentXY))
}
val updatedQueue = rest ++ unvisited
mazeSolverLoop(isFinish, isTraversable, grid, updatedQueue, indexGrid)
}
}
def findUnknownFinish[A](start: Indices,
isFinish: (Indices, A) => Boolean,
isTraversable: Predicate[A],
grid: Grid[A]) =
if (validAndTraversable(isTraversable, grid, start)) {
val (x, y) = start
val indexGrid = Array.fill[Opt2[Indices]](grid.length, grid(0).length)(None)
indexGrid(x)(y) = Some(None)
mazeSolverLoop(isFinish, isTraversable, grid, Queue(start), indexGrid)
}
else {
Nil
}
def findKnownFinish[A](start: Indices,
finish: Indices,
isTraversable: Predicate[A],
grid: Grid[A]) = findUnknownFinish(start, (xy: Indices, a: A) => (xy == finish), isTraversable, grid)
def escapeMaze[A](start: Indices, isTraversable: Predicate[A], grid: Grid[A]) = {
val xbound = grid.length
val ybound = grid(0).length
val boundaryPred = (xy: Indices, a: A) => {
val (x, y) = xy
((x == 0) || (x == xbound - 1) || (y == 0) || (y == ybound - 1))
}
findUnknownFinish(start, boundaryPred, isTraversable, grid)
}
def escapeMazeV2[A](start: Indices, isTraversable: Predicate[A], grid: Grid[A]) = {
val xbound = grid.length
val ybound = grid(0).length
val boundaryPred = (xy: Indices, a: A) => {
val (x, y) = xy
((x == 0) || (x == xbound - 1) || (y == 0) || (y == ybound - 1)) && (xy != start)
}
findUnknownFinish(start, boundaryPred, isTraversable, grid)
}
def printMazeGrid[A](grid: Grid[A]) = {
grid.foreach { row => println(row.mkString(" ")) };
println();
}
def printPath(path: List[Indices]) {
path.foreach(println(_));
println();
}
}
object Mazes {
val maze_01 = Array(Array(1,1,1,1,1,1,0),
Array(0,0,0,0,0,0,0),
Array(1,1,1,1,1,1,0),
Array(0,0,0,0,0,0,0),
Array(0,1,1,1,1,1,1),
Array(0,0,0,0,0,0,0),
Array(1,1,1,0,1,1,1),
Array(0,0,0,0,0,0,0),
Array(0,1,1,1,1,1,0))
val maze_02 = Array("xxxxxxxxxxxxxxxxxxxxx",
"x x x",
"xx xxxx xxxxxx xxx x",
"x x x x xx x",
"x xxxxx xxxxxxxx x x",
"x x xx x",
"xxxxxx xxxxx xxxx x",
"x xxxx x x x",
"x xx x x x x x x xxx",
"x xx x x x x x x",
"xx x x x xxx xxx xxx",
"x xx x x",
"xxxx x xxxxxx xxxx x",
"x xx x x x x",
"xxxxxx x x xxxxx xxx",
"x xx x x x x",
"xxx x xx xxx xxx x x",
"x x x x x x",
"x x xxxxxx xxxx xxx x",
"x x ox",
"x xxxxxxxxxxxxxxxxxxx").map(_.toArray)
val maze_03 = Array("###########",
"# #",
"# ##### # #",
" # # #",
"### # ### #",
"# # #",
"# # ### ###",
"# # # ",
"# ### # # #",
"# # #",
"###########").map(_.toArray)
def main(args: Array[String]) {
val maze1_s1 = MazeSolver.findKnownFinish(start = (1,0),
finish = (8,6),
isTraversable = (x: Int) => (x == 0),
grid = maze_01)
val maze2_s1 = MazeSolver.findUnknownFinish(start = (1,1),
isFinish = (xy: (Int, Int), c: Char) => (c == 'o'),
isTraversable = (c: Char) => (c != 'x'),
grid = maze_02)
val maze2_s2 = MazeSolver.escapeMaze(start = (1,1),
isTraversable = (c: Char) => (c != 'x'),
grid = maze_02)
val maze3_s1 = MazeSolver.escapeMazeV2(start = (3,0),
isTraversable = (c: Char) => (c != '#'),
grid = maze_03)
val maze3_s2 = MazeSolver.escapeMazeV2(start = (7,10),
isTraversable = (c: Char) => (c != '#'),
grid = maze_03)
MazeSolver.printMazeGrid(maze_01)
MazeSolver.printPath(maze1_s1)
MazeSolver.printMazeGrid(maze_02)
MazeSolver.printPath(maze2_s1)
MazeSolver.printPath(maze2_s2)
MazeSolver.printMazeGrid(maze_03)
MazeSolver.printPath(maze3_s1)
MazeSolver.printPath(maze3_s2)
}
}