I created a recursive DFS algorithm to generate/solve sudoku boards in Java, but it's taking forever to terminate, and an explanation/optimization would be welcome. I can't imagine that generating a sudoku board would be so time-consuming, especially with all the apps around (although they might have a database.)
Basically, I traverse all cells, seeing whether any of [1-9] would satisfy the sudoku constraints, and backtrack on dead-end branches. To conserve memory and avoid copying the 2D array that serves as the board with each invocation of the recursive method (and there are potentially 81*9! leaves in that tree, if I'm not mistaken...), I created a 2D matrix of integer stacks, in which an element is pushed every time a branch is explored, and popped if it's a dead-end.
Below is the implementation. Any advice on speedup would be welcome. I'm doing this as a personal excercise, and I'm wondering if something asymptotically better exists.
Hope it's not a terrible read below.. Thank you!
1) The algorithm implementation: (note that values are in a "jumbled" array of [1-9] to create unique boards.)
/**
* Provides one solution to a board with an initial configuration, or <code>null</code> if there is none.
* The search is randomized, s.t. the algorithm can serve to populate an empty board.
*
* #param initial The initial board given to solve.
* #return The fully solved board, or null if no solution found.
*/
public static int[][] solveBoard (int[][] initial){
return solveBoard(new StackedBoard(initial), 0, 0);
}
private static int[][] solveBoard (StackedBoard board, int xPos, int yPos){
// base case - success
int remaining = 81;
for (int x = 0; x < 9; x++){
for (int y = 0; y < 9; y++){
if (board.peekAt(x, y) != Board.EMPTY){
remaining--;
}
}
}
if (remaining == 0){
return board.flatView();// the only creation of an array.
}
// recursive case
for (int x = 0; x < 9; x++){
for (int y = 0; y < 9; y++){
if (board.peekAt(x, y) == Board.EMPTY){
for (int val : getJumbledRandomVals()){
if (isMoveLegal(board, x, y, val)){
board.pushAt(x, y, val);
int[][] leafBoard = solveBoard(board, x, y);
if (leafBoard != null) {
return leafBoard;
}
}
}
}
}
}
// base case - dead branch
board.popAt(xPos, yPos);
return null;
}
2) The StackedBoard implementation:
/**
* Represents square boards with stacked int elements.
*/
class StackedBoard {
ArrayList<ArrayList<Stack<Integer>>> xaxis = new ArrayList<ArrayList<Stack<Integer>>>();
/**
*
* #param init A square array - both dimensions of equal length, or <code>null</code> if no initialization.
*/
public StackedBoard (int[][] init) {
for (int i = 0; i < 9; i++){
ArrayList<Stack<Integer>> yaxis = new ArrayList<Stack<Integer>>();
xaxis.add(yaxis);
for (int j = 0; j < 9; j++){
Stack<Integer> stack = new Stack<Integer>();
yaxis.add(stack);
}
}
if (init != null){
for (int x = 0; x < init.length; x++){
for (int y = 0; y < init.length; y++){
getStackAt(x, y).push(init[x][y]);
}
}
}
}
public Stack<Integer> getStackAt (int x, int y){
return xaxis.get(x).get(y);
}
public int peekAt (int x, int y){
return getStackAt(x, y).peek();
}
public void pushAt (int x, int y, int value){
getStackAt(x, y).push(value);
}
public Integer popAt (int x, int y){
try {
return getStackAt(x, y).pop();
} catch (EmptyStackException e){
// shhhhh!
return Board.EMPTY;
}
}
/**
* Flat view of the stacked-board; peek of the top elements.
*/
public int[][] flatView (){
int[][] view = new int[xaxis.size()][xaxis.size()];
for (int x = 0; x < xaxis.size(); x++){
for (int y = 0; y < xaxis.size(); y++){
view[x][y] = getStackAt(x, y).peek();
}
}
return view;
}
}
3) The constraints function implementation:
/**
* Is the move legal on the suggested board?
*
* #param board The board.
* #param x The X coordinate, starts with 0.
* #param y The Y coordinate, starts with 0.
* #param value The value.
* #return <code>true</code> iff the move is legal.
*/
private static boolean isMoveLegal (StackedBoard board, int x, int y, int value){
// by value
if (1 > value || value > 9){
return false;
}
// by column
for (int i = 0; i < 9; i++){
if (board.peekAt(i, y) == value){
return false;
}
}
// by row
for (int i = 0; i < 9; i++){
if (board.peekAt(x, i) == value){
return false;
}
}
// by lil square
int lowerX = x < 3 ? 0 : (x < 6 ? 3 : 6);
int upperX = lowerX + 2;
int lowerY = y < 3 ? 0 : (y < 6 ? 3 : 6);
int upperY = lowerY + 2;
for (int i = lowerX; i <= upperX; i++){
for (int j = lowerY; j <= upperY; j++){
if (board.peekAt(i, j) == value){
return false;
}
}
}
return true;
}
If you are willing to make a complete left turn, there are much better algorithms for generating / solving Sudokus. Don Knuth's dancing links algorithm is known to be extremely good at rapidly enumerating all Sudoku solutions (once they're phrased as instances of the exact cover problem) and is commonly used as the main algorithm in Sudoku solvers, and it's worth looking into. It requires a lot of pointer/reference gymnastics, but it relatively short to code up.
If you want to stick with your existing approach, one useful optimization would be to always choose the most constrained cell as the next value to fill in. This will likely cause a cascade of "forced moves" that will help you reduce the size of your search space, though it's only a heuristic.
Hope this helps!
Related
My goal is a "paint fill" function that one might see on many image editing programs. That is, given a screen (represented by a two-dimensional array of colors), a point, and a new color, fill in the surrounding area until the color changes from the original color.
I've implemented it for a 2D array, and here is the code :
public static void paint (int [][] screen,int OldColor,int NewColor,int y,int x)
{
if(y>screen.length-1||y<0||x>screen[0].length||x<0||screen[y][x]!=OldColor)
return;
screen[y][x]=NewColor;
paint(screen,OldColor,NewColor,y-1,x);
paint(screen, OldColor, NewColor, y+1, x);
paint(screen, OldColor, NewColor, y, x-1);
paint(screen, OldColor, NewColor, y, x+1);
}
But I want to implement it for multidimensional arrays like 3D that could be solved by adding:
paint(screen, OldColor, NewColor, y, x,z-1);
paint(screen, OldColor, NewColor, y, x,z+1);
But imagine the array is 100 D... How can I solve this problem?
Thanks to #Spektre's suggestion about the structure of the points, I managed to write a simple N-Dimensional floodfill.
Instead of images, I used char matrix to simplify the coding. Changing it to int as color value and some changes in other matrix's data type, will do the 100D for you :)
In this simple program, I try to fill all "A"'s with "B" and it fill all of connected char values similar to ants nest. You can trace the connections between A's using other layers to see the fill path.
In second image (Im1, add intentionally added a B and then added an A above it which is not accessible from fill point) and it worked fine as well.
package test;
import java.awt.Point;
import java.util.LinkedList;
import java.util.Queue;
/**
*
* #author Pasban
*/
public class NDFloodFill {
public int N1 = 8; // width
public int N2 = 6; // height
public int N = 3; // number of layers
public ImageData[] images = new ImageData[N];
public static void main(String[] args) {
NDFloodFill ndf = new NDFloodFill();
//print original data
//ndf.print();
ndf.fill(0, 0, 0, 'A', 'B');
ndf.print();
}
public NDFloodFill() {
String im0 = ""
+ "AA...A..\n"
+ ".....A..\n"
+ "....AA..\n"
+ "........\n"
+ "........\n"
+ "...AA.AA";
String im1 = ""
+ ".A..A...\n"
+ "....B...\n"
+ "..AAA...\n"
+ "........\n"
+ "...AA.A.\n"
+ "..AA..A.";
String im2 = ""
+ ".A......\n"
+ ".AA.....\n"
+ "..A.....\n"
+ "..A.....\n"
+ "..A.AAA.\n"
+ "..A.....";
images[0] = new ImageData(im0, 0);
images[1] = new ImageData(im1, 1);
images[2] = new ImageData(im2, 2);
}
private void print() {
for (int i = 0; i < N; i++) {
System.out.println(images[i].getImage());
}
}
private void fill(int x, int y, int index, char original, char fill) {
Queue<PixFill> broadCast = new LinkedList<>();
broadCast.add(new PixFill(new Point(x, y), index));
for (int i = 0; i < N; i++) {
images[i].reset();
}
while (!broadCast.isEmpty()) {
PixFill pf = broadCast.remove();
Queue<PixFill> newPoints = images[pf.index].fillArea(pf.xy, original, fill);
if (newPoints != null) {
broadCast.addAll(newPoints);
}
}
}
public class PixFill {
Point xy;
int index;
public PixFill(Point xy, int index) {
this.xy = xy;
this.index = index;
}
#Override
public String toString() {
return this.xy.x + " : " + this.xy.y + " / " + this.index;
}
}
public class ImageData {
char[][] pix = new char[N1][N2];
boolean[][] done = new boolean[N1][N2];
int index;
public ImageData(String image, int index) {
int k = 0;
this.index = index;
for (int y = 0; y < N2; y++) { // row
for (int x = 0; x < N1; x++) { // column
pix[x][y] = image.charAt(k++);
}
k++; // ignoring the \n char
}
}
public void reset() {
for (int y = 0; y < N2; y++) {
for (int x = 0; x < N1; x++) {
done[x][y] = false;
}
}
}
public String getImage() {
String ret = "";
for (int y = 0; y < N2; y++) { // row
String line = "";
for (int x = 0; x < N1; x++) { // column
line += pix[x][y];
}
ret += line + "\n";
}
return ret;
}
public Queue<PixFill> fillArea(Point p, char original, char fill) {
if (!(p.x >= 0 && p.y >= 0 && p.x < N1 && p.y < N2) || !(pix[p.x][p.y] == original)) {
return null;
}
// create queue for efficiency
Queue<Point> list = new LinkedList<>();
list.add(p);
// create broadcasting to spread filled points to othwer layers
Queue<PixFill> broadCast = new LinkedList<>();
while (!list.isEmpty()) {
p = list.remove();
if ((p.x >= 0 && p.y >= 0 && p.x < N1 && p.y < N2) && (pix[p.x][p.y] == original) && (!done[p.x][p.y])) {
//fill
pix[p.x][p.y] = fill;
done[p.x][p.y] = true;
//look for neighbors
list.add(new Point(p.x - 1, p.y));
list.add(new Point(p.x + 1, p.y));
list.add(new Point(p.x, p.y - 1));
list.add(new Point(p.x, p.y + 1));
// there will not be a duplicate pixFill as we always add the filled points that are not filled yet,
// so duplicate fill will never happen, so do pixFill :)
// add one for upper layer
if (index < N - 1) {
broadCast.add(new PixFill(p, index + 1));
}
// add one for lower layer
if (index > 0) {
broadCast.add(new PixFill(p, index - 1));
}
//layers out of range <0, N> can be filtered
}
}
return broadCast;
}
}
}
Avoid recursive functions! Use a queue instead to flood fill the image ith.
On which image you want to start filling?
check the image color on ith image and add that point to your list.
Later on check if you can go up or down from the stored point to (i+1)th or (i-1)th image and repeat this process from there.
This is a raw idea, but all you may need is this.
Plus, you need to have an array for each level to check if you have filled that pixel for that image or not. So you will escape from infinite loop :)
Check this for flood fill using queue:
Flood Fill Optimization: Attempting to Using a Queue
Salivan is right with his suggestions but he did not grasp the real problem you are asking about. For arbitrary dimensionality you need to change the point structure from notation like pnt.x,pnt.y,pnt.z to pnt[0],pnt[1],pnt[2] then there are few approaches how to handle this:
fixed limit size padded with zeros
so handle all like 10D (if 10D is maximal dimensionality used) and fill unused axises with zeros. This is slow ugly,painfully demanding on memory and limiting max dimensionality.
use nested for loop (for initializations and more)
look here: rasterize and fill a hypersphere
many multidimensional operations require nested loops this one has arbitrary depth. You can look at it as an increment function of multi digit number where each digit represents axis in your space.
use normal for loop for neighbors generation in N-D
// point variables
int p[N],q[N];
// here you have actual point p and want to find its neighbors
for (int i=0;i<N;i++)
{
for (int j=0;i<N;i++) q[j]=p[j]; // copy point
q[i]--;
// add q to flood fill
q[i]+=2;
// add q to flood fill
}
Here's the DisjointSet class i use :
public class DisjointSet{
public DisjointSet(int size){
s = new int[size];
for(int i = 0; i < size; ++i){
s[i] = -1;
}
}
public void union(int el1, int el2){
int root1 = find(el1);
int root2 = find(el2);
if(root1 == root2){
return;
}
if(s[root2] < s[root1]){
s[root1] = root2;
}
else{
if(s[root1] == s[root2]){
--s[root1];
}
s[root2] = root1;
}
}
public int find(int x){
if(s[x] < 0){
return x;
}
else{
s[x] = find(s[x]);
return s[x];
}
}
private int[] s;
}
And here's my Maze class :
public class Maze
{
public Vector<Wall> maze;
public Vector<Room> graph;
public Vector<Integer> path;
public int LASTROOM;
public int height;
public int width;
public Random generator;
public DisjointSet ds;
public int dsSize;
public Maze(int w, int h, int seed)
{
width = w;
height = h;
dsSize = width * height;
LASTROOM = dsSize-1;
// Maze initialization with all walls
maze = new Vector<Wall>();
for(int i = 0; i < height; ++i)
{
for(int j = 0; j < width; ++j)
{
if(i > 0)
maze.add(new Wall(j+i*height, j+(i-1)*height));
if(j > 0)
maze.add(new Wall(j+i*height, j-1+i*height));
}
}
// Creation of the graph topology of the maze
graph = new Vector<Room>();
for(int i = 0; i < dsSize; ++i)
graph.add(new Room(i));
// Sort the walls of random way
generator = new Random(seed);
for(int i = 0; i < maze.size(); ++i)
{
//TODO
}
// Initialization of related structures
ds = new DisjointSet(dsSize);
path = new Vector<Integer>();
}
public void generate()
{
//TODO
}
public void solve()
{
//TODO
}
}
I've been looking for a way to implement generate() and solve() along with the random sorting of the maze's walls for a long time now, and I can't seem to find any algorithm or implementation on the Internet to do this.
generate() should go through the permuted walls in the maze variable and destroy it if the two parts (rooms) connected by the wall are not already in the same set. The method should also add an edge in the room graph (each room has a list of adjacency named paths and the Room class has a variable id that identifies each graph's vertices).
solve() should solve the maze path and generate the vector of the Maze class containing the order of the rooms to go through to get to the exit. The first room is positionned at 0 and the last room is positionned at LASTROOM.
Note: Maze and Room constructors are as follow:
public Wall(int r1, int r2)
{
room1 = r1;
room2 = r2;
}
public Room(int i)
{
id = i;
distance = -1;
paths = new Vector<Integer>();
}
If someone would be kind enough to suggest an implementation that would work in Java, I would greatly appreciate it, thank you.
First off, I really like the idea of mazes and have been working on a similar project in Java with generating Torus Mazes.
To generate your maze, you need to look at this key sentence:
... each room has a list of adjacency named paths and the Room class has a variable id that identifies each graph's vertices
What does this tell us? It tells us the data structure we need! When you are dealing with problems such as this; adjacent vectors connected by edges, you are undoubtedly going to be creating an adjacency list.
There are a few different ways that you can go about doing this, but by far the easiest (and arguably most efficient in this case), is to create an array of linked lists. This is an adjacency list that I created without using built-in structures from the Java libraries, but the same logic can be used if you choose to use the LinkedList<> built-in.
/*
* The Node class creates individual elements that populate the
* List class. Contains indexes of the node's neighbors and their
* respective edge weights
*/
class Node {
public int top;
public int topWeight;
public int bottom;
public int bottomWeight;
public int left;
public int leftWeight;
public int right;
public int rightWeight;
public int numConnec;
// Default constructor, ititializes neghbors to -1 by default and edge
// weights to 0
Node () {
top = -1;
right = -1;
bottom = -1;
left = -1;
}
} // End Node class
/*
* The List class contains Nodes, which are linked to one another
* to create a Linked List. Used as an adjacency list in the
* UnionFind class
*/
class List {
public Node neighbors;
// Default constructor
List () {
neighbors = new Node ();
}
/**
* Generates valid edges for the node, also assigns a randomly generated weight to that edge
* #param i The row that the node exists on, used to generate outer-node edges
* #param j The index of the node in the maze from 0 to (2^p)^2 - 1
* #param twoP Represents the dimensions of the maze, used in calculating valid edges
* #param choice Randomly generated number to choose which edge to generate
* #param weight Randomly generated number to assign generated edge a weight
* #return If the assignment was done correctly, returns true. Else returns false.
*/
public boolean validEdges (int i, int j, int twoP, int choice, int weight) {
if (neighbors.numConnec < 4) {
// Top
if (choice == 0) {
neighbors.top = generateTop(i, j, twoP);
neighbors.topWeight = weight;
neighbors.numConnec++;
}
// Right
else if (choice == 1) {
neighbors.right = generateRight(i, j, twoP);
neighbors.rightWeight = weight;
neighbors.numConnec++;
}
// Bottom
else if (choice == 2) {
neighbors.bottom = generateBottom(i, j, twoP);
neighbors.bottomWeight = weight;
neighbors.numConnec++;
}
// Left
else if (choice == 3) {
neighbors.left = generateLeft(i, j, twoP);
neighbors.leftWeight = weight;
neighbors.numConnec++;
}
}
else {
return false;
}
return true;
}
public int generateTop (int i, int j, int twoP) {
int neighbor = 0;
// Set the top neighbor
if (i == 0) {
neighbor = j + twoP * (twoP + (-1));
}
else {
neighbor = j + (-twoP);
}
return neighbor;
}
public int generateRight (int i, int j, int twoP) {
int neighbor = 0;
// Set the right neighbor
if (j == twoP * (i + 1) + (-1)) {
neighbor = twoP * i;
}
else {
neighbor = j + 1;
}
return neighbor;
}
public int generateBottom (int i, int j, int twoP) {
int neighbor = 0;
// Set the bottom neighbor
if (i == twoP + (-1)) {
neighbor = j - twoP * (twoP + (-1));
}
else {
neighbor = j + twoP;
}
return neighbor;
}
public int generateLeft (int i, int j, int twoP) {
int neighbor = 0;
// Set the left neighbor
if (j == twoP * i) {
neighbor = twoP * (i + 1) + (-1);
}
else {
neighbor = j + (-1);
}
return neighbor;
}
} // End List class
To solve the maze, this sounds like a problem that an implementation of Dijkstra's Algorithm could tackle.
Dijkstra's works by starting at your first node to create the known set. You then identify the shortest path to the next edge and add that node to the known set. Each time you do look for the next shortest path, you add the distance traveled from the first node.
This process continues until all nodes are in the known set and the shortest path to your goal has been calculated.
Shortest Paths
I have an ArrayList with a lot of points (so x,y).
I sorted the points on XY.
Is there a datatype/algorithm to get all y points a certain x fast? (or the other way around, all x on a certain y).
At the moment I have something like this, which works ok but I have the feeling it's to complex for what I need.
int lastXPos;
int lastIndex;
int[] workArray = new int[4096];
int workArrayIndex;
// returns all the y values where x matches
public int[] grab(int xPos) {
workArrayIndex = 0;
int startIndex = 0;
// this can increase speed a lot
if (lastXPos+1 == xPos) {
startIndex = lastIndex+1;
}
PVector v;
for (int i = startIndex; i < vecs.size(); i++) {
v = vecs.get(i);
if (v.x > xPos) {
lastIndex = i-1;
break;
}
if (v.x == xPos) {
workArray[workArrayIndex++] = (int) v.y;
}
}
lastXPos = xPos;
int[] result = new int[workArrayIndex];
for (int i = 0; i < workArrayIndex; i++) {
result[i] = workArray[i];
}
return result;
}
Edit:
One more thing, it has to process a new list 60 times a second, so creating the data object also has to be fast.
Then as a bonus question, we have rows and columns, is there a way to describe both of those? (direction for example).
I am trying to implement a backtracking algorithm in Java, to solve a sudoku problem.
I'm 95% sure the problem is in solve method, but I included the two accesory methods in case.
Some of the strange-ish things I'm doing are just due to requirements/convenience, like the hard-coded initial values for the puzzle. I'm sure the issue lies near the bottom of my solve method, but I cannot figure it out...
My current problem is this: after working on the first row, and finding a potentially valid permutation of values, my program simply gives up. If I uncomment the line that prints "ROW IS DONE," it'll print that after ONE row, and no more output is given. Why is it giving up after the first row? Is there anything else about my implementation I should be worried about
EDIT: I made a lot of changes. It is getting very close. If I print when EXHAUST is true, I get a puzzle that has every row solved except the last one. It looks like it is undoing everything after it's solved/nearly solved it. I get the feeling that it might already reach point where the puzzle is fully solved, but I'm not passing back TRUE at the right time... What am I doing wrong now?
import java.util.ArrayList;
class Model
{
ArrayList<View> views = new ArrayList<View>();
int[][] grid =
{
{5,3,0,0,7,0,0,0,0},
{6,0,0,1,9,5,0,0,0},
{0,9,8,0,0,0,0,6,0},
{8,0,0,0,6,0,0,0,3},
{4,0,0,8,0,3,0,0,1},
{7,0,0,0,2,0,0,0,6},
{0,6,0,0,0,0,2,8,0},
{0,0,0,4,1,9,0,0,5},
{0,0,0,0,8,0,0,7,9}
};
/**
* Method solve
*
* Uses a backtracking algorithm to solve the puzzle.
*/
public boolean solve(int row, int col) //mutator
{
if(exhaust(row,col)) {printGrid(); return true;}
int rownext = row;
int colnext = col+1;
if(colnext>8)
{
colnext = 0;
rownext++;
}
if(grid[row][col] != 0) solve(rownext,colnext);
else //is == 0
{
for(int num = 1; num <= 9; num++)
{
if(!conflict(row,col,num)) //try a non-conflicting number
{
grid[row][col] = num;
if(solve(rownext,colnext)) return true;
grid[row][col] = 0;
}
}
}
return false;
}
/**
* Method exhaust
*
* Iteratively searches the rest of the puzzle for empty space
* using the parameters as the starting point.
*
* #return true if no 0's are found
* #return false if a 0 is found
*/
public boolean exhaust(int row, int col)
{
for(int i = row; i <= 8; i++)
{
for(int j = col; j <= 8; j++)
{
if(grid[i][j] == 0) return false;
}
}
System.out.printf("Exhausted.\n");
return true;
}
/**
* Method conflict
*
* Checks if the choice in question is valid by looking to see
* if the choice has already been made in the same row or col,
* or block.
*
* #return true if there IS a conflict
* #return false if there is NOT a conflict
*/
public boolean conflict(int row, int col, int num)
{
for(int j = 0; j <= 8; j++)
{
if(grid[row][j] == num) {
return true;
}
}
for(int i = 0; i <= 8; i++)
{
if(grid[i][col] == num) {
return true;
}
}
int rowstart = 0;
if(row>=3) rowstart = 3;
if(row>=6) rowstart = 6;
int colstart = 0;
if(col>=3) colstart = 3;
if(col>=6) colstart = 6;
for(int r = rowstart; r <= (rowstart + 2); r++)
{
for(int c = colstart; c <= (colstart + 2); c++)
{
if(grid[r][c] == num) {
return true;
}
}
}
return false;
}
}
Imagine you're moving forward smoothly, row by row, and haven't backtracked. Your next position is solve(1,1);. Pay attention to rownext as you trace through your code. You should see the problem quickly. If you aren't backtracking, rownext should hold its value of at least 1.
As far as I understand (from answers such as this), java has no native multi-dimensional continuous memory arrays (unlike C#, for example).
While the jagged array syntax (arrays of arrays) might be good for most applications, I would still like to know what's the best practice if you do want the raw efficiency of a continuous-memory array (avoiding unneeded memory reads)
I could of course use a single-dimensional array that maps to a 2D one, but I prefer something more structured.
it's not difficult to do it manually:
int[] matrix = new int[ROWS * COLS];
int x_i_j = matrix[ i*COLS + j ];
now, is it really faster than java's multi dimension array?
int x_i_j = matrix[i][j];
for random access, maybe. for continuous access, probably not - matrix[i] is almost certainly in L1 cache, if not in register cache. in best scenario, matrix[i][j] requires one addition and one memory read; while matrix[i*COLS + j] may cost 2 additions, one multiply, one memory read. but who's counting?
It depends on your access pattern. Using this simple program, comparing an int[][] with a 2D mapped over a 1D int[] array treated as a matrix, a native Java 2D matrix is:
25% faster when the row is on the cache, ie: accessing by rows:
100% slower when the row is not in the cache, ie: accessing by colums:
ie:
// Case #1
for (y = 0; y < h; y++)
for (x = 0; x < w; x++)
// Access item[y][x]
// Case #2
for (x = 0; x < w; x++)
for (y = 0; y < h; y++)
// Access item[y][x]
The 1D matrix is calculated as:
public int get(int x, int y) {
return this.m[y * width + x];
}
Let's say you have a 2D array int[][] a = new int[height][width], so by convention you have the indices a[y][x]. Depending on how you represent the data and how you access them, the performance varies in a factor of 20 :
The code:
public class ObjectArrayPerformance {
public int width;
public int height;
public int m[];
public ObjectArrayPerformance(int w, int h) {
this.width = w;
this.height = h;
this.m = new int[w * h];
}
public int get(int x, int y) {
return this.m[y * width + x];
}
public void set(int x, int y, int value) {
this.m[y * width + x] = value;
}
public static void main (String[] args) {
int w = 1000, h = 2000, passes = 400;
int matrix[][] = new int[h][];
for (int i = 0; i < h; ++i) {
matrix[i] = new int[w];
}
long start;
long duration;
System.out.println("duration[ms]\tmethod");
start = System.currentTimeMillis();
for (int z = 0; z < passes; z++) {
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
matrix[y][x] = matrix[y][x] + 1;
}
}
}
duration = System.currentTimeMillis() - start;
System.out.println(duration+"\t2D array, loop on x then y");
start = System.currentTimeMillis();
for (int z = 0; z < passes; z++) {
for (int x = 0; x < w; x++) {
for (int y = 0; y < h; y++) {
matrix[y][x] = matrix[y][x] + 1;
}
}
}
duration = System.currentTimeMillis() - start;
System.out.println(duration+"\t2D array, loop on y then x");
//
ObjectArrayPerformance mt = new ObjectArrayPerformance(w, h);
start = System.currentTimeMillis();
for (int z = 0; z < passes; z++) {
for (int x = 0; x < w; x++) {
for (int y = 0; y < h; y++) {
mt.set(x, y, mt.get(x, y) + 1);
}
}
}
duration = System.currentTimeMillis() - start;
System.out.println(duration+"\tmapped 1D array, access trough getter/setter");
//
ObjectArrayPerformance mt2 = new ObjectArrayPerformance(w, h);
start = System.currentTimeMillis();
for (int z = 0; z < passes; z++) {
for (int x = 0; x < w; x++) {
for (int y = 0; y < h; y++) {
mt2.m[y * w + x] = mt2.m[y * w + x] + 1;
}
}
}
duration = System.currentTimeMillis() - start;
System.out.println(duration+"\tmapped 1D array, access through computed indexes, loop y then x");
ObjectArrayPerformance mt3 = new ObjectArrayPerformance(w, h);
start = System.currentTimeMillis();
for (int z = 0; z < passes; z++) {
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
mt3.m[y * w + x] = mt3.m[y * w + x] + 1;
}
}
}
duration = System.currentTimeMillis() - start;
System.out.println(duration+"\tmapped 1D array, access through computed indexes, loop x then y");
ObjectArrayPerformance mt4 = new ObjectArrayPerformance(w, h);
start = System.currentTimeMillis();
for (int z = 0; z < passes; z++) {
for (int y = 0; y < h; y++) {
int yIndex = y * w;
for (int x = 0; x < w; x++) {
mt4.m[yIndex + x] = mt4.m[yIndex + x] + 1;
}
}
}
duration = System.currentTimeMillis() - start;
System.out.println(duration+"\tmapped 1D array, access through computed indexes, loop x then y, yIndex optimized");
}
}
We can conclude that linear access performance depends more on the way you process the array (lines then columns or the reverse?: performance gain = x10, much due to CPU caches) than the structure of the array itself (1D vs 2D : performance gain = x2).
If random access, the performance differences should be much lower, because the CPU caches have less effect.
If you really want more structure with a continuous-memory array, wrap it in an object.
public class My2dArray<T> {
int sizeX;
private T[] items;
public My2dArray(int x, int y) {
sizeX = x;
items = new T[x*y];
}
public T elementAt(int x, int y) {
return items[x+y*sizeX];
}
}
Not a perfect solution, and you probably already know it. So consider this confirmation of what you suspected to be true.
Java only provides certain constructs for organizing code, so eventually you'll have to reach for a class or interface. Since this also requires specific operations, you need a class.
The performance impacts include creating a JVM stack frame for each array access, and it would be ideal to avoid such a thing; however, a JVM stack frame is how the JVM implements it's scoping. Code organization requires appropriate scoping, so there's not really a way around that performance hit that I can imagine (without violating the spirit of "everything is an object").
Sample implementation, without a compiler. This is basically what C/C++ do behind the scenes when you access multidimensional arrays. You'll have to further define accessor behaviour when less than the actual dimensions are specified & so on. Overhead will be minimal and could be optimized further, but thats microoptimizing imho. Also, you never actually know what goes on under the hood after JIT kicks in.
class MultiDimentionalArray<T> {
//disclaimer: written within SO editor, might contain errors
private T[] data;
private int[] dimensions; //holds each dimensions' size
public MultiDimensionalArray(int... dims) {
dimensions = Arrays.copyOf(dims, dims.length);
int size = 1;
for(int dim : dims)
size *= dim;
data = new T[size];
}
public T access(int... dims) {
int idx = 1;
for(int i = 0; i < dims.length)
idx += dims[i] * dimensions[i]; //size * offset
return data[idx];
}
}
The most efficient method of implementing multi-dimensional arrays is by utilizing one-dimensional arrays as multi-dimensional arrays. See this answer about mapping a 2D array into a 1D array.
// 2D data structure as 1D array
int[] array = new int[width * height];
// access the array
array[x + y * width] = /*value*/;
I could of course use a single-dimensional array that maps to a 2D one, but I prefer something more structured.
If you want to access array in a more structured manner, create a class for it:
public class ArrayInt {
private final int[] array;
private final int width, height;
public ArrayInt(int width, int height) {
array = new int[width * height];
this.width = width;
this.height = height;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public int get(int x, int y) {
return array[x + y * width];
}
public void set(int x, int y, int value) {
array[x + y * width] = value;
}
}
If you wanted arrays of objects, you could use generics and define class Array<T>, where T is the object stored in the array.
Performance-wise, this will, in most cases, be faster than a multi-dimensional array in Java. The reasons can be found in the answers to this question.
If you cannot live without C constructs, there's always JNI.
Or you could develop your own Java-derived language (and VM and optimizing JIT compiler) that has a syntax for multidimensional continuous-memory arrays.