I have to implement Conway's Game of Life. Everything works as it should and given tests are passing. My only problem is that this method gives complexity error while running PMD rules on my file. I understand that so many if sentences are the cause of that, but while trying to compact them into smaller groups I accidentally broke my code.
Here's what it says:
The method 'getNeighbourCount(int, int)' has a cyclomatic complexity of 21.
The method 'getNeighbourCount(int, int)' has an NPath complexity of 20736, current threshold is 200
What would best the best options for optimizing this method?
public Integer getNeighbourCount(int x, int y) {
// x = column (20), y = row (15)
int countNeigbours = 0;
if (x != 0 && y != 0 && isAlive(x - 1,y - 1)) {
countNeigbours++;
}
if (x != 0 && isAlive(x - 1, y)) {
countNeigbours++;
}
if (x != 0 && y != rows - 1 && isAlive(x - 1,y + 1)) {
countNeigbours++;
}
if (y != 0 && isAlive(x,y - 1)) {
countNeigbours++;
}
// check self
if (y != rows - 1 && isAlive(x,y + 1)) {
countNeigbours++;
}
if (x != columns - 1 && y != 0 && isAlive(x + 1,y - 1)) {
countNeigbours++;
}
if (x != columns - 1 && isAlive(x + 1, y)) {
countNeigbours++;
}
if (x != columns - 1 && y != rows - 1 && isAlive(x + 1,y + 1)) {
countNeigbours++;
}
return countNeigbours;
}
isAlive returns the boolean if the cell is taken (true) or not (false).
Loop over the "delta x" and "delta y" from your current position:
for (int dx : new int[]{-1, 0, 1}) {
if (x + dx < 0 || x + dx >= columns) continue;
for (int dy : new int[]{-1, 0, 1}) {
if (y + dy < 0 || y + dy >= rows) continue;
if (dx == 0 && dy == 0) continue;
if (isAlive(x + dx, y + dy)) countNeighbours++;
}
}
(Of course, you don't have to use arrays and enhanced for loops: you can just do for (int dx = -1; dx <= 1; ++dx), or however else you like)
I don't know if this would provide a speed-up, but you could try having a second array which held the sums, and increased or decreased these values when setting or clearing individual cells. That replaces the many 'isAlive' checks with a check to tell if a cell should be toggled on or off, and reduces the cell adjacency computations to just those cells which were toggled, which should be many fewer than repeating the computation for the entire array. That is, for a mostly empty grid, only a small subset of cells should require recomputation, many fewer than the entire grid.
Also, you could try having whole row isActive, minActive, and maxActive values. That would further reduce the amount of looping, but would increase the complexity and cost of each iteration. The sparseness of active cells would determine whether the extra cost was balanced by the reduction in the number of iterations.
Related
Hey guys I started programming a year ago and recently discovered streams. So I decided to complete my old tasks using streams whenever I could just to get used to them. I know it might not be smart to force using them but it's just practice.
One of my old tasks was to program Minesweeper and right now I try to find a better solution for counting adjacent mines whenever I click a field.
Some details:
I saved a bunch of mines in a Mine[] (arsenal.getArsenal()) and each of the mines has an x and y value. Whenever I click on a field I need to count all mines around the clicked field (from x-1,y-1 till x+1,y+1).
My current solutions are:
private int calculateNearby(int x, int y) {
return (int) Arrays.stream(arsenal.getArsenal())
.filter(mine -> mine.getX() == x + 1 && mine.getY() == y
|| mine.getX() == x && mine.getY() == y + 1
|| mine.getX() == x - 1 && mine.getY() == y
|| mine.getX() == x && mine.getY() == y - 1
|| mine.getX() == x - 1 && mine.getY() == y - 1
|| mine.getX() == x - 1 && mine.getY() == y + 1
|| mine.getX() == x + 1 && mine.getY() == y - 1
|| mine.getX() == x + 1 && mine.getY() == y + 1)
.count();
}
private int calculateNearby(int x, int y) {
return (int) Arrays.stream(arsenal.getArsenal())
.filter(mine ->
{
boolean b = false;
for (int i = -1; i < 2; ++i) {
for (int j = -1; j < 2; ++j) {
if ((x != 0 || y != 0) && mine.getX() == x + i && mine.getY() == y + j) {
b = true;
}
}
}
return b;
})
.count();
}
Both solutions work fine but the first looks "wrong" because of all the cases and the seconds uses for-loops which I basically tried to avoid using.
It would be nice if you could show me a better (ideally shorter) solution using streams. :D
I'm sorry if there's already a thread about this. I really tried to find anything related but there either isn't anything or I searched for the wrong keywords.
You can simplify your stream condition. Instead of checking each case if getX() equals x, x-1 or x+1 you can just check if getX() is greater or equals than x-1 and smaller or equals x+1. The same for getY().
return (int) Arrays.stream(arsenal.getArsenal())
.filter(mine -> mine.getX() >= x - 1 && mine.getX() <= x + 1
&& mine.getY() >= y - 1 && mine.getY() <= y + 1)
.count();
You could also create a method for the check to make the code more readable.
private int calculateNearby(int x, int y) {
return (int) Arrays.stream(arsenal.getArsenal())
.filter(mine -> inRange(mine.getX(), x)
&& inRange(mine.getY(), y))
.count();
}
private boolean inRange(int actual, int target) {
return actual >= target - 1 && actual <= target + 1;
}
Another way is to check if the absolute distance in each direction is less than or equal to 1:
private int calculateNearby(int x, int y) {
return (int) Arrays.stream(arsenal.getArsenal())
.filter(mine -> Math.abs(mine.getX() - x) <= 1 && Math.abs(mine.getY() - y) <= 1)
.count();
}
Note that this also counts a mine which is at the point (x, y) which is not the case with the code in the question.
When is a mine adjacent? When its only one field horizontally or vertically away.
Horizontal distance is Math.abs(mine.getX() - x), vertical distance is Math.abs(mine.getY() - y). It doesn't matter if its -1 or 1, just that it is one field away.
But it shouldn't be more than one field, either vertical or horizontal be away, so max(dx, dy) == 1.
Predicate<Mine> isAdjacent = mine ->
Math.max(
Math.abs(mine.getX() - x)
Math.abs(mine.getY() - y)
) == 1;
return (int) Arrays.stream(arsenal.getArsenal())
.filter(isAdjacent)
.count();
This question already has answers here:
What causes a java.lang.ArrayIndexOutOfBoundsException and how do I prevent it?
(26 answers)
Closed 4 years ago.
I have a 2D array which acts as a game board and the user can make moves into certain cells etc and each user has a specific id e.g. player 1 replaces the cells in the array with their playerId = 1.
I am trying to create a method to find out when the user's playerId gets stuck. They get stuck when they are surrounded by another a value which is not 0.
For example, if player 1 is in the cell 2,5 then P(which represents a 0) is the possible moves they can make. However, if the player was surrounded by other values then they should not be able to make a move (return false).
xxxPPPxxxx
xxxP1Pxxxx
xxxPPPxxxx
xxxxxxxxxx
xxxxxxxxxx
xxxxxxxxxx
Method:
public boolean checkBlocked(int[][] array,List<Coordinates> track,int playerId)
{
boolean blocked = false;
int trueCount = 0;
for (Coordinates cells: track)
{
int x = cells.getX();
int y = cells.getY();
if(array[x-1][y-1] == 0 ) trueCount++; //topleft
if(array[x-1][y] == 0) trueCount++; //top
if(array[x-1][y+1] == 0) trueCount++;//topright
if(array[x][y+1] == 0) trueCount++;//right
if(array[x][y-1] == 0) trueCount++;//left
if(array[x+1][y-1] == 0) trueCount++;//bottomleft
if(array[x+1][y] == 0) trueCount++;//bottom
if(array[x+1][y+1] == 0) trueCount++; //bottomright
}
if (trueCount == 0)
{
blocked = true;
}
return blocked;
}
I tried to do this method, but then this doesn't work because the 2D array is 6x10 and then if the cell is 1,1 or 6,1 then it gives an ArrayIndexOutOfBoundsException.
Is there an easier way that I can do this check or a way around the exception?
Three solutions
Make the array larger in all directions (and make walls that are invisible/impenetrable) -- This has the highest performance and is my favorite trick for image processing.
Make a method like getBoardSquare that does the bounds checking for you and returns a special value (or 'wall' value) for out-of-bound coordinates -- this has the best 'code readability'
Do manual extensive bounds checking wherever you access the array -- not recommended
You have to check that surround cells are in the board. To do so you could add additional checks to all of the if. E.g.:
if(x > 0 && y > 0 && array[x-1][y-1] == 0) trueCount++;
if(x > 0 && array[x-1][y] == 0) trueCount++;
if(x > 0 && y < array[x-1].length - 1 && array[x-1][y+1] == 0) trueCount++;
I think, it is better to create separate method with internal check:
private static int get(int[][] array, int row, int col) {
return row < 0 || row >= arr.length || col < 0 || col >= array[row].length ? 0 : array[row][col];
}
and us it in your if:
if(get(array, x-1, y-1) == 0) trueCount++;
if(get(array, x-1, y) == 0) trueCount++;
if(get(array, x-1, y+1) == 0) trueCount++;
Finally, I think it's better to encapsulate Board logic into separate class:
public class Board {
private final int[][] board;
public Board(int row, int col) {
board = new int[row][col];
}
public boolean isBlocked(int row, int col) {
if (cell(row - 1, col - 1) == 0 || cell(row - 1, col) == 0 || cell(row - 1, col + 1) == 0)
return false;
if (cell(row, col + 1) == 0 || cell(row, col - 1) == 0)
return false;
if (cell(row + 1, col - 1) == 0 || cell(row + 1, col) == 0 || cell(row + 1, col + 1) == 0)
return false;
return true;
}
private int cell(int row, int col) {
return row < 0 || row >= board.length || col < 0 || col >= board[row].length ? 0 : board[row][col];
}
}
P.S.
Check your x and y. x - column and y - row, so it means
arr[y][x] but not arr[x][y]!
I made a Connect4 game recently and my Connect4 doesn't win the game when it's connected diagonally towards the right. And it only works for some combinations when it's connected diagonally to the left. Coordinates:- Top left: (0,0), Bottom left: (5,0), Top right: (0,6), Bottom right: (5,6). The Connect4 board is 6 by 7.
Problem: Connecting diagonally towards left works fine for only some combinations. But, none of the connections connected diagonally towards right work.
/** A method of winning diagonally towards the left side when playing connect 4 two player*/
/** Giving the new method with all the possible possibilities for a user to win diagonally-left */
public static void diagWinningLeft() {
for (int x = 5; x > 2; x--) { // Checks to see if same colored pegs are lining diagonally to the left
for (int y = 6; y > 3; y--) {
if (board[x][y] == 1 && board[x - 1][y - 1] == 1 && board[x - 2][y - 2] == 1 && board[x - 3][y - 3] == 1) {
JOptionPane.showMessageDialog(null, playerNames[0]+" has connected four diagonally-left in a row in " +(countForRed)+ " turns!");
b.drawLine(x,y,x-3,y-3);
}
if (board[x - 1][y - 1] == 1 && board[x - 2][y - 2] == 1 && board[x - 3][y - 3] == 1 && board[x - 4][y - 4] == 1) {
JOptionPane.showMessageDialog(null, playerNames[0]+" has connected four diagonally-left in a row in " +(countForRed)+ " turns!");
b.drawLine(x,y,x-3,y-3);
}
if (board[x][y] == 2 && board[x - 1][y - 1] == 2 && board[x - 2][y - 2] == 2 && board[x - 3][y - 3] == 2) {
JOptionPane.showMessageDialog(null, playerNames[1]+ " has connected four diagonally-left in a row in " +(countForYellow)+ " turns!");
b.drawLine(x,y,x-3,y-3);
}
}
}
}
/** Another method of winning diagonally towards the right side when playing connect 4 two player*/
/** Giving the new method with all the possible possibilities for a user to win diagonally-right*/
public static void diagWinningRight() {
for (int x = 0; x < 2; x++) { // Check to see if same colored pegs are lining diagonally to the right
for (int y = 0; y < 3; y++) {
if (board[x][y] == 1 && board[x + 1][y + 1] == 1 && board[x + 2][y + 2] == 1 && board[x + 3][y + 3] == 1) {
JOptionPane.showMessageDialog(null, playerNames[0]+" has connected four diagonally-right in a row in " +(countForRed)+ " turns!");
}
if (board[x][y] == 2 && board[x + 1][y + 1] == 2 && board[x + 2][y + 2] == 2 && board[x + 3][y + 3] == 2) {
JOptionPane.showMessageDialog(null, playerNames[1]+" has connected four diagonally-right in a row in " +(countForYellow)+ " turns!");
}
}
}
}
Forgive me for not directly answering the question, but this is stuff that will help you fix it and end up with better code and a better ability to write code in future.
Extracting the logic of your "if" condition into a separate method makes it easier to think about that logic on its own, and lets you test it independently of the rest of the program.
So instead of:
if (board[x][y] == 1 && board[x - 1][y - 1] == 1 && board[x - 2][y - 2] == 1 && board[x - 3][y - 3] == 1) {
JOptionPane.showMessageDialog(...)
}
... use:
if(isDiagonalLeft(x,y,1) { ... }
... and ...
boolean isDiagonalLeft(int x, int y, int player) {
return board[x][y] == player &&
board[x - 1][y - 1] == player &&
board[x - 2][y - 2] == player &&
board[x - 3][y - 3] == player
}
Now you can run unit tests on isDiagonalLeft() to make sure it works. That is, a small program that sets up a board and just runs isDiagonalLeft() to make sure it gives the right answer in various circumstances. This feels like extra work, but most people who try it learn that it saves effort by catching bugs early.
What you've done is to somewhat separate the game logic from the presentation code (JOptionPane), so that the presentation code is not in the way when you just want to exercise the game logic. Later on in your programming studies you'll encounter ways to separate these even more, like the Model-View-Controller model.
Pulling the logic out like this also helps if you need to ask questions on Stack Overflow -- by separating the game logic from Swing, you open up the question to potential answerers who don't know anything about Swing.
And, you can re-use this method, once for each player, rather than copying the logic into two places as you have.
If it doesn't work, use the debugger in your IDE to step through it.
Now that you've done this, you can smarten up the method so that the computer does the decrementing instead of the programmer...
boolean isDiagonalLeft(int x, int y, int player) {
for(int i = 0; i<4; i++) {
if(board[x-i][y-i] != player) {
return false;
}
}
return true;
}
...and you can generalise it so that it covers both directions of diagonal:
boolean isDiagonal(int x, int y, int player, boolean direction) {
int dirUnit = direction ? -1 : 1;
for(int i = 0; i<4; i++) {
if(board[x-i][y + dirUnit] != player) {
return false;
}
}
return true;
}
... so now you can re-use the method in 4 places; for each player and for each direction.
When you come across a situation where it doesn't work in your GUI, make a unit tests that sets up the board the way it is in the GUI, and runs isDiagonal() on it. If the test passes, you know the problem is somewhere else. If the test fails, you can work with a debugger and the method's code, to make it pass.
Following code should work:
//Winning diagonally towards left
public static void diagWinningLeft() {
for (int x = 5; x > 2; x--) { // Checks to see if same colored pegs are lining diagonally to the left
for (int y = 6; y > 2; y--) {
if (board[x][y] == 1 && board[x - 1][y - 1] == 1 && board[x - 2][y - 2] == 1 && board[x - 3][y - 3] == 1) {
JOptionPane.showMessageDialog(null, playerNames[0]+" has connected four diagonally-left in a row in " +(countForRed)+ " turns!");
b.drawLine(x,y,x-3,y-3);
}
if (board[x][y] == 2 && board[x - 1][y - 1] == 2 && board[x - 2][y - 2] == 2 && board[x - 3][y - 3] == 2) {
JOptionPane.showMessageDialog(null, playerNames[1]+ " has connected four diagonally-left in a row in " +(countForYellow)+ " turns!");
b.drawLine(x,y,x-3,y-3);
}
}
}
}
//Winning diagonally towards right
public static void diagWinningRight() {
for (int x = 5; x > 2; x--) { // Check to see if same colored pegs are lining diagonally to the right
for (int y = 0; y < 4; y++) {
if (board[x][y] == 1 && board[x - 1][y + 1] == 1 && board[x - 2][y + 2] == 1 && board[x - 3][y + 3] == 1) {
JOptionPane.showMessageDialog(null, playerNames[0]+" has connected four diagonally-right in a row in " +(countForRed)+ " turns!");
}
if (board[x][y] == 2 && board[x - 1][y + 1] == 2 && board[x - 2][y + 2] == 2 && board[x - 3][y + 3] == 2) {
JOptionPane.showMessageDialog(null, playerNames[1]+" has connected four diagonally-right in a row in " +(countForYellow)+ " turns!");
}
}
}
}
The problem with your code was that while checking towards right your coordinates(indices) continued to check for left direction(as there are 4 diagonals from a[x][y] viz. a[x-1][y-1], a[x+1][y+1] constituting diagonal in NW-SE direction(or left diagonal) and a[x-1][y+1], a[x+1][y-1] constituting diagonal in NE-SW direction(or right diagonal)).
Moreover your inner for loop didn't run to the limits.Hence, diagWinningLeft() didn't work at times..
I am writing software that detects an images outline, thins it to a "single pixel" thick, then performs operations on the resulting outline. My hope is to eventually get the following:
I have written software that detects the RGBA colors, converts it to HSB, asks for a limit that sets whether a pixel is an outline or not (typically some value around 0.25, and checking the B (brightness) value), and then stores true or false in a 2-dimensional array of booleans (true is an outline, false is not). This gets me to stage 2 just fine. I am currently stuck on stage 3, and am currently attempting to achieve the following:
Here is my current code, where the outline[][] variable is the original 2d array of trues/falses (stage 2) and thinned[][] is the outline in stage 3.
public void thinOutline() {
thinned = new boolean[outline.length][outline[0].length];
for (int x = 0; x < thinned.length; x++)
for (int y = 0; y < thinned[0].length; y++) {
if (x > 0 && x < thinned.length - 1 && y > 0 && y < thinned[0].length - 1)
if (!thinned[x + 1][y] && !thinned[x - 1][y] && !thinned[x][y + 1] && !thinned[x][y - 1] && outline[x][y])
thinned[x][y] = true;
else
thinned[x][y] = false;
else
thinned[x][y] = outline[x][y];
}
}
I've devised a pretty simple solution that works well enough for my purposes. I have 3 arrays, outline[][], thinned[][], and thinIteration[][]. outline[][] and thinned[][] are both set when an image is loaded as explained in my question (stages 1 and 2). thinIteration[][] is then loaded with batches of pixels that need to be thinned and are considered as "border" pixels. The function then erases these pixels, and if it erased any pixels, it restarts the method. It continues to do this cycle until it finds no more pixels to thin.
The program knows whether or not to thin a pixel if it is itself an outline pixel, has at least 2 bordering pixels left/right/up/down and at least 2 bordering pixels diagonally, but not more than 3 left/right/up/down and diagonally (which would mean it is a contained pixel)
public void thinOutline() {
boolean didThinIteration = false;
for (int x = 1; x < originalWidth - 1; x++)
for (int y = 1; y < originalHeight - 1; y++) {
int numOfBorders = (thinned[x - 1][y] ? 1 : 0) + (thinned[x + 1][y] ? 1 : 0) + (thinned[x][y + 1] ? 1 : 0) + (thinned[x][y - 1] ? 1 : 0);
int numOfDiagonals = (thinned[x - 1][y + 1] ? 1 : 0) + (thinned[x + 1][y + 1] ? 1 : 0) + (thinned[x - 1][y - 1] ? 1 : 0) + (thinned[x + 1][y - 1] ? 1 : 0);
boolean thin = thinned[x][y] && numOfBorders > 1 && numOfBorders < 4 && numOfDiagonals > 1 && numOfDiagonals < 4;
thinIteration[x][y] = thin;
if (thin && !didThinIteration)
didThinIteration = true;
}
for (int x = 0; x < originalWidth; x++)
for (int y = 0; y < originalHeight; y++)
if (thinIteration[x][y])
thinned[x][y] = false;
if (didThinIteration)
thinOutline();
}
Is this possible guys? This is homework I have, and my teacher obviously believes it is, but it seems to me that it's impossible not to use addition or multiplication outside of the short-multiplication method.
Write (and provide a tester for) a recursive algorithm:
int multiply(int x, int y)
to multiply two positive integers together without using the *
operator. Do not just add x to itself y times!!!
(Hint: Write a recursive method that will multiply an integer by a
value in the range 0 .. 10. Then write a second recursive method to
implement the multiplication algorithm you learned to multiply
multi-digit numbers in elementary school.)
My issue is that once you break down any multi digit number and starting adding those together you have to use multiplication of numbers greater than 10, i.e 22 * 6 is 2 * 6 + 20 * 6 ... so am I totally missing something?
EDIT
I guess I should have added this is the code I have,
public int mult(int x, int y){
return x == 0 ? 0 : (mult(x-1, y) + y);
}
which is perfect, but as far as I understand the instructions, that's breaking do not just add x to itself y times. I personally believe it isn't, but my teacher hasn't been very clear, and I'd like to know if there's some other way that I havn't thought of, sorry for the confusion.
Yes, it's possible. Yes, I think you're missing something. Try writing down the steps you'd follow to manually multiply two numbers, the way you learned in elementary school.
Then turn those steps into code.
My interpretation of the assignment is that the teacher would like the student to implement a recursive algorithm to perform Grid method multiplication (the kind we learn in elementary school).
For example, multiplying 34 x 13 would be done like so...
34
* 13
====
12
90
40
+300
====
442
I didn't have easy access to a Java development environment, so I wrote the code in C# but the algorithm should be simple enough to convert into Java.
public int Multiply(int x, int y)
{
if (x < 0) throw new ArgumentException("must be positive integer", "x");
if (y < 0) throw new ArgumentException("must be positive integer", "y");
if (x == 0 || y == 0) return 0; // obvious quick-exit condition
// integer division
int xDivBy10 = x / 10;
int yDivBy10 = y / 10;
bool xIsSingleDigit = xDivBy10 == 0;
bool yIsSingleDigit = yDivBy10 == 0;
// base case
if (xIsSingleDigit && yIsSingleDigit)
{
return MultiplySingleDigits(x, y);
}
// otherwise, use grid multiplication recursively
// http://en.wikipedia.org/wiki/Grid_method_multiplication
if (xIsSingleDigit) // y must not be a single digit
{
return (Multiply(x, yDivBy10) * 10) + Multiply(x, y % 10);
}
if (yIsSingleDigit) // x must not be a single digit
{
return (Multiply(xDivBy10, y) * 10) + Multiply(x % 10, y);
}
// else - x and y are both numbers which are not single digits
return (Multiply(x, yDivBy10) * 10) + Multiply(x, y % 10); // the same code as the "if (xIsSingleDigit)" case
}
// technically, this algorith can multiply any positive integers
// but I have restricted it to only single digits as per the assignment's requirements/hint
private int MultiplySingleDigits(int x, int y)
{
if (x < 0 || x > 9) throw new ArgumentException("must be in range 0 - 9 (inclusive)", "x");
if (y < 0 || y > 9) throw new ArgumentException("must be in range 0 - 9 (inclusive)", "y");
if (x == 0 || y == 0) return 0; // base case
return x + MultiplySingleDigits(x, y - 1);
}
NOTES:
This approach still uses the * operator but not for actually multiplying x and y, it is used to increase other sub-products by multiples of 10
Many parts of this code could be simplified/refactored but I specifically left them expanded to make the steps more obvious
Of course you can do it.
First of all, think about the condition. If some number is 0, then the result is? Right.. zero.
So.. You'll have if x is zero or y is zero return 0
Now.. saying X * Y is like saying "X, Y times", which is like writing: X + .... + X (Y times).
So, you'll have something like:
x + multiply(x, y - 1);
You'll have to consider the case in which one of the numbers is negative (But if you understand the basic, I believe you can easily do it).
This solution will work for both when y>=0 and y<0
public int multiply(final int x, final int y) {
if (y != 0 && x != 0) {
if (y > 0) {
return multiply(x, y - 1) + x;
} else {
return multiply(x, y + 1) - x;
}
}
return 0;
}
Easily possible.
int multiply(int x, int y) {
if(y == 0) {
return 0;
}
return x + multiply(x, y - 1);
}
The above fails to take into account the case where y is negative, but you wouldn't want me to do all your work for you . . .
static int Multiply(int x, int y)
{
if (y > 0 && x > 0)
return (x + Multiply(x, y - 1));
if (y < 0 && x > 0)
return -Multiply(x, -y);
if (x < 0 && y > 0)
return -Multiply(-x, y);
if (x < 0 && y < 0)
return Multiply(-x, -y);
return 0;
}