Thinning a line - java

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();
}

Related

Reduce complexity of counting neighbours (Conway's Game of Life)

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.

Smarter way to calculate adjacent mines in Minesweeper using Java 8 Streams

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();

Wandering Robot - in Kickstart got WA

I try to write a short solution for a Google Kick Start question in 2020 Round B and always get a WA. I tried to figure out where was wrong but nothing found. I even download other's passed code and wrote a dataset creator to let two programs run and compare the result, but after running more than 100,000 groups of data, nothing different has been found. I'm kind of crazy now, could you help me to check out what's wrong in my code, please?
Here is the question:
Jemma is competing in a robotics competition. The challenge for today
is to build a robot that can navigate around a hole in the arena.
The arena is a grid of squares containing W columns (numbered 1 to W
from left to right) and H rows (numbered 1 to H from top to bottom).
The square in the x-th column and y-th row is denoted (x, y). The
robot begins in the top left square (1,1) and must navigate to the
bottom right square (W, H).
A rectangular subgrid of squares has been cut out of the grid. More
specifically, all the squares that are in the rectangle with top-left
square (L, U) and bottom-right square (R, D) have been removed.
Jemma did not have much time to program her robot, so it follows a
very simple algorithm: If the robot is in the rightmost column, it
will always move to the square directly below it. Otherwise, If the
robot is in the bottommost row, it will always move to the square
directly right of it. Otherwise, The robot will randomly choose to
either move to the square directly to the right, or to the square
directly below it with equal probability.
Jemma passes the challenge if her robot avoids falling into the hole
and makes it to the square (W, H). What is the probability she passes
the challenge?
Input
The first line of the input gives the number of test cases, T. T
test cases follow. Each test case consists of a single line containing
W, H, L, U, R, and D.
Output
For each test case, output one line containing Case #x: y,
where x is the test case number (starting from 1) and y is a real
number between 0 and 1 inclusive, the probability that Jemma passes
the challenge.
y will be considered correct if it is within an absolute or relative
error of 10-5 of the correct answer. See the FAQ for an explanation of
what that means, and what formats of real numbers we accept.
Limits Time limit:
15 seconds per test set. Memory limit: 1GB. 1 ≤ T ≤
100. 1 ≤ U ≤ D ≤ H. 1 ≤ L ≤ R ≤ W. Neither the top-left nor bottom-right squares will be missing.
Test set 1 1 ≤ W ≤ 300. 1 ≤ H ≤ 300.
Test set 2 1 ≤ W ≤ 105. 1 ≤ H ≤ 105.
Here is my code:
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int sets = scan.nextInt();
for(int q = 0; q < sets; q++){
int rows = scan.nextInt();
int cols = scan.nextInt();
double[][] board = new double[rows + 1][cols + 1];
int leftx = scan.nextInt();
int lefty = scan.nextInt();
int rightx = scan.nextInt();
int righty = scan.nextInt();
if((rightx == rows && righty == cols) || (leftx == 1 && rightx == 1)){
System.out.println("Case #" + (q + 1) + ": " + 0.0);
continue;
}
board[1][1] = 1;
for(int i = 1; i < rows; i++){
for(int j = 1; j < cols; j++){
if((i == rightx && j >= lefty && j <= righty) || (j == righty && i >= leftx && i <= rightx)){
continue;
}
board[i + 1][j] += board[i][j] / 2;
board[i][j + 1] += board[i][j] / 2;
}
}
for(int i = 1; i < rows; i++){
if(cols == righty && i >= leftx && i <= rightx){
continue;
}
board[i + 1][cols] += board[i][cols];
}
for(int i = 1; i < cols; i++){
if(rows == rightx && i >= lefty && i <= righty){
continue;
}
board[rows][i + 1] += board[rows][i];
}
System.out.printf("Case #%d: %.10f\n", q + 1, board[rows][cols]);
}
}
}
Thanks!!
The problem asks to find the probability, y is a real number between 0 and 1 inclusive. Your code computes the number of paths safely leading to the bottom-right corner, an integer. No wonders it is a wrong answer.

How do I output the vertical list into horizontal lists

public class NormalNumbers {
public static void main(String[] args) {
int x = 1;
while ((x >= 1) && (x <= 100)) {
System.out.println("x = " + x);
x = x + 1;
}
}
}
The current output is:
x = 1
x = 2
...
x = 100
I want to change the format to:
x=1 x=2 x=3 x=4 x=5
x=6 x=7 x=8 x=9 x=10
and so on.
How do I achieve that?
Instead of using println(), which automatically inserts a newline character at the end of whatever you're printing, just use print() and add an extra space to pad your entries.
If you want to inject a newline after 5 entries specifically, you can do so with an empty println() and the modulus operator like so:
while ((x >= 1) && (x <= 100)) {
System.out.print("x = " + x);
if (x % 5 == 0) {
System.out.println();
}
x = x + 1;
}
Divide your counter by 5 using modulus division if there is no remainder then create a new line:
int x = 1;
while ((x >= 1) && (x <= 100))
{
System.out.print("x = " + x + " ");
if(x % 5 == 0)
{
System.out.print("\n");
}
x = x + 1;
}
println is next line, print is on the same line.
x % 5 == 0 checks that the x values is a multiple of 5 or not.
int x = 1;
while ((x >= 1) && (x <= 100)) {
if (x % 5 == 0) {
System.out.println("x="+x);
} else {
System.out.print("x=" +x+ " ");
}
x = x + 1;
}
That gives you output as
x=1 x=2 x=3 x=4 x=5
x=6 x=7 x=8 x=9 x=10
x=11 x=12 x=13 x=14 x=15
x=16 x=17 x=18 x=19 x=20
-----
System.out.println prints the text and adds a new line. Use System.out.print to print on the same line instead.
So it would be something like this:
System.out.print("x=" + x + " ");
To add a new line each 5 numbers, use:
// if x is multiple of 5, add a new line
if (x % 5 == 0) {
System.out.println();
}
PD: You can use x++ (increment operator) or x += 1 (in the case you want to increase in more than one unit) instead of x = x + 1.
PD2 : You might want to use a tabulation (\t) instead of a space for separating your numbers. That way, numbers with two digits will have the same indentation than numbers with one digit.
System.out.print("x=" + x + "\t");
I think that in your case the better way is using for(;;) statement:
for (int x = 1; x > 0 && x < 101;)
System.out.print("x = " + x + (x++ % 5 == 0 ? "\n" : " "));
The ternary operator x++ % 5 == 0 ? "\n" : " " is responsible for the new line and increment the x variable.
Output:
x = 1 x = 2 x = 3 x = 4 x = 5
x = 6 x = 7 x = 8 x = 9 x = 10
...
x = 96 x = 97 x = 98 x = 99 x = 100

Java: Connect 4 Winning Diagonally

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..

Categories

Resources