Search code examples
javapath-finding

Path Finding (2D Array)


2D Array

I want to find the path from one point to another point. the black boxes are obstacles and we can't go to it and the blue box is the starting point and the yellow box is the end point I want to go from starting point to end point. I write this code with the help of an algorithm from my book, actually now I want to make it dynamic that means for n x n matrix. so I want guidance that what steps should I have to take to make it run able for n x n matrix. and I also want to ask that is this a best solution to find a shortest path in this scenario or something else ?

import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;

public class Main {
    public static void main(String[] args) {

        boolean[][] boolMaze = new boolean[][] {
                { true, true, true, true, true },
                { false, true, false, true, true },
                { true, false, true, true, true },
                { false, true, true, false, false },
                { false, false, false, false, false },
                { true, true, true, true, true },
                { false, true, false, true, true },
                { true, false, true, true, true },
                { false, true, true, false, false },
                { false, false, false, false, false },
                { true, true, true, true, true },
                { false, true, false, true, true },
                { true, false, true, true, true },
                { false, true, true, false, false },
                { false, false, false, false, false },
                { true, true, true, true, true },
                { false, true, false, true, true },
                { true, false, true, true, true },
                { false, true, true, false, false },
                { false, false, false, false, false } };
        Maze maze = new Maze(boolMaze);
        List<Maze.Cell> path = maze.findPath(0, 0, 3, 2);
        System.out.println("found path in maze: " + path);
    }
}
    class Maze {
        private final boolean[][] maze;

        public Maze(boolean[][] maze) {
            this.maze = maze;
        }

        public int height() {
            return maze.length;
        }

        public int width() {
            return maze[0].length;
        }

    }

    public List<Cell> findPath(int xStart, int yStart, int xGoal, int yGoal) {
        LinkedList<Cell> path = new LinkedList(); // use a linked list, since we
                                                    // don't know how many
                                                    // elements it will contain
                                                    // straight away..
        path.add(new Cell(xStart, yStart));
        HashSet<Cell> visited = new HashSet(); // this set will store all
                                                // visited cells. Make sure to
                                                // add any cell you looked at.
                                                // Don't work with cells which
                                                // you have visited previously,
                                                // by checking
                                                // visited.contains(cell).
        visited.add(path.getFirst());

        // ///////////////////////////

        if (xStart - 1 >= 0 && maze[xStart][yStart - 1]) {
            Cell cell = new Cell(xStart, yStart - 1);
            return path;
        }

        else if (yStart + 1 < height() && maze[xStart][yStart + 1]) {
            Cell cell = new Cell(xStart, yStart + 1);
            return path;
        }

        else if (xStart + 1 < width() && maze[xStart + 1][yStart]) {
            Cell cell = new Cell(xStart + 1, yStart);
            return path;
        }

        else if (xStart - 1 >= 0 && maze[xStart - 1][yStart]) {
            Cell cell = new Cell(xStart - 1, yStart);
            return path;
        }

        // //////////////////////////
        // use your path finding algorithm here (note that you can use getLast()
        // and getFirst() on your list.
    }

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (!(o instanceof Cell))
            return false;

        Cell cell = (Cell) o;

        if (x != cell.x)
            return false;
        return y == cell.y;

    }

    @Override
    public int hashCode() {
        int result = x;
        result = 31 * result + y;
        return result;
    }

    class Cell implements Comparable<Cell> {
        Cell(int x, int y) {
            this.x = x;
            this.y = y;
        }

        int x;
        int y;

        @Override
        public int compareTo(Cell o) {

            if (o.equals(x) && o.equals(y))
                return 0;

            return 1;
        }
    }

Solution

  • There are two parts to your question. First is to make the solution work on a NxM maze and the other is style and performance improvements.

    First of all, there are two things you can quickly fix by using find and replace.

    • replace each occurrence of Integer with int. Integers are actual objects which have to be created and destroyed by the garbage collector. Also they have to be converted each time you use them for calculation, which heavily affects performance.
    • The same is true for your Boolean array. Use boolean[][] instead

    Please also remove the directions array. It is pointless, since directions[i] == i is always true. You could just work with int variables all the time. There would even be an even nicer solution with enum, but lets not introduce too many new concepts here..

    This code:

    for(int i = 0; i < directions.length; i++) {
         Cell newCell = findCell(current,directions[i]);
         //code..
     }
    

    would become this code:

     for(int i = 0; i < 4; i++) {
         Cell newCell = findCell(current,i);
         //code..
     }
    

    Then you should start using maze as an object, since it is already a class. You'll have to remove the static keyword from all variables and methods, since they will work on private members in the future Create a new Class called Main, where you call the following code in the newly added main method:

    boolean[][] boolMaze = new boolean[][]{/*initialize array*/};
    Maze maze = new Maze(boolMaze);
    List<Cell> path = maze.findPath(0, 0, 3, 2);
    System.out.println("found path in maze: "+path)
    

    So we need two new things. A proper constructor for Maze and the method findPath.

    The class Maze should contain the private (possibly final) member boolean[][] maze and the constructor should set it.

    public Maze(boolean[][] maze) {
        this.maze = maze;
    }
    

    Also remove the variables WIDTH and HEIGHT, as they don't necessarily reflect the size of the array. The nice thing about java is, that arrays remember which size they have. Add public helper functions for quick access though:

    public int height() {
        return maze.length;
    }
    
    public int width() {
        return maze[0].length; // assuming that maze.length is always > 0
    }
    

    The findPath method should create a List<Cell> and return it:

    public List<Cell> findPath(int xStart, int yStart, int xGoal, int yGoal) {
        LinkedList<Cell> path = new LinkedList(); //use a linked list, since we don't know how many elements it will contain straight away..
        path.add(new Cell(xStart, yStart));
        HashSet<Cell> visited = new HashSet(); //this set will store all visited cells. Make sure to add any cell you looked at. Don't work with cells which you have visited previously, by checking visited.contains(cell).
        visited.add(path.getFirst());
        //use your path finding algorithm here (note that you can use getLast() and getFirst() on your list.
        return path;
    }
    

    You can then also get rid of the parent attribute in cell. And to compare if two Cells are the same, don't use compare. That method is used to sort objects. Implement:

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Cell)) return false;
    
        Cell cell = (Cell) o;
    
        if (x != cell.x) return false;
        return y == cell.y;
    
    }
    
    @Override
    public int hashCode() {
        int result = x;
        result = 31 * result + y;
        return result;
    }
    

    In Cell. When checking if two Cells are the same, call cell.equals(otherCell)


    Further improvements

    You actually implemented a flood fill algorithm, which means you just naively fill the whole plane until you found your goal. The standard algorithm for solving mazes is always trying to stick to one wall. Note that this only works, if your entry point and your goal are on the edge of your maze (like it is often the case) Use this site to learn more about the algorithm You might want to keep your path finding algorithm and only use the improved one if your start and goal are next to the edge.