I am building a Sudoku Solver visualizer that will solve the sudoku board and show the computer's steps as it tries each number in the available slots. I have been using the JFrame/JPanel framework to visualize this but have had problems updating the graphics to show the computer solving it and pausing the graphics in between each new attempt at each slot.
The solver works perfectly and correctly solves the board but I can only see the unsolved board and the solved board when I click the enter button.
Here is my class with the main method:
public class sudokuSolverVisualizer {
public static void main(String[] args) {
new GameFrame();
}
}
Here is the Game Frame class:
import javax.swing.*;
public class GameFrame extends JFrame {
GameFrame(){
this.add(new GamePanel());
this.setTitle("Sudoku Solver");
this.setResizable(false);
this.pack();
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
this.setVisible(true);
}
}
And here is my JPanel class that solves the sudoku board and updates the graphics:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Line2D;
public class GamePanel extends JPanel implements ActionListener {
//region Variable Declaration
private static final int SIZE = 9;
static final int SCREEN_WIDTH = 270;
static final int SCREEN_HEIGHT = 270;
static final int UNIT_SIZE = SCREEN_WIDTH/SIZE;
static final int GAME_DELAY = 25;
static final int[][] board = {
{7, 0, 2, 0, 5, 0, 6, 0, 0},
{0, 0, 0, 0, 0, 3, 0, 0, 0},
{1, 0, 0, 0, 0, 9, 5, 0, 0},
{8, 0, 0, 0, 0, 0, 0, 9, 0},
{0, 4, 3, 0, 0, 0, 7, 5, 0},
{0, 9, 0, 0, 0, 0, 0, 0, 8},
{0, 0, 9, 7, 0, 0, 0, 0, 5},
{0, 0, 0, 2, 0, 0, 0, 0, 0},
{0, 0, 7, 0, 4, 0, 2, 0, 3}
};
static boolean solving = false;
static boolean solved = false;
static Timer timer;
//endregion
GamePanel(){
this.setPreferredSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT));
this.setBackground(Color.lightGray);
this.setFocusable(true);
this.addKeyListener(new MyKeyAdapter());
startGame();
}
public void paintComponent(Graphics g){
super.paintComponent(g);
draw(g);
}
public void startGame(){
timer = new Timer(GAME_DELAY, this);
timer.start();
}
private static void draw(Graphics g) {
if (solving){
solveBoard(board, g);
}
Graphics2D g2 = (Graphics2D) g;
for (int row = 0; row < SIZE; row++){
if (row % 3 == 0 && row != 0){
g2.setStroke(new BasicStroke(5));
} else {
g2.setStroke(new BasicStroke(1));
}
g2.draw(new Line2D.Float(0, row * UNIT_SIZE, SCREEN_WIDTH, row * UNIT_SIZE));
for (int column = 0; column < SIZE; column++){
if (column % 3 == 0 && column != 0){
g2.setStroke(new BasicStroke(5));
} else {
g2.setStroke(new BasicStroke(1));
}
g2.draw(new Line2D.Float(column * UNIT_SIZE, 0, column * UNIT_SIZE, SCREEN_HEIGHT));
if (solved){
g.setColor(Color.green);
}
g2.drawString(String.valueOf(board[row][column]), column * UNIT_SIZE + 12, row * UNIT_SIZE + 22);
g.setColor(Color.black);
}
}
}
private static boolean isInRow(int[][] board, int num, int row){
for (int i = 0; i < SIZE; i++){
if (board[row][i] == num){
return true;
}
}
return false;
}
private static boolean isInColumn(int[][] board, int num, int column){
for (int i = 0; i < SIZE; i++){
if (board[i][column] == num){
return true;
}
}
return false;
}
private static boolean isInBox(int[][] board, int num, int row, int column){
int rowStart = row - row % 3;
int columnStart = column - column % 3;
for (int i = rowStart; i < rowStart + 3; i++){
for (int j = columnStart; j < columnStart + 3; j++) {
if (board[i][j] == num) {
return true;
}
}
}
return false;
}
private static boolean isValidPlace(int[][] board, int num, int row, int column){
return !isInRow(board, num, row) &&
!isInColumn(board, num, column) &&
!isInBox(board, num, row, column);
}
private static boolean solveBoard(int[][] board, Graphics g){
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.black);
for (int row = 0; row < SIZE; row++) {
for (int column = 0; column < SIZE; column++) {
if (board[row][column] == 0) {
for (int num = 1; num <= SIZE; num++) {
if (isValidPlace(board, num, row, column)) {
board[row][column] = num;
drawElements(g2, row, column, true);
if (solveBoard(board, g)) {
return true;
}
else{
board[row][column] = 0;
drawElements(g2, row, column, false);
}
}
}
return false;
}
}
}
solving = false;
solved = true;
draw(g);
return true;
}
private static void drawElements(Graphics2D g2, int row, int column, boolean correct){
if (correct) {
g2.setColor(Color.green);
} else {
g2.setColor(Color.red);
}
g2.clearRect(column * UNIT_SIZE, row * UNIT_SIZE, UNIT_SIZE, UNIT_SIZE);
g2.drawString(String.valueOf(board[row][column]), column * UNIT_SIZE + 12, row * UNIT_SIZE + 22);
g2.drawRect(column * UNIT_SIZE, row * UNIT_SIZE, UNIT_SIZE, UNIT_SIZE);
g2.setColor(Color.black);
}
private static void printBoard(int[][] board){
for (int row = 0; row < SIZE; row++){
if (row % 3 == 0 && row != 0){
System.out.println("-------------------");
}
for (int column = 0; column < SIZE; column++){
if (column % 3 == 0 && column != 0){
System.out.print("|");
}
System.out.print(board[row][column] + " ");
}
System.out.println();
}
}
#Override
public void actionPerformed(ActionEvent e) {
if (solving) {
repaint();
}
}
public class MyKeyAdapter extends KeyAdapter {
#Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()){
case KeyEvent.VK_ENTER:
solving = true;
break;
}
}
}
}
If you could help me find a way to pause the graphics in between each attempt at each slot or somehow repaint the graphics every time it tries a number, that would be great!
Visualising recursion like this is very hard, because what you need is control over the flow. Rather than the algorithm being allowed to run "freely", you need some way that you can control each iteration.
The first thing I did was adapted a non-recursive solver algorithm from Java | Recursive and non-recursive Sudoku solutions (Starter-friendly)
This allowed to create a step method which would move the solution along by a single (or close enough for the purpose) step.
This means that I can call step from Timer and control when the algorithm updates.
Important
This is NOT a Sudoku solution, this focus on how you might adopt a "recursive" style algorithm so it can be visualised, I take no responsibility for the accuracy of the algorithm 😉
Runnable example
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Stack;
import java.util.StringJoiner;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Main {
int[][] board = {
{7, 0, 2, 0, 5, 0, 6, 0, 0},
{0, 0, 0, 0, 0, 3, 0, 0, 0},
{1, 0, 0, 0, 0, 9, 5, 0, 0},
{8, 0, 0, 0, 0, 0, 0, 9, 0},
{0, 4, 3, 0, 0, 0, 7, 5, 0},
{0, 9, 0, 0, 0, 0, 0, 0, 8},
{0, 0, 9, 7, 0, 0, 0, 0, 5},
{0, 0, 0, 2, 0, 0, 0, 0, 0},
{0, 0, 7, 0, 4, 0, 2, 0, 3}
};
public static void main(String[] args) {
new Main();
}
public Main() {
// print(board);
SudokuSolver solver = new SudokuSolver(board);
// System.out.println();
print(solver.solve());
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
SudokuSolver solver = new SudokuSolver(board);
JFrame frame = new JFrame("Test");
frame.add(new MainPane(solver));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public void print(int[][] board) {
for (int[] row : board) {
StringJoiner joiner = new StringJoiner(" ", "", "\n");
for (int cell : row) {
joiner.add(String.format("%d", cell));
}
System.out.print(joiner.toString());
}
}
public class MainPane extends JPanel {
private SolverPane solverPane;
public MainPane(SudokuSolver solver) {
setLayout(new BorderLayout());
solverPane = new SolverPane(solver);
add(solverPane);
JButton startButton = new JButton("Go");
startButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
startButton.setEnabled(false);
solverPane.start(new SolverPane.Observer() {
#Override
public void didCompleteSolution() {
startButton.setEnabled(true);
}
});
}
});
add(startButton, BorderLayout.SOUTH);
}
}
public class SolverPane extends JPanel {
public interface Observer {
public void didCompleteSolution();
}
private SudokuSolver solver;
private Observer observer;
private Dimension preferredSize;
private int gap = 4;
public SolverPane(SudokuSolver solver) {
this.solver = solver;
solver.prepareSolution();
setFont(new Font("Monospaced", Font.PLAIN, 20));
}
#Override
public Dimension getPreferredSize() {
if (preferredSize == null) {
FontMetrics fm = getFontMetrics(getFont());
preferredSize = new Dimension(10 + ((fm.stringWidth("0") + gap) * 9), 10 + (fm.getHeight() * 9));
}
return preferredSize;
}
public void start(Observer observer) {
this.observer = observer;
solver.prepareSolution();
Instant startedAt = Instant.now();
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (solver.step()) {
observer.didCompleteSolution();
((Timer) e.getSource()).stop();
Duration duration = Duration.between(Instant.now(), startedAt);
System.out.println(duration);
}
repaint();
}
});
timer.start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
FontMetrics fm = g2d.getFontMetrics();
int gap = 4;
int cellWidth = fm.stringWidth("0") + 4;
int xPos = (getWidth() - (cellWidth * 9)) / 2;
int yPos = (getHeight() - (cellWidth * 9)) / 2;
g2d.translate(xPos, yPos);
Stack<SudokuSolver.Cell> solved = solver.getSolved();
if (solved != null) {
for (SudokuSolver.Cell cell : solver.getSolved()) {
int x = cell.getCol() * cellWidth;
int y = ((cell.getRow() - 1) * fm.getHeight());
g2d.drawString(Integer.toString(cell.getValue()), x, y);
}
}
g2d.dispose();
}
}
class SudokuSolver {
// Adapted from https://leetcode.com/problems/sudoku-solver/discuss/1392747/java-recursive-and-non-recursive-sodoku-solutions-starter-friendly
private int[][] board;
private LinkedList<Cell> unsolved;
private Stack<Cell> solved;
public SudokuSolver(int[][] originalBoard) {
this.board = originalBoard;
}
public LinkedList<Cell> getUnsolved() {
return unsolved;
}
public Stack<Cell> getSolved() {
return solved;
}
protected void prepareSolution() {
// all we need are just 1 stack, and 1 queue.
unsolved = new LinkedList<>(); // queue: all unconfirmed cells
solved = new Stack<>(); // stack: all cells which are confirmed temporarily
// init candidates
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if (board[i][j] == 0) {
Cell cell = new Cell(i, j, board);
unsolved.addLast(cell);
} else {
Cell cell = new Cell(i, j);
cell.value = board[i][j];
solved.add(cell);
}
}
}
}
public boolean step() {
if (unsolved == null || solved == null) {
prepareSolution();
}
if (unsolved.peekFirst() == null) {
return true;
}
Cell curr = unsolved.removeFirst();
if (curr.isValidCandid()) {
solved.push(curr); // *
unsolved = excludeCandid(curr, unsolved);
} else { // MARK-s4
unsolved.addFirst(curr); // *
Cell prev = solved.pop(); // *
unsolved = revertCandid(prev, unsolved);
curr.resetCandid(); // restart selection
prev.nextCandid();
unsolved.addFirst(prev); // *
}
return unsolved.peekFirst() == null;
}
public int[][] solve() {
prepareSolution();
// try different combinations until all unsolved cells gone
Cell curr;
while (unsolved.peekFirst() != null) {
curr = unsolved.removeFirst();
if (curr.isValidCandid()) {
solved.push(curr); // *
unsolved = excludeCandid(curr, unsolved);
} else { // MARK-s4
unsolved.addFirst(curr); // *
Cell prev = solved.pop(); // *
unsolved = revertCandid(prev, unsolved);
curr.resetCandid(); // restart selection
prev.nextCandid();
unsolved.addFirst(prev); // *
}
}
int[][] solution = new int[board.length][board.length];
for (int row = 0; row < board.length; row++) {
for (int col = 0; col < board[row].length; col++) {
solution[row][col] = board[row][col];
}
}
// solutions back
while (!solved.empty()) {
confirm(solved.pop(), solution);
}
return solution;
}
void confirm(Cell solution, int[][] problem) {
problem[solution.row][solution.col] = solution.value;
}
// once some candidates are taken, we need to exclude them in related cells
LinkedList<Cell> excludeCandid(Cell target, LinkedList<Cell> before) {
LinkedList<Cell> after = new LinkedList<Cell>();
Cell curr;
while ((curr = before.peekFirst()) != null) {
before.removeFirst();
curr.excludeCandidate(target); // exclude the target candidate
// OPTIONAL, but win more about 70% time!
if (curr.isValidCandid()) // if there is conflict, handle it first
{
after.addLast(curr);
} else {
after.addFirst(curr);
}
}
return after;
}
// once the previous candidates were incorrectly taken, we need to revert/recover them in related cells
LinkedList<Cell> revertCandid(Cell target, LinkedList<Cell> before) {
LinkedList<Cell> after = new LinkedList<Cell>();
Cell curr = null;
while ((curr = before.peekFirst()) != null) {
before.removeFirst();
curr.enableCandidate(target);
after.addLast(curr);
}
return after;
}
// >> SOLUTION
// << STRUCT
public class Cell {
/**
*
* To solve sudoku, we can use one stack to save all temporarily
* confirmed cells, and one queue to save all unconfirmed ones, then
* repeatedly dequeue elements from queue and push them into the
* stack, analysing them at the same time, until queue is empty,
* then Sudoku is solved. If stack encounter EmptyStackException at
* some point of time, this method then is not capable to solve the
* given Sudoku. Note, analysing cells is simple and
* straightforward, just the "pointer" stuff, and the detail is
* shown below.
*
*
* ################################## ## "1 stack and 1 queue" model
* : ##################################
*
* ........................................................................
* .----------- (2,5) (2,4) (2,6) (2,0) .... |
* ........................................................................
* | (unsolved Queue/List) | | \/ | | | | | (2,3) | | .... | | ....
* | | .... | ---------- (solved Stack)
*
*
*
* ################################## ## "candidate pointer" model :
* ( cell(2,0) at any possible point of time )
* ##################################
*
* Characters: 1 2 3 4 5 6 7 8 9
*
* candidates: [-999999, 0 , 33 , -1 , 78 , 0 , 0 , -1 , 0 , -1] ^
* [Stage 1] ... 21 ^ [Stage 2] ... 21 23 22 25 ^(>9) [Stage 3]
*
* Explanations: [Stage 1] candidate pointer(cPtr), when at very
* beginning, and there are 3 possible values that can be chosen;
* [Stage 2] after '1' is selected by 21st cell, i.e. problem[2][2],
* cPtr moves to 5th; [Stage 3] at this point, the '1' was taken by
* 21st cell, '5' was by 23rd, '6' was by 22nd, '8' was by 25th,
* result in cPtr overflow, which means some of previous candidates
* were taken incorrectly, and getting back and retrying is needed.
* [Stage 4] details is shown in some place of codes, marked as
* "MARK-s4"
*
*
*
*/
private int row;
private int col;
private int[] candidates; // -1: confirmed 0: possible >0: be selected by others
private int value = -1; // [1,9] or 10,11,...
public Cell(int i, int j) {
row = i;
col = j;
candidates = new int[10];
candidates[0] = -999999;
}
public Cell(int i, int j, int[][] datasource) {
this(i, j);
initCandidates(datasource);
}
public int getRow() {
return row;
}
public int getCol() {
return col;
}
public int getValue() {
return value;
}
protected void initCandidates(int[][] datasource) {
// same row
for (int i = 0; i < 9; i++) {
if (datasource[row][i] != 0) {
candidates[datasource[row][i]] = -1;
}
}
// same col
for (int i = 0; i < 9; i++) {
if (datasource[i][col] != 0) {
candidates[datasource[i][col]] = -1;
}
}
// same 9-cell
int start_i = row / 3 * 3;
int start_j = col / 3 * 3;
for (int i = start_i; i < start_i + 3; i++) {
for (int j = start_j; j < start_j + 3; j++) {
if (datasource[i][j] != 0) {
candidates[datasource[i][j]] = -1;
}
}
}
// init candid ptr
resetCandid();
}
protected int getCurrCandid() { // [1-9] or -1
if (isValidCandid()) {
return value;
}
return -1;
}
private void resetCandid() {
// to left most 0
int i = 1;
for (; i < 10; i++) {
if (candidates[i] == 0) {
break;
}
}
value = i; // 1..9 or 10
}
private void nextCandid() {
int i = value + 1;
while (i < 10 && candidates[i] != 0) {
i++;
}
value = i; // 1..9 or 10,11,...
}
private void excludeCandidate(Cell by) {
int their = by.getCurrCandid();
if (candidates[their] == 0) {
int theirIdx = by.row * 9 + by.col + 1;
// same row
if (by.row == row) {
candidates[their] = theirIdx;
}
// same col
if (by.col == col) {
candidates[their] = theirIdx;
}
// same cell
if (by.row / 3 * 3 == row / 3 * 3 && by.col / 3 * 3 == col / 3 * 3) {
candidates[their] = theirIdx;
}
}
if (!isValidCandid()) {
nextCandid();
}
}
private void enableCandidate(Cell by) {
int their = by.getCurrCandid(); // must exist
int theirIdx = by.row * 9 + by.col + 1;
if (candidates[their] > 0 && candidates[their] == theirIdx) { // result from their
candidates[their] = 0; // >0 -> 0
if (value >= their) {
resetCandid(); // *
}
}
}
private int numOfCandidate() {
int num = 0;
for (int i = 1; i < 10; i++) {
if (candidates[i] == 0) {
num++;
}
}
return num;
}
private boolean isValidCandid() {
if (value < 1 || value > 9) {
return false;
}
return candidates[value] == 0;
}
public String toString() {
return String.format("%d,%d cptr:%d(%b) (%d)candids:%s\n", row, col, value, isValidCandid(), numOfCandidate(), Arrays.toString(candidates));
}
}
// >> STRUCT
}
}
I have a question in JAVA I can't solve no matter how long I try to think about the solution:
There's a matrix and I need to find the shortest path possible to get from Mat[0][0] to the bottom right of the matrix and I can only proceed to the adjacent square (no diagonals) if the number in it is bigger than the one I'm on right now.
For example:
0 1 2 3 4
0 { 5 13 2 5 2
1 58 24 32 3 24
2 2 7 33 1 7
3 45 40 37 24 70
4 47 34 12 25 2
5 52 56 68 76 100}
So a valid solution would be:
(0,0)->(0,1)->(1,1)->(2,1)->(2,2)->(2,3)->(3,1)->(3,0)->(0,4)->(0,5)->(5,1)->(5,2)->(5,3)->(5,4)
And the method will return 14 because that's the shortest possible path.
I have to use a recursive method only (no loops).
This is what I came up with so far but I don't know how to figure out which is the shortest one.
Public static int shortestPath(int[][]mat)
{
int length=0;
int i=0;
int j=0;
shortestPath(mat, length, i, j);
}
Private static int shortestPath(int[][]math, int length, int i, int j)
{
if((i==mat.length)||(j==mat[i].length))
return length;
if(shortestPath(mat, length, i+1, j) > shortestPath(mat, length, i, j))
return length +1;
if(shortestPath(mat, length, i, j+1) > shortestPath(mat, length, i, j))
return length +1;
if shortestPath(mat, length, i-1, j) > shortestPath(mat, length, i, j))
return length +1;
if shortestPath(mat, length, i, j-1) > shortestPath(mat, length, i, j))
return length +1;
}
I'm not sure if that's the way to do it, and if it is: how do I know which is the shortest way, because right now it would return all possible ways and will add them together (I think).
Also, I think I should add something about reaching the bottom right of the matrix.
The code shouldn't be too complicated.
im not sure if the approach of going to the next smallest value is the shortest, but anyway:
public class Pathfinder {
private int[][] matrix;
private int matrixLenghtI;
private int matrixLenghtJ;
public Pathfinder(int[][] matrix, int matrixLenghtI, int matrixLenghtJ) {
this.matrix = matrix;
this.matrixLenghtI = matrixLenghtI;
this.matrixLenghtJ = matrixLenghtJ;
}
public static void main(String[] args) {
int matrixLenghtI = 6;
int matrixLenghtJ = 5;
int[][] matrix1 = { { 3, 13, 15, 28, 30 }, { 40, 51, 52, 29, 30 }, { 28, 10, 53, 54, 54 },
{ 53, 12, 55, 53, 60 }, { 70, 62, 56, 20, 80 }, { 80, 81, 90, 95, 100 } };
int[][] matrix2 = { { 5, 13, 2, 5, 2 }, { 58, 24, 32, 3, 24 }, { 2, 7, 33, 1, 7 }, { 45, 40, 37, 24, 70 },
{ 47, 34, 12, 25, 2 }, { 52, 56, 68, 76, 100 } };
Pathfinder finder1 = new Pathfinder(matrix1, matrixLenghtI, matrixLenghtJ);
finder1.run();
Pathfinder finder2 = new Pathfinder(matrix2, matrixLenghtI, matrixLenghtJ);
finder2.run();
}
private void run() {
int i = 0;
int j = 0;
System.out.print("(" + i + "," + j + ")");
System.out.println("\nLength: " + find(i, j));
}
private int find(int i, int j) {
int value = matrix[i][j];
int[] next = { i, j };
int smallestNeighbour = 101;
if (i > 0 && matrix[i - 1][j] > value) {
smallestNeighbour = matrix[i - 1][j];
next[0] = i - 1;
next[1] = j;
}
if (j > 0 && matrix[i][j - 1] < smallestNeighbour && matrix[i][j - 1] > value) {
smallestNeighbour = matrix[i][j - 1];
next[0] = i;
next[1] = j - 1;
}
if (i < matrixLenghtI - 1 && matrix[i + 1][j] < smallestNeighbour && matrix[i + 1][j] > value) {
smallestNeighbour = matrix[i + 1][j];
next[0] = i + 1;
next[1] = j;
}
if (j < matrixLenghtJ - 1 && matrix[i][j + 1] < smallestNeighbour && matrix[i][j + 1] > value) {
smallestNeighbour = matrix[i][j + 1];
next[0] = i;
next[1] = j + 1;
}
System.out.print("->(" + next[0] + "," + next[1] + ")");
if (i == matrixLenghtI - 1 && j == matrixLenghtJ - 1)
return 1;
return find(next[0], next[1]) + 1;
}
}
Output:
(0,0)->(0,1)->(0,2)->(0,3)->(1,3)->(1,4)->(2,4)->(3,4)->(4,4)->(5,4)->(5,4)
Length: 10
(0,0)->(0,1)->(1,1)->(1,2)->(2,2)->(3,2)->(3,1)->(3,0)->(4,0)->(5,0)->(5,1)->(5,2)->(5,3)->(5,4)->(5,4)
Length: 14
Position class:
/**
* Represents a position in the matrix.
*/
public class Position {
final private int x;
final private int y;
public Position(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
#Override
public String toString() {
return "(" + x + ", " + y + ')';
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Position position = (Position) o;
if (x != position.x) return false;
return y == position.y;
}
#Override
public int hashCode() {
int result = x;
result = 31 * result + y;
return result;
}
}
Board class:
/**
* A board represents all of the locations in the matrix. It provides a simple interface to getting
* the value in a position, and tracking the height and width of the matrix.
*/
public class Board {
final int [][] board;
public Board(int[][] board) {
this.board = board;
}
final int positionValue(Position position) {
return this.board[position.getY()][position.getX()];
}
final int getWidth() {
return board[0].length;
}
final int getHeight() {
return board.length;
}
}
PathFinder class:
import java.util.ArrayList;
import java.util.List;
/**
* Find the shortest path from a starting point to ending point in a matrix, assuming you can
* only move to a position with a greater value than your current position.
*/
public class PathFinder {
final private Board board;
final private Position start;
final private Position end;
public PathFinder(Board board, int startX, int startY, int endX, int endY) {
this.board = board;
this.start = new Position(startX, startY);
this.end = new Position(endX, endY);
}
/**
* Gets the shortest path from the start to end positions. This method
* takes all of the paths, then determines which one is shortest and returns that.
*
* #return the shortest path from the start to end positions.
*/
public List<Position> shortestPath() {
List<List<Position>> allPaths = this.getAllPaths();
System.out.println("Paths found: " + allPaths.size());
List<Position> shortestPath = null;
for (List<Position> path : allPaths) {
if (shortestPath == null) {
shortestPath = path;
}
else if (shortestPath.size() > path.size()) {
shortestPath = path;
}
}
return shortestPath;
}
/**
* Convenience method for starting the getAllPaths process.
*
* #return all of the paths from the start to end positions
*/
private List<List<Position>> getAllPaths() {
List<List<Position>> paths = new ArrayList<List<Position>>();
return this.getAllPaths(paths, new ArrayList<Position>(), start);
}
/**
* Gets all of the paths from the start to end position. This is done recursively by visiting every
* position, while following the rules that you can only move to a position with a value greater
* than the position you're currently on. When reaching the end position, the path is added to
* the list of all found paths, which is returned.
*
* #param paths the current list of all found paths.
* #param path the current path
* #param position the current position
* #return all paths from the start to end positions
*/
private List<List<Position>> getAllPaths(List<List<Position>> paths, List<Position> path, Position position) {
path.add(position);
if (position.equals(end)) {
paths.add(path);
return paths;
}
//x+
if (position.getX() + 1 < board.getWidth()) {
Position xp = new Position(position.getX() + 1, position.getY());
if (board.positionValue(position) < board.positionValue(xp)) {
getAllPaths(paths, new ArrayList<Position>(path), xp);
}
}
//x-
if (position.getX() - 1 >= 0) {
Position xm = new Position(position.getX() - 1, position.getY());
if (board.positionValue(position) < board.positionValue(xm)) {
getAllPaths(paths, new ArrayList<Position>(path), xm);
}
}
//y+
if (position.getY() + 1 < board.getHeight()) {
Position yp = new Position(position.getX(), position.getY() + 1);
if (board.positionValue(position) < board.positionValue(yp)) {
getAllPaths(paths, new ArrayList<Position>(path), yp);
}
}
//y-
if (position.getY() - 1 >= 0) {
Position ym = new Position(position.getX(), position.getY() - 1);
if (board.positionValue(position) < board.positionValue(ym)) {
getAllPaths(paths, new ArrayList<Position>(path), ym);
}
}
return paths;
}
/**
* Run the example then print the results.
*
* #param args na
*/
public static void main(String[] args) {
int [][] array = {{5, 13, 2, 5, 2},
{14, 24, 32, 3, 24},
{15, 7, 33, 1, 7},
{45, 40, 37, 24, 70},
{47, 34, 12, 25, 2},
{52, 56, 68, 76, 100}
};
final Board board = new Board(array);
final Position end = new Position(board.getWidth()-1, board.getHeight() - 1);
final PathFinder pathFinder = new PathFinder(board, 0, 0, board.getWidth()-1, board.getHeight()-1);
final List<Position> path = pathFinder.shortestPath();
System.out.println("Shortest Path: ");
for (Position position : path) {
if (!position.equals(end)) {
System.out.print(position + " -> ");
}
else {
System.out.println(position);
}
}
System.out.println();
}
}
I really like this problem. Unfortunately I haven't worked in Java for many years, so this answer is pseudo-Java and you'll have to fix some of the syntax. Probably some of the function params should be references and not copies; you'll figure it out (update: I've added a TESTED version in python below).
// just a little thing to hold a set of coordinates
class Position
{
// not bothering with private / getters
public int x ;
public int y ;
public constructor (int x, int y)
{
this.x = x ;
this.y = y ;
}
}
class PathFinder
{
public void main (void)
{
// create a path with just the start position
start = new Position(0, 0) ;
path = new Vector() ;
path.add(start) ;
// create an empty path to contain the final shortest path
finalPath = new Vector() ;
findPath(path, finalPath) ;
print ("Shortest Path: ") ;
showPath (finalPath) ;
}
private void showPath (Vector path) {
// print out each position in the path
iter = path.iterator() ;
while (pos = iter.next()) {
print ("(%, %) ", pos.x, pos.y);
}
// print out the length of the path
print (" Length: %\n", path.size()) ;
}
// recursive function to find shortest path
private void findPath (Vector path, Vector finalPath)
{
// always display the current path (it should never be the same path twice)
showPath(path) ;
// where are we now?
here = path.lastElement() ;
// does the current path find the exit (position 4,5)?
if (here.x == 4 && here.y == 5) {
if (finalPath.size() == 0) {
//finalPath is empty, put the current path in finalPath
finalPath = path ;
} else {
// some other path found the exit already. Which path is shorter?
if (finalPath.size() > path.size()) {
finalPath = path ;
}
}
// either way, we're at the exit and this path goes no further
return ;
}
// path is not at exit; grope in all directions
// note the code duplication in this section is unavoidable
// because it may be necessary to start new paths in three
// directions from any given position
// If no new paths are available from the current position,
// no new calls to findPath() will happen and
// the recursion will collapse.
if (here.x > 0 && matrix[here.x-1][here.y] > matrix[here.x][here.y]) {
// we can move left
newPos = new Position(here.x-1, here.y) ;
newPath = path ;
newPath.add (newPos) ;
findPath(newPath, finalPath) ;
}
if (here.x < 4 && matrix[here.x+1][here.y] > matrix[here.x][here.y]) {
// we can move right
newPos = new Position(here.x+1, here.y) ;
newPath = path ;
newPath.add (newPos) ;
findPath(newPath, finalPath) ;
}
if (here.y > 0 && matrix[here.x][here.y-1] > matrix[here.x][here.y]) {
// we can move up
newPos = new Position(here.x, here.y-1) ;
newPath = path ;
newPath.add (newPos) ;
findPath(newPath, finalPath) ;
}
if (here.y < 5 && matrix[here.x][here.y+1] > matrix[here.x][here.y]) {
// we can move down
newPos = new Position(here.x, here.y+1) ;
newPath = path ;
newPath.add (newPos) ;
findPath(newPath, finalPath) ;
}
}
}
Here's a tested version of the same algorithm in python. (I noticed that using x, y as coordinates is kind of misleading. x is actually "vertical" and y is "horizontal" with the array indexed the way it is. I've set up a matrix with four paths to the exit and a couple of dead ends.)
import copy, sys
matrix = [
[5, 13, 17, 58, 2],
[17, 24, 32, 3, 24],
[23, 7, 33, 1, 7],
[45, 40, 37, 38, 70],
[47, 34, 12, 25, 2],
[52, 56, 68, 76, 100]]
def showPath(path):
for position in path:
sys.stdout.write("(" + str(position[0]) + ", " + str(position[1]) + "), ")
sys.stdout.write("\n\n")
sys.stdout.flush()
def findPath(path):
#showPath(path)
global finalPath
x = path[-1][0]
y = path[-1][1]
if x == 5 and y == 4:
showPath(path)
if len(finalPath) == 0 or len(finalPath) > len (path):
finalPath[:] = copy.deepcopy(path)
return
if x > 0 and matrix[x-1][y] > matrix[x][y]:
# we can move up
newPath = copy.deepcopy(path)
newPath.append([x-1, y])
findPath(newPath)
if x < 5 and matrix[x+1][y] > matrix[x][y]:
# we can move down
newPath = copy.deepcopy(path)
newPath.append([x+1, y])
findPath(newPath)
if y > 0 and matrix[x][y-1] > matrix[x][y]:
# we can move left
newPath = copy.deepcopy(path)
newPath.append([x, y-1])
findPath(newPath)
if y < 4 and matrix[x][y+1] > matrix[x][y]:
# we can move right
newPath = copy.deepcopy(path)
newPath.append([x, y+1])
findPath(newPath)
path = []
path.append([0, 0])
finalPath = []
findPath(path)
print "Shortest Path: " + str(len(finalPath)) + " steps.\n"
showPath(finalPath)
If you uncomment the first showPath() call in findPath() you can see every step and see where the dead ends get abandoned. If you only show paths that reach the exit, the output looks like:
(0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (5, 0), (5, 1), (5, 2), (5, 3), (5, 4),
(0, 0), (1, 0), (1, 1), (1, 2), (2, 2), (3, 2), (3, 1), (3, 0), (4, 0), (5, 0), (5, 1), (5, 2), (5, 3), (5, 4),
(0, 0), (0, 1), (1, 1), (1, 2), (2, 2), (3, 2), (3, 1), (3, 0), (4, 0), (5, 0), (5, 1), (5, 2), (5, 3), (5, 4),
(0, 0), (0, 1), (0, 2), (1, 2), (2, 2), (3, 2), (3, 1), (3, 0), (4, 0), (5, 0), (5, 1), (5, 2), (5, 3), (5, 4),
Shortest Path: 10 steps.
(0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (5, 0), (5, 1), (5, 2), (5, 3), (5, 4),
here you can build a tree to all possibilities and take then the shortest. There is a loop inside for tracing the result, but you can also get around that with some ugly ifs...
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
public class BetterPathfinder {
public class Comperator implements Comparator<Path> {
#Override
public int compare(Path o1, Path o2) {
return o1.getValue().compareTo(o2.getValue());
}
}
public class Path {
private Integer lenght;
TreeMap<Integer, String> trace = new TreeMap<>();
public Path(int lenght) {
this.lenght = lenght;
}
public Path(Path find, int i, int j) {
this.lenght = find.getValue() + 1;
this.trace.putAll(find.getTrace());
this.trace.put(lenght, "(" + i + "," + j + ")");
}
private Map<Integer, String> getTrace() {
return trace;
}
public Integer getValue() {
return lenght;
}
#Override
public String toString() {
String res = "end";
for (Entry<Integer, String> is : trace.entrySet()) {
res = is.getValue() + "->" + res;
}
return res;
}
}
private int[][] matrix;
private int matrixLenghtI;
private int matrixLenghtJ;
public BetterPathfinder(int[][] matrix, int matrixLenghtI, int matrixLenghtJ) {
this.matrix = matrix;
this.matrixLenghtI = matrixLenghtI;
this.matrixLenghtJ = matrixLenghtJ;
}
public static void main(String[] args) {
int matrixLenghtI = 6;
int matrixLenghtJ = 5;
int[][] matrix1 = { { 3, 13, 15, 28, 30 }, { 40, 51, 52, 29, 30 }, { 28, 10, 53, 54, 54 },
{ 53, 12, 55, 53, 60 }, { 70, 62, 56, 20, 80 }, { 80, 81, 90, 95, 100 } };
int[][] matrix2 = { { 5, 13, 2, 5, 2 }, { 58, 24, 32, 3, 24 }, { 2, 7, 33, 1, 7 }, { 45, 40, 37, 24, 70 },
{ 47, 34, 12, 25, 2 }, { 52, 56, 68, 76, 100 } };
BetterPathfinder finder1 = new BetterPathfinder(matrix1, matrixLenghtI, matrixLenghtJ);
finder1.run();
BetterPathfinder finder2 = new BetterPathfinder(matrix2, matrixLenghtI, matrixLenghtJ);
finder2.run();
}
private void run() {
int i = 0;
int j = 0;
System.out.println(new Path(find(i, j), i, j));
}
private Path find(int i, int j) {
int value = matrix[i][j];
int[] next = { i, j };
ArrayList<Path> test = new ArrayList<>();
if (i == matrixLenghtI - 1 && j == matrixLenghtJ - 1)
return new Path(1);
if (i > 0 && matrix[i - 1][j] > value) {
next[0] = i - 1;
next[1] = j;
test.add(new Path(find(next[0], next[1]), next[0], next[1]));
}
if (j > 0 && matrix[i][j - 1] > value) {
next[0] = i;
next[1] = j - 1;
test.add(new Path(find(next[0], next[1]), next[0], next[1]));
}
if (i < matrixLenghtI - 1 && matrix[i + 1][j] > value) {
next[0] = i + 1;
next[1] = j;
test.add(new Path(find(next[0], next[1]), next[0], next[1]));
}
if (j < matrixLenghtJ - 1 && matrix[i][j + 1] > value) {
next[0] = i;
next[1] = j + 1;
test.add(new Path(find(next[0], next[1]), next[0], next[1]));
}
if (test.isEmpty()) {
return new Path(100);
}
return Collections.min(test, new Comperator());
}
}
result:
(0,0)->(1,0)->(1,1)->(1,2)->(2,2)->(3,2)->(4,2)->(5,2)->(5,3)->(5,4)->end
(0,0)->(0,1)->(1,1)->(1,2)->(2,2)->(3,2)->(3,1)->(3,0)->(4,0)->(5,0)->(5,1)->(5,2)->(5,3)->(5,4)->end
You want a recursive strategy. A pretty easy, though expensive method, is to simply flood the board. Something like "Try every possible path and compute the distance".
You can do this recursively by imagining moving a pebble around.
public int shortestPath(Point src, Point dest) {
if (src.equals(dest)) {
return 0;
}
// You need to do some bound checks here
int left = shortestPath(new Point(src.x - 1, src.y), dest);
int right = shortestPath(new Point(src.x + 1, src.y), dest);
int up = shortestPath(new Point(src.x, src.y + 1), dest);
int down = shortestPath(new Point(src.x, src.y - 1), dest);
// Decide for the direction that has the shortest path
return min(left, right, up, down) + 1;
}
If you are interested in the path represented by the solution, you may trace the path while creating. For this you simply need to save for which direction min decided.
I needed to solve a similar task ages ago in my computer science studies. We needed to compute the shortest amount of moves a knight on a chess board needs for reaching a given destination. Maybe this also helps you: http://pastebin.com/0xwMcQgj
public class shortestPath{
public static int shortestPath(int[][] mat){
if(mat == null || mat.length == 0 || mat[0].length == 0)
return 0;
else {
int n = shortestPath(mat, 0, 0, 0);
return (n == mat.length*mat.length+1 ) ? 0 : n;
}
}
private static int shortestPath(int[][]mat, int row, int col,int prev){
if (!valid(mat,row,col) || !(mat[row][col] > prev)){
return mat.length*mat.length+1;
} else if(row == mat.length - 1 && col == mat[row].length - 1) {
return 1;
} else {
return minimum(shortestPath(mat,row-1,col, mat[row][col]),
shortestPath(mat,row+1,col, mat[row][col]),
shortestPath(mat,row,col-1, mat[row][col]),
shortestPath(mat,row,col+1, mat[row][col])) + 1;
}
}
private static boolean valid(int[][]mat,int row, int col){
if(row < 0 || col < 0 || col > mat[0].length-1 || row > mat.length-1)
return false;
else
return true;
}
private static int minimum(int x, int y, int t, int z){
int min1 = (x > y)? y : x;
int min2 = (t > z)? z : t;
return (min1 > min2)? min2 : min1;
}
public static void main(String[] args){
int maze[][] = {
{ 3, 13, 15, 28, 30},
{ 40, 51, 52, 29, 30},
{ 28, 10, 53, 54, 53},
{ 53, 12, 55, 53, 60},
{ 70, 62, 56, 20, 80},
{ 81, 81, 90, 95, 100}};
System.out.println(shortestPath(maze));
}
}
Here is how I solved it, note that in your example we should get 16
public static void main(String[] args)
{
int[][] mat =
{
{ 3, 13, 15, 28, 30 },
{ 40, 51, 52, 29, 30 },
{ 28, 10, 53, 54, 53 },
{ 53, 12, 55, 53, 60 },
{ 70, 62, 56, 20, 80 },
{ 80, 81, 90, 95, 100 }
};
System.out.println(shortestPath(mat)); // 10
int[][] mat1 =
{
{0, 1, 2, 3, 4 },
{0, 5, 13, 2, 5, 2},
{1, 58, 24, 32, 3, 24} ,
{2, 2 , 7, 33, 1, 7} ,
{3, 45, 40, 37, 24, 70},
{4, 47, 34, 12, 25, 2},
{5, 52, 56, 68, 76, 100}
};
System.out.println(shortestPath(mat1)); // 16
}
public static int shortestPath(int[][] mat)
{
return shortestPath(mat, 0, 0, mat[0][0] - 1, 0);
}
private static int shortestPath(int[][] mat, int row, int col, int prev, int counter)
{
if (row < 0 || row == mat.length || col < 0 || col == mat[row].length) // boundaries
return Integer.MAX_VALUE;
if (mat[row][col] <= prev || mat[row][col] == -999) // if the sequence is not ascending or if we have been in this cell before
return Integer.MAX_VALUE;
if (row == mat.length - 1 && col == mat[row].length - 1)
return counter + 1;
int temp = mat[row][col];
mat[row][col] = -999;
int up = shortestPath(mat, row - 1, col, temp, counter + 1); // go up and count
int down = shortestPath(mat, row + 1, col, temp, counter + 1);
int left = shortestPath(mat, row, col - 1, temp, counter + 1);
int right = shortestPath(mat, row, col + 1, temp, counter + 1);
mat[row][col] = temp;
return Math.min(Math.min(up, down), Math.min(left, right)); // get the min
}
I am coding Zombies infect people in a city whereas:
2: There is no person
1: Uninfected people
0: Zombies
Zombies will infect all normal people that are around Zombies.
Below is my Java program but I am getting the error: StackOverflowError.
public class InfectGame {
/**
* #param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
int a[][] = { { 1, 1, 1, 1, 2, 2, 2, 1, 1, 0 },
{ 1, 1, 1, 1, 1, 0, 1, 1, 1, 1 },
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
{ 1, 1, 2, 1, 1, 2, 1, 1, 1, 1 },
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
{ 1, 1, 1, 1, 0, 0, 1, 1, 1, 1 },
{ 1, 1, 1, 1, 1, 0, 1, 1, 1, 1 },
{ 1, 1, 1, 1, 0, 1, 1, 1, 2, 1 },
{ 2, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
{ 2, 2, 2, 1, 1, 1, 1, 1, 1, 2 }, };
int i = 0;
int j = 0;
for (i = 0; i < 10; i++) {
for (j = 0; j < 10; j++) {
if (a[i][j] == 0) {
run_test(i, j, a, 0, 10);
}
}
}
i = 0;
j = 0;
for (i = 0; i < 10; i++) {
System.out.print("\n");
for (j = 0; j < 10; j++) {
System.out.print(a[i][j] + " ");
}
}
}
public static void run_test(int x, int y, int a[][], int v, int size) {
if ((x < 0) || (x >= size))
return;
if ((y < 0) || (y >= size))
return;
// System.out.print(a[x][y] + " ");
// a[x][y] = v;
if (a[x][y] != 2) {
a[x][y] = v;
if (x + 1 < size) {
run_test(x + 1, y, a, v, size);
}
if (x > 0) {
run_test(x - 1, y, a, v, size);
}
if (y + 1 < size) {
run_test(x, y + 1, a, v, size);
}
if (y > 0) {
run_test(x, y - 1, a, v, size);
}
}
}
}
Exception in thread "main" java.lang.StackOverflowError
at InfectGame.run_test(InfectGame.java:55)
at InfectGame.run_test(InfectGame.java:58)
at InfectGame.run_test(InfectGame.java:55)
at InfectGame.run_test(InfectGame.java:58)
at InfectGame.run_test(InfectGame.java:55)
at InfectGame.run_test(InfectGame.java:58)
at InfectGame.run_test(InfectGame.java:55)
at InfectGame.run_test(InfectGame.java:58)
at InfectGame.run_test(InfectGame.java:55)
at InfectGame.run_test(InfectGame.java:58)
at InfectGame.run_test(InfectGame.java:55)
at InfectGame.run_test(InfectGame.java:58)
at InfectGame.run_test(InfectGame.java:55)
at InfectGame.run_test(InfectGame.java:58)
at InfectGame.run_test(InfectGame.java:55)
at InfectGame.run_test(InfectGame.java:58)
at InfectGame.run_test(InfectGame.java:55)
at InfectGame.run_test(InfectGame.java:58)
at InfectGame.run_test(InfectGame.java:55)
at InfectGame.run_test(InfectGame.java:58)
at InfectGame.run_test(InfectGame.java:55)
at InfectGame.run_test(InfectGame.java:58)
........................................................
Recursion only works if you have terminating conditions.
You don't have them. Look at this part of your code:
if (x + 1 < size) {
run_test(x + 1, y, a, v, size);
}
if (x > 0) {
run_test(x - 1, y, a, v, size);
}
The first if statement will recurse until you've hit the end of the grid on the current line. At that point, it will go to the second if statement and recurse again, but one step back to the left. But on the next recursion, it will go one step to the right again, ad infinitum.
That's why you get the error - you never terminate your recursion.
Now that you explained that you are trying to do a flood fill, I can see which terminating condition you need:
if (a[x][y] != 2) {
Should be:
if (a[x][y] != 2 && a[x][y] != v) {
Otherwise you keep filling the same squares with zombies over and over again.
But thinking more about your problem of zombies, it is a bit different from an ordinary fill. In a fill, if a pixel already has the colour your want, you can stop the recursion.
For zombies that is not true for the first zombie - from this zombie it spreads out. So the first iteration should not check whether it is already a zombie, but any further iteration of the recursion should check, or else it doesn't terminate.
You can do it like this:
public static void run_test(int x, int y, int a[][], int v, int size, boolean first) {
if ((x < 0) || (x >= size))
return;
if ((y < 0) || (y >= size))
return;
if (a[x][y] != 2 && (first || a[x][y] != v)) {
a[x][y] = v;
if (x + 1 < size) {
run_test(x + 1, y, a, v, size, false);
}
if (x > 0) {
run_test(x - 1, y, a, v, size, false);
}
if (y + 1 < size) {
run_test(x, y + 1, a, v, size, false);
}
if (y > 0) {
run_test(x, y - 1, a, v, size, false);
}
}
}
And the invocation from the main method should read:
run_test(i, j, a, 0, 10, true);
You stuck here:
if (x + 1 < size) {
run_test(x + 1, y, a, v, size);
}
if (x > 0) {
run_test(x - 1, y, a, v, size);
}
Because x is always the first or second if- condition.
You should use better variable names. Use something that describes the variable instead of x, y, z.