Implementing Watershed Segmentation in Java - java

I'm trying to write my own implementation of Watershed Segmentation for a project. I have a version that returns something resembling the correct segmentation given really trivial pictures. Unfortunately, it's super-slow/inefficient and it may or may not terminate in all cases.
I've been working from the description in "Digital Image Processing," by Woods and Gonzales, and from the Watershed Wikipedia page. The general algorithm is coded and included below, but I have a feeling I'm looping over a lot of things I do not need to be. I appreciate any and all help here, thanks in advance.
public static Set<Point> watershed(double[][] im) {
//Get the Gradient Magnitude to use as our "topographic map."
double t1 = System.currentTimeMillis();
double[][] edges = EdgeDetection.sobelMagnitude(im);
//Only iterate over values in the gradient magnitude to speed up.
double[] histogram = Image.getHistogram(edges);
Image.drawHistogram(histogram, Color.red);
int minPixelValue = 0;
for (int i = 0; i < histogram.length; i++) {
if (histogram[i] > 0) {
minPixelValue = i;
break;
}
}
int h = im.length;
int w = im[0].length;
//SE is a 3x3 structuring element for morphological dilation.
boolean[][] SE = {{true, true, true}, {true, true, true}, {true, true, true}};
//Keeping track of last iteration's components to see if two flooded together.
ArrayList<Set<Point>> lastComponents = connectedComponents(getSet(EdgeDetection.underThreshold(edges, minPixelValue + 1)));
ArrayList<Set<Point>> components;
Set<Point> boundary = new HashSet<Point>();
for (int i = minPixelValue + 1; i < 256; i++) {
if (histogram[i] != 0) {
System.out.println("BEHHH " + i);
t1 = System.currentTimeMillis();
ArrayList<Integer> damLocations = new ArrayList<Integer>();
HashMap<Integer, ArrayList<Integer>> correspondingSets = new HashMap<Integer, ArrayList<Integer>>();
//Figure out which of the old sets the new sets incorporated.
//Here is where we check if sets flooded into eachother.
//System.out.println("Checking for flooding");
components = connectedComponents(getSet(EdgeDetection.underThreshold(edges, i)));
for (int nc = 0; nc < components.size(); nc++) {
//System.out.println("Checking component " + nc);
Set<Point> newComponent = components.get(nc);
for (int oc = 0; oc < lastComponents.size(); oc++) {
//System.out.println(" Against component " + oc);
Set<Point> oldComponent = lastComponents.get(oc);
if (numberInCommon(newComponent, oldComponent) > 0) {
//System.out.println(" In there.");
ArrayList<Integer> oldSetsContained;
if (correspondingSets.containsKey(nc)) {
oldSetsContained = correspondingSets.get(nc);
damLocations.add(nc);
} else {
//System.out.println(" Nope.");
oldSetsContained = new ArrayList<Integer>();
}
oldSetsContained.add(oc);
correspondingSets.put(nc, oldSetsContained);
}
}
}
System.out.println("Calculating overlapping sets: " + (System.currentTimeMillis() - t1));
//System.out.println("Check done.");
for (int key : correspondingSets.keySet()) {
Integer[] cs = new Integer[correspondingSets.get(key).size()];
correspondingSets.get(key).toArray(cs);
if (cs.length == 1) {
//System.out.println("Set " + cs[0] + " has grown without flooding.");
} else {
//System.out.println("The following sets have flooded together: " + Arrays.toString(cs));
}
}
//Build Damns to prevent flooding
for (int c : damLocations) {
System.out.println("Building dam for component " + c);
Set<Point> bigComponent = components.get(c);
System.out.println("Total size: " + bigComponent.size());
ArrayList<Set<Point>> littleComponent = new ArrayList<Set<Point>>();
for (int lcindex : correspondingSets.get(c)) {
littleComponent.add(lastComponents.get(lcindex));
}
Set<Point> unionSet = new HashSet<Point>(boundary);
for (Set<Point> lc : littleComponent) {
unionSet = union(unionSet, lc);
}
System.out.println("Building union sets: " + (System.currentTimeMillis() - t1));
while (intersection(unionSet, bigComponent).size() < bigComponent.size()) {
for (int lIndex = 0; lIndex < littleComponent.size(); lIndex++) {
Set<Point> lc = littleComponent.get(lIndex);
Set<Point> lcBoundary = extractBoundaries(lc, SE, h, w);
Set<Point> toAdd = new HashSet<Point>();
Set<Point> otherComponents = new HashSet<Point>(unionSet);
otherComponents.removeAll(lc);
otherComponents.removeAll(boundary);
otherComponents = extractBoundaries(otherComponents, SE, h, w);
for (Point pt : lcBoundary) {
Set<Point> eightNbrs = get8Neighborhood(pt);
for (Point nbr : eightNbrs) {
if (bigComponent.contains(nbr) & !boundary.contains(nbr)) {
Set<Point> neighborNbr = get8Neighborhood(nbr);
if (intersection(neighborNbr, otherComponents).size() > 0) {
boundary.add(nbr);
edges[nbr.y][nbr.x] = 256;
break;
} else if (!lc.contains(nbr)) {
toAdd.add(nbr);
//if(i==65)System.out.println("Adding point " + nbr.y + " " + nbr.x);
} else {
//if(i==65)System.out.println("Already in here " + nbr.y + " " + nbr.x);
}
}
}
}
t1 = System.currentTimeMillis();
lc.addAll(toAdd);
toAdd.removeAll(toAdd);
littleComponent.set(lIndex, lc);
unionSet = new HashSet<Point>(boundary);
for (Set<Point> ltc : littleComponent) {
unionSet = union(unionSet, ltc);
}
System.out.println("This is a donk " + intersection(unionSet, bigComponent).size());
otherComponents = new HashSet<Point>(unionSet);
otherComponents.removeAll(lc);
otherComponents.removeAll(boundary);
}
}
}
}
}
boundary = close(boundary,h,w);
Image.drawSet(boundary, h, w);
return boundary;
}

The algorithm as it seems is at most O(n^2).
You have many many nested loops.. I was not able to find the Woods description.
This code by Christopher Mei implements the algorithm: it is a really simple implementation.
WatershedPixel.java
/*
* Watershed algorithm
*
* Copyright (c) 2003 by Christopher Mei (christopher.mei#sophia.inria.fr)
*
* This plugin is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this plugin; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
import java.lang.*;
import java.util.*;
import ij.*;
/**
* The aim of WatershedPixel is to enable
* sorting the pixels of an Image according
* to their grayscale value.
*
* This is the first step of the Vincent
* and Soille Watershed algorithm (1991)
*
**/
public class WatershedPixel implements Comparable {
/** Value used to initialise the image */
final static int INIT = -1;
/** Value used to indicate the new pixels that
* are going to be processed (intial value
* at each level)
**/
final static int MASK = -2;
/** Value indicating that the pixel belongs
* to a watershed.
**/
final static int WSHED = 0;
/** Fictitious pixel **/
final static int FICTITIOUS = -3;
/** x coordinate of the pixel **/
private int x;
/** y coordinate of the pixel **/
private int y;
/** grayscale value of the pixel **/
private byte height;
/** Label used in the Watershed immersion algorithm **/
private int label;
/** Distance used for working on pixels */
private int dist;
/** Neighbours **/
private Vector neighbours;
public WatershedPixel(int x, int y, byte height) {
this.x = x;
this.y = y;
this.height = height;
label = INIT;
dist = 0;
neighbours = new Vector(8);
}
public WatershedPixel() {
label = FICTITIOUS;
}
public void addNeighbour(WatershedPixel neighbour) {
/*IJ.write("In Pixel, adding :");
IJ.write(""+neighbour);
IJ.write("Add done");
*/
neighbours.add(neighbour);
}
public Vector getNeighbours() {
return neighbours;
}
public String toString() {
return new String("("+x+","+y+"), height : "+getIntHeight()+", label : "+label+", distance : "+dist);
}
public final byte getHeight() {
return height;
}
public final int getIntHeight() {
return (int) height&0xff;
}
public final int getX() {
return x;
}
public final int getY() {
return y;
}
/** Method to be able to use the Collections.sort static method. **/
public int compareTo(Object o) {
if(!(o instanceof WatershedPixel))
throw new ClassCastException();
WatershedPixel obj = (WatershedPixel) o;
if( obj.getIntHeight() < getIntHeight() )
return 1;
if( obj.getIntHeight() > getIntHeight() )
return -1;
return 0;
}
public void setLabel(int label) {
this.label = label;
}
public void setLabelToINIT() {
label = INIT;
}
public void setLabelToMASK() {
label = MASK;
}
public void setLabelToWSHED() {
label = WSHED;
}
public boolean isLabelINIT() {
return label == INIT;
}
public boolean isLabelMASK() {
return label == MASK;
}
public boolean isLabelWSHED() {
return label == WSHED;
}
public int getLabel() {
return label;
}
public void setDistance(int distance) {
dist = distance;
}
public int getDistance() {
return dist;
}
public boolean isFICTITIOUS() {
return label == FICTITIOUS;
}
public boolean allNeighboursAreWSHED() {
for(int i=0 ; i<neighbours.size() ; i++) {
WatershedPixel r = (WatershedPixel) neighbours.get(i);
if( !r.isLabelWSHED() )
return false;
}
return true;
}
}
WatershedFIFO.java
/*
* Watershed plugin
*
* Copyright (c) 2003 by Christopher Mei (christopher.mei#sophia.inria.fr)
*
* This plugin is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this plugin; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
import java.util.*;
import ij.*;
/** This class implements a FIFO queue that
* uses the same formalism as the Vincent
* and Soille algorithm (1991)
**/
public class WatershedFIFO {
private LinkedList watershedFIFO;
public WatershedFIFO() {
watershedFIFO = new LinkedList();
}
public void fifo_add(WatershedPixel p) {
watershedFIFO.addFirst(p);
}
public WatershedPixel fifo_remove() {
return (WatershedPixel) watershedFIFO.removeLast();
}
public boolean fifo_empty() {
return watershedFIFO.isEmpty();
}
public void fifo_add_FICTITIOUS() {
watershedFIFO.addFirst(new WatershedPixel());
}
public String toString() {
StringBuffer ret = new StringBuffer();
for(int i=0; i<watershedFIFO.size(); i++) {
ret.append( ((WatershedPixel)watershedFIFO.get(i)).toString() );
ret.append( "\n" );
}
return ret.toString();
}
}
WatershedStructure.java
/*
* Watershed algorithm
*
* Copyright (c) 2003 by Christopher Mei (christopher.mei#sophia.inria.fr)
*
* This plugin is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this plugin; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
import java.lang.*;
import java.util.*;
import ij.process.*;
import ij.*;
import java.awt.*;
/**
* WatershedStructure contains the pixels
* of the image ordered according to their
* grayscale value with a direct access to their
* neighbours.
*
**/
public class WatershedStructure {
private Vector watershedStructure;
public WatershedStructure(ImageProcessor ip) {
byte[] pixels = (byte[])ip.getPixels();
Rectangle r = ip.getRoi();
int width = ip.getWidth();
int offset, topOffset, bottomOffset, i;
watershedStructure = new Vector(r.width*r.height);
/** The structure is filled with the pixels of the image. **/
for (int y=r.y; y<(r.y+r.height); y++) {
offset = y*width;
IJ.showProgress(0.1+0.3*(y-r.y)/(r.height));
for (int x=r.x; x<(r.x+r.width); x++) {
i = offset + x;
int indiceY = y-r.y;
int indiceX = x-r.x;
watershedStructure.add(new WatershedPixel(indiceX, indiceY, pixels[i]));
}
}
/** The WatershedPixels are then filled with the reference to their neighbours. **/
for (int y=0; y<r.height; y++) {
offset = y*width;
topOffset = offset+width;
bottomOffset = offset-width;
IJ.showProgress(0.4+0.3*(y-r.y)/(r.height));
for (int x=0; x<r.width; x++) {
WatershedPixel currentPixel = (WatershedPixel)watershedStructure.get(x+offset);
if(x+1<r.width) {
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x+1+offset));
if(y-1>=0)
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x+1+bottomOffset));
if(y+1<r.height)
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x+1+topOffset));
}
if(x-1>=0) {
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x-1+offset));
if(y-1>=0)
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x-1+bottomOffset));
if(y+1<r.height)
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x-1+topOffset));
}
if(y-1>=0)
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x+bottomOffset));
if(y+1<r.height)
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x+topOffset));
}
}
Collections.sort(watershedStructure);
//IJ.showProgress(0.8);
}
public String toString() {
StringBuffer ret = new StringBuffer();
for(int i=0; i<watershedStructure.size() ; i++) {
ret.append( ((WatershedPixel) watershedStructure.get(i)).toString() );
ret.append( "\n" );
ret.append( "Neighbours :\n" );
Vector neighbours = ((WatershedPixel) watershedStructure.get(i)).getNeighbours();
for(int j=0 ; j<neighbours.size() ; j++) {
ret.append( ((WatershedPixel) neighbours.get(j)).toString() );
ret.append( "\n" );
}
ret.append( "\n" );
}
return ret.toString();
}
public int size() {
return watershedStructure.size();
}
public WatershedPixel get(int i) {
return (WatershedPixel) watershedStructure.get(i);
}
}
Watershed_Algorithm.java
/*
* Watershed algorithm
*
* Copyright (c) 2003 by Christopher Mei (christopher.mei#sophia.inria.fr)
*
* This plugin is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this plugin; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
import ij.*;
import ij.plugin.filter.PlugInFilter;
import ij.process.*;
import ij.gui.*;
import ij.plugin.frame.PlugInFrame;
import java.awt.*;
import java.util.*;
/**
* This algorithm is an implementation of the watershed immersion algorithm
* written by Vincent and Soille (1991).
*
* #Article{Vincent/Soille:1991,
* author = "Lee Vincent and Pierre Soille",
* year = "1991",
* keywords = "IMAGE-PROC SKELETON SEGMENTATION GIS",
* institution = "Harvard/Paris+Louvain",
* title = "Watersheds in digital spaces: An efficient algorithm
* based on immersion simulations",
* journal = "IEEE PAMI, 1991",
* volume = "13",
* number = "6",
* pages = "583--598",
* annote = "Watershed lines (e.g. the continental divide) mark the
* boundaries of catchment regions in a topographical map.
* The height of a point on this map can have a direct
* correlation to its pixel intensity. WIth this analogy,
* the morphological operations of closing (or opening)
* can be understood as smoothing the ridges (or filling
* in the valleys). Develops a new algorithm for obtaining
* the watershed lines in a graph, and then uses this in
* developing a new segmentation approach based on the
* {"}depth of immersion{"}.",
* }
*
* A review of Watershed algorithms can be found at :
* http://www.cs.rug.nl/~roe/publications/parwshed.pdf
*
* #Article{RoeMei00,
* author = "Roerdink and Meijster",
* title = "The Watershed Transform: Definitions, Algorithms and
* Parallelization Strategies",
* journal = "FUNDINF: Fundamenta Informatica",
* volume = "41",
* publisher = "IOS Press",
* year = "2000",
* }
**/
public class Watershed_Algorithm implements PlugInFilter {
private int threshold;
final static int HMIN = 0;
final static int HMAX = 256;
public int setup(String arg, ImagePlus imp) {
if (arg.equals("about"))
{showAbout(); return DONE;}
return DOES_8G+DOES_STACKS+SUPPORTS_MASKING+NO_CHANGES;
}
public void run(ImageProcessor ip) {
boolean debug = false;
IJ.showStatus("Sorting pixels...");
IJ.showProgress(0.1);
/** First step : the pixels are sorted according to increasing grey values **/
WatershedStructure watershedStructure = new WatershedStructure(ip);
if(debug)
IJ.write(""+watershedStructure.toString());
IJ.showProgress(0.8);
IJ.showStatus("Start flooding...");
if(debug)
IJ.write("Starting algorithm...\n");
/** Start flooding **/
WatershedFIFO queue = new WatershedFIFO();
int curlab = 0;
int heightIndex1 = 0;
int heightIndex2 = 0;
for(int h=HMIN; h<HMAX; h++) /*Geodesic SKIZ of level h-1 inside level h */ {
for(int pixelIndex = heightIndex1 ; pixelIndex<watershedStructure.size() ; pixelIndex++) /*mask all pixels at level h*/ {
WatershedPixel p = watershedStructure.get(pixelIndex);
if(p.getIntHeight() != h) {
/** This pixel is at level h+1 **/
heightIndex1 = pixelIndex;
break;
}
p.setLabelToMASK();
Vector neighbours = p.getNeighbours();
for(int i=0 ; i<neighbours.size() ; i++) {
WatershedPixel q = (WatershedPixel) neighbours.get(i);
if(q.getLabel()>=0) {/*Initialise queue with neighbours at level h of current basins or watersheds*/
p.setDistance(1);
queue.fifo_add(p);
break;
} // end if
} // end for
} // end for
int curdist = 1;
queue.fifo_add_FICTITIOUS();
while(true) /** extend basins **/{
WatershedPixel p = queue.fifo_remove();
if(p.isFICTITIOUS())
if(queue.fifo_empty())
break;
else {
queue.fifo_add_FICTITIOUS();
curdist++;
p = queue.fifo_remove();
}
if(debug) {
IJ.write("\nWorking on :");
IJ.write(""+p);
}
Vector neighbours = p.getNeighbours();
for(int i=0 ; i<neighbours.size() ; i++) /* Labelling p by inspecting neighbours */{
WatershedPixel q = (WatershedPixel) neighbours.get(i);
if(debug)
IJ.write("Neighbour : "+q);
/* Original algorithm :
if( (q.getDistance() < curdist) &&
(q.getLabel()>0 || q.isLabelWSHED()) ) {*/
if( (q.getDistance() <= curdist) &&
(q.getLabel()>=0) ) {
/* q belongs to an existing basin or to a watershed */
if(q.getLabel() > 0) {
if( p.isLabelMASK() )
// Removed from original algorithm || p.isLabelWSHED() )
p.setLabel(q.getLabel());
else
if(p.getLabel() != q.getLabel())
p.setLabelToWSHED();
} // end if lab>0
else
if(p.isLabelMASK())
p.setLabelToWSHED();
}
else
if( q.isLabelMASK() &&
(q.getDistance() == 0) ) {
if(debug)
IJ.write("Adding value");
q.setDistance( curdist+1 );
queue.fifo_add( q );
}
} // end for, end processing neighbours
if(debug) {
IJ.write("End processing neighbours");
IJ.write("New val :\n"+p);
IJ.write("Queue :\n"+queue);
}
} // end while (loop)
/* Detect and process new minima at level h */
for(int pixelIndex = heightIndex2 ; pixelIndex<watershedStructure.size() ; pixelIndex++) {
WatershedPixel p = watershedStructure.get(pixelIndex);
if(p.getIntHeight() != h) {
/** This pixel is at level h+1 **/
heightIndex2 = pixelIndex;
break;
}
p.setDistance(0); /* Reset distance to zero */
if(p.isLabelMASK()) { /* the pixel is inside a new minimum */
curlab++;
p.setLabel(curlab);
queue.fifo_add(p);
while(!queue.fifo_empty()) {
WatershedPixel q = queue.fifo_remove();
Vector neighbours = q.getNeighbours();
for(int i=0 ; i<neighbours.size() ; i++) /* inspect neighbours of p2*/{
WatershedPixel r = (WatershedPixel) neighbours.get(i);
if( r.isLabelMASK() ) {
r.setLabel(curlab);
queue.fifo_add(r);
}
}
} // end while
} // end if
} // end for
} /** End of flooding **/
IJ.showProgress(0.9);
IJ.showStatus("Putting result in a new image...");
/** Put the result in a new image **/
int width = ip.getWidth();
ImageProcessor outputImage = new ByteProcessor(width, ip.getHeight());
byte[] newPixels = (byte[]) outputImage.getPixels();
for(int pixelIndex = 0 ; pixelIndex<watershedStructure.size() ; pixelIndex++) {
WatershedPixel p = watershedStructure.get(pixelIndex);
if(p.isLabelWSHED() && !p.allNeighboursAreWSHED())
newPixels[p.getX()+p.getY()*width] = (byte)255;
}
IJ.showProgress(1);
IJ.showStatus("Displaying result...");
new ImagePlus("Watershed", outputImage).show();
}
void showAbout() {
IJ.showMessage("About Watershed_Algorithm...",
"This plug-in filter calculates the watershed of a 8-bit images.\n" +
"It uses the immersion algorithm written by Vincent and Soille (1991)\n"
);
}
}

Related

What am I doing wrong with the below implementation for problem : queens-attack-2 hackerrank?

You will be given a square chess board with one queen and a number of obstacles placed on it. Determine how many squares the queen can attack.
Please find below my implementation.
Its working fine for many testcases but failing for few.
Am I missing any use case?
Kindly help. Thanks in advance.
import java.io.*;
import java.util.*;
import java.util.stream.*;
import static java.util.stream.Collectors.toList;
class Point{
int x;
int y;
public Point(int x, int y) {
super();
this.x = x;
this.y = y;
}
public Point() {
super();
}
#Override
public String toString() {
return "Point [x=" + x + ", y=" + y + "]";
}
}
class Result {
public static void printBoard(int n, int k, int r_q, int c_q, List<List<Integer>> obstacles) {
char[][] board = new char[n+1][n+1];
for(int i=0;i<=n;i++)for(int j=0;j<=n;j++)board[i][j]='.';
board[r_q][c_q]='Q';
for(List<Integer> o : obstacles) {
int x = o.get(0);
int y = o.get(1);
board[x][y]='X';
}
for(int i=n;i>=1;i--) {
for(int j=1;j<=n;j++) {
System.out.print(board[i][j]);
}
System.out.println();
}
}
/*
* n => row and column count in square board
* k => number of obstacles
* r_q => Queens row position
* c_q => Queens column position
* obstacles => All obstacles position in (row, column) format
*
*/
public static int queensAttack(int n, int k, int r_q, int c_q, List<List<Integer>> obstacles) {
// printBoard(n,k,r_q,c_q,obstacles);
/*
* Board is like below
* |
* ^ |
* | |
* | |
* | |
* row |
*index|
* |
* |_________________
* column index --->
*/
//initiate last available blank position for all 8 terminal points
Point
topLeft = r_q+c_q-1>n?new Point(n, r_q+c_q-n):new Point(r_q+c_q-1,1),
top = new Point(n,c_q),
topRight = r_q>c_q?new Point(n,n-(r_q-c_q)):new Point(n+(r_q-c_q),n),
left = new Point(r_q,1),
right = new Point(r_q,n),
bottomLeft = r_q>c_q?new Point(r_q-c_q+1,1):new Point(1,c_q-r_q+1),
bottom = new Point(1,c_q),
bottomRight = r_q+c_q-1>n?new Point(r_q+c_q-n,n):new Point(1,r_q+c_q-1);
for(List<Integer> o : obstacles) {
int x = o.get(0);
int y = o.get(1);
/*
* row==r_q would be horizontal line
* left and right would be needed to update
* if obstacle is at (2,3) and queen at (2,9)
* then last available space on left side is (2,4)
*
*/
if(x==r_q) {
if(y>=left.y && y<c_q) left.y=y+1;
if(y<=right.y && y>c_q) right.y=y-1;
}
/*
* col==c_q would be vertical line
* top and bottom would be needed to update
*/
if(y==c_q) {
if(x>=bottom.x && x<r_q) bottom.x=x+1;
if(x<=top.x && x>r_q)top.x=x-1;
}
/*
* row+col==r_q+c_q would be diagonal line from top left to bottom right
*/
if(x+y==r_q+c_q) {
if(y>=topLeft.y && y<c_q) {
topLeft.y=y+1;
topLeft.x=x-1;
}
if(y<=bottomRight.y && y>c_q) {
bottomRight.y=y-1;
bottomRight.x=x+1;
}
}
/*
* row-col==r_q-c_q would be diagonal line from top right to bottom left
*/
if(x-y==r_q-c_q) {
if(y>=bottomLeft.y && y<c_q) {
bottomLeft.y=y+1;
bottomLeft.x=x+1;
}
if(y<=topRight.y && y>c_q) {
topRight.y=y-1;
topLeft.x=x-1;
}
}
}
/*
* if in any line (x,y) is last available space and
* queen is at (r_q,c_q), then number of places queen can attack is
* |r_q-x| or |c_q-y| both would necessarily be equal
* so I am using any one of them
*/
return
(topLeft.x-r_q) +
(top.x-r_q) +
(topRight.x-r_q)+
(c_q-left.y)+
(right.y-c_q)+
(c_q-bottomLeft.y)+
(r_q-bottom.x)+
(r_q-bottomRight.x);
}
}
public class Solution {
public static void main(String[] args) throws IOException {
BufferedReader bufferedReader = new BufferedReader(new FileReader(new File("INPUT.txt")));
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("OUTPUT.txt"));
String[] firstMultipleInput = bufferedReader.readLine().replaceAll("\\s+$", "").split(" ");
int n = Integer.parseInt(firstMultipleInput[0]);
int k = Integer.parseInt(firstMultipleInput[1]);
String[] secondMultipleInput = bufferedReader.readLine().replaceAll("\\s+$", "").split(" ");
int r_q = Integer.parseInt(secondMultipleInput[0]);
int c_q = Integer.parseInt(secondMultipleInput[1]);
List<List<Integer>> obstacles = new ArrayList<>();
IntStream.range(0, k).forEach(i -> {
try {
obstacles.add(
Stream.of(bufferedReader.readLine().replaceAll("\\s+$", "").split(" "))
.map(Integer::parseInt)
.collect(toList())
);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
});
int result = Result.queensAttack(n, k, r_q, c_q, obstacles);
bufferedWriter.write(String.valueOf(result));
bufferedWriter.newLine();
bufferedReader.close();
bufferedWriter.close();
}
}
Input file : https://drive.google.com/file/d/1yFPAIkW4o-Zf3fwf5o_qK-_ckZWLEAr0/view?usp=share_link
My Output : 190709
Expected Output : 110198

How to recreate the exact RSI calculation from tradingview in java?

I have read every related post on stackoverflow but i just still cant seem to replicate the exact same RSI calculation that tradingview uses. I've spend over dozens of hours trying to get this right. At this point it is really sucking the joy out of my hobby project. Any help would be greatly appreciated.
If any other info or something is required please let me know and i will add it.
What the tradingview documentation says about how the calculation is done:
* For a practical example, the built-in Pine Script function rsi(), could be replicated in long form as follows.
* change = change(close)
* gain = change >= 0 ? change : 0.0
* loss = change < 0 ? (-1) * change : 0.0
* avgGain = rma(gain, 14)
* avgLoss = rma(loss, 14)
* rs = avgGain / avgLoss
* rsi = 100 - (100 / (1 + rs))
*
*
* where
* RMA = alpha * source + (1-alpha) * RMA[1]
* alpha = 1/length
I just cant seem to get the RMA part right, i just dont have a clue anymore on how to interperted it. Below is my class with at the bottom some sample data. If someone has managed to get a exact or near RSI calculation to to the tradingview could you please walk me through the steps.
Rsi_indicator.class
public class Rsi_indicator {
static Candlestick[] candles = getCandleSticks();
static double[] gain_array = new double[candles.length];
static double[] loss_array = new double[candles.length];
public static final int RSI_PERIOD = 14;
public static void main(String[] args) {
/**
* 1. Calculate change over closes
*
* */
for (int i = 0; i < candles.length; i++) {
double gain = 0;
double loss = 0;
if (i >= 1) {
double change = candles[i - 1].getClose() - candles[i].getClose();
gain_array[i] = change >= 0 ? change : 0.0d;
loss_array[i] = change < 0 ? (-1) * change : 0.0d;
}
}
/**
* 2. Calculate AVG gain
*
* According to trading view they use this --> avgGain = rma(gain, 14)
* Where
* RMA = alpha * source + (1-alpha) * RMA[1]
* alpha = 1/length
*/
double alpha = 1.0 / RSI_PERIOD;
RMA rma_gain = new RMA(alpha);
RMA rma_loss = new RMA(alpha);
/*
TODO HOW EXACTLY TO IMPLEMENT THE ABOVE RMA?
what is exactly meant by source ? and RMA[1] is this the previous value that was calculated?
*/
/**
* 4. Calculate relative strength
* */
double RS = avg_gain / avg_loss;
/**
* 5. Calculate RSI
* */
double rsi = 100 - (100 / (1 + RS));
/**
* The last 4 calculated rsi values should be:
* 7 nov 2021 --> 59.68
* 8 nov 2021 --> 67.65
* 9 nov 2021 --> 65.75
* 10 nov 2021 --> 59.33
*/
System.out.println("RSI: " + rsi);
}
static class RMA {
private double alpha;
private Double oldValue;
public RMA(double alpha) {
this.alpha = alpha;
}
public double average(double value) {
if (oldValue == null) {
oldValue = value;
return value;
}
double newValue = oldValue + alpha * (value - oldValue);
return newValue;
}}
/**
* This is just for you to have a working sample instantly
* The sample data i pulled from the binance api is the
* 1D BINANCE BTCUSDT pair from 2021-10-20T02:00 TO 2021-11-10T01:00.
*/
static Candlestick[] getCandleSticks() {
String[] data =
{"1634688000000,64280.59,67000.0,63481.4,66001.41,1634774399999",
"1634774400000,66001.4,66639.74,62000.0,62193.15,1634860799999",
"1634860800000,62193.15,63732.39,60000.0,60688.22,1634947199999",
"1634947200000,60688.23,61747.64,59562.15,61286.75,1635033599999",
"1635033600000,61286.75,61500.0,59510.63,60852.22,1635119999999",
"1635120000000,60852.22,63710.63,60650.0,63078.78,1635206399999",
"1635206400000,63078.78,63293.48,59817.55,60328.81,1635292799999",
"1635292800000,60328.81,61496.0,58000.0,58413.44,1635379199999",
"1635379200000,58413.44,62499.0,57820.0,60575.89,1635465599999",
"1635465600000,60575.9,62980.0,60174.81,62253.71,1635551999999",
"1635552000000,62253.7,62359.25,60673.0,61859.19,1635638399999",
"1635638400000,61859.19,62405.3,59945.36,61299.8,1635724799999",
"1635724800000,61299.81,62437.74,59405.0,60911.11,1635811199999",
"1635811200000,60911.12,64270.0,60624.68,63219.99,1635897599999",
"1635897600000,63220.57,63500.0,60382.76,62896.48,1635983999999",
"1635984000000,62896.49,63086.31,60677.01,61395.01,1636070399999",
"1636070400000,61395.01,62595.72,60721.0,60937.12,1636156799999",
"1636156800000,60940.18,61560.49,60050.0,61470.61,1636243199999",
"1636243200000,61470.62,63286.35,61322.78,63273.59,1636329599999",
"1636329600000,63273.58,67789.0,63273.58,67525.83,1636415999999",
"1636416000000,67525.82,68524.25,66222.4,66947.66,1636502399999",
"1636502400000,66947.67,69000.0,62822.9,64882.43,1636588799999",
};
List<Candlestick>list = new ArrayList<>();
for (String s: data) {
Candlestick c = new Candlestick();
String[] sArr = s.split(",");
c.setOpenTime(Long.valueOf(sArr[0]));
c.setOpen(sArr[1]);
c.setHigh(sArr[2]);
c.setLow(sArr[3]);
c.setClose(sArr[4]);
c.setCloseTime(Long.valueOf(sArr[5]));
list.add(c);
}
return list.stream().toArray(Candlestick[]::new);
}
}
Candlestick.class
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
public class Candlestick {
private Long openTime;
private double open;
private double high;
private double low;
private double close;
private double volume;
private Long closeTime;
public Long getOpenTime() {
return openTime;
}
public void setOpenTime(Long openTime) {
this.openTime = openTime;
}
public double getOpen() {
return open;
}
public void setOpen(String open) {
this.open = toDouble(open);
}
public double getHigh() {
return high;
}
public void setHigh(String high) {
this.high = toDouble(high);
}
public double getLow() {
return low;
}
public void setLow(String low) {
this.low = toDouble(low);
}
public double getClose() {
return close;
}
public void setClose(String close) {
this.close = toDouble(close);
}
public Long getCloseTime() {
return closeTime;
}
public void setCloseTime(Long closeTime) {
this.closeTime = closeTime;
}
public LocalDateTime getFormattedOpenTime() {
return Instant.ofEpochMilli(openTime).atZone(ZoneId.systemDefault()).toLocalDateTime();
}
public LocalDateTime getFormattedCloseTime() {
return Instant.ofEpochMilli(closeTime).atZone(ZoneId.systemDefault()).toLocalDateTime();
}
public double toDouble(String a) {
return Double.parseDouble(a);
}
}

java: the import data cannot be resolved - import data.Frame

I'm using some code from my university's lecturer for a new project and everything seems to be working except for that one little piece, where I want to import "data.Frame" and I just don't know what's missing so my code would function. If anyone knows the solution or could give me an alternative so I could import the needed "Frame" - would be awesome!
I'm using eclipse!
The code is:
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import data.Frame; // HERE IS THE PROBLEM!
/**
* Compares the annotations of two or more annotators and groups them together,
* before they are written to a csv file.
*/
public class ComparisonWriter
{
private String content;
private final int A1 = 1;
private final int A2 = 2;
private final int A3 = 3;
// private final int A4 = 4;
private int numberOfAnnotators = 3;
String outputFileName = "data/200Saetze/Gruppe 1/comparison_buchpreis2_alle.csv";
String inputA1 = "data/200Saetze/Gruppe 1/Daniela/buchpreis2.xml";
String inputA2 = "data/200Saetze/Gruppe 1/Inga/buchpreis2.xml";
String inputA3 = "data/200Saetze/Gruppe 1/Stefan/buchpreis2.xml";
public ComparisonWriter()
{
content = "SatzID;Annotator;Frame;SE;exact;partial;Source;Annotator;SourceString;" +
"exact;exact ohne leer;exact ohne leer (SE);partial;koreferent;SourceFlag;exact;Target;Annotator;" +
"TargetString;exact;exact ohne leer;exact ohne leer (SE);partial;koreferent;TargetFlag;;FrameFlag";
AnnotationReader annotationReaderA1 = new AnnotationReader(inputA1);
Map<String, List<Frame>> seAnnotator1 = annotationReaderA1.getAllSubjectiveExpressions();
AnnotationReader annotationReaderA2 = new AnnotationReader(inputA2);
Map<String, List<Frame>> seAnnotator2 = annotationReaderA2.getAllSubjectiveExpressions();
AnnotationReader annotationReaderA3 = new AnnotationReader(inputA3);
Map<String, List<Frame>> seAnnotator3 = annotationReaderA3.getAllSubjectiveExpressions();
Set<String> sentences = seAnnotator1.keySet();
for (String sentence : sentences)
{
//add leading zeros
content += "\n\n" + sentence + ";" + annotationReaderA1.sentenceStrings.get(sentence);
//add the annotations in a sorted order
Map<Integer, List<Frame>> allFramesInSentence = new HashMap<Integer, List<Frame>>();
allFramesInSentence.put(A1, seAnnotator1.get(sentence));
allFramesInSentence.put(A2, seAnnotator2.get(sentence));
allFramesInSentence.put(A3, seAnnotator3.get(sentence));
// allFramesInSentence.put(A4, seAnnotator4.get(sentence));
//
//get the one with the most annotations
int largest = getIndexOfLargestList(allFramesInSentence);
if(largest == 0)
continue;
for (int i = 0; i < allFramesInSentence.get(largest).size(); i++)
{
Frame frame = allFramesInSentence.get(largest).get(i);
content += "\n\n;A" + largest + ";" + frame;
frame.setConsidered(true);
findOverlappingAnnotations(allFramesInSentence, largest, frame);
}
//Check, if there are not considered annotations in one of the frame lists for that sentence.
for (int a = 1; a <= 4; a++)
{
List<Frame> frameList = allFramesInSentence.get(a);
if(a != largest && frameList != null)
{
for (Frame frame : frameList)
{
if(frame.isConsidered() == false)
{
content += "\n\n;A" + a + ";" + frame;
frame.setConsidered(true);
findOverlappingAnnotations(allFramesInSentence, a, frame);
}
}
}
}
}
writeContentToFile(content, outputFileName, false);
}
/**
* Find overlapping annotations.
* #param frames - list of frames, potentially overlapping with the given one.
* #param largest
* #param frame - frame in question.
*/
private void findOverlappingAnnotations(Map<Integer, List<Frame>> frames,
int largest, Frame frame)
{
for (int a = 1; a <= 4; a++)
{
//If j is not the current largest frame list and there are annotated frames
//in from this annotator (a)
if(a != largest && frames.get(a) != null)
{
for (Frame compareFrame : frames.get(a))
{
addOverlappingAnnotations2Conent(frame, a, compareFrame);
}
}
}
}
/**
* Add overlapping Annotations (measured by matching ids) to the content attribute.
* #param frame
* #param a - Annotator index
* #param compareFrame
*/
private void addOverlappingAnnotations2Conent(Frame frame, int a,
Frame compareFrame)
{
List<String> terminalIDs = compareFrame.getTerminalIDs();
for (String id : terminalIDs)
{
if(compareFrame.isConsidered())
break;
if(frame.getTerminalIDs().contains(id))
{
//Write it to the content
content += "\n;A" + a + ";" + compareFrame;
compareFrame.setConsidered(true);
break;
}
}
}
/**
* Get the index of the largest frame list in the map.
* #param frames - a map with all the frames for each annotator (key: annotator)
* #return The index of the largest frame list.
*/
private int getIndexOfLargestList(Map<Integer, List<Frame>> frames)
{
int size = 0;
int largest = 0;
for(int a = 0; a <= numberOfAnnotators; a++)
{
if(frames.get(a) != null)
{
if(frames.get(a).size() > size)
largest = a;
}
}
return largest;
}
You basically need Frame class in your classpath. If you have this class in some jar, then add that jar to your classpath.
You need to get a copy of the source code or class file for the Frame class and add it to your project. The source code should be in a file called Frame.java.

Sudoku game serialization issue

I am creating the Sudoku game and I am trying to provide options to save, save as, and open games. I am using JFileChooser to do this. I am able to save (or "save as") but when I try to open a saved file, I get an error. I am new to programming and I'm hoping someone could spot the issue and educate me on how to read in the contents of the Sudoku board when I am saving (as well as how to deal with re-creating the Sudoku board when I open the file). I hear there is an easier way to deal with this using InputStream/OutputStream instead of Reader/Writer...
Here is my code for the inner class that implements this (I don't know if there's a way to post my entire class without exceeding the character limit for this text box.):
EDIT:
// this inner class provides a JMenuBar object at the top of
// the board
class MenuAtTop extends JMenuBar implements ActionListener{
// SudokuMain object we are dealing with
private SudokuMain main;
// the "File" menu
private JMenu fileMenu;
// the "New Game" option
private JMenuItem newGame;
// the "Open" option
private JMenuItem open;
// the "Save" option
private JMenuItem save;
// the "Save As" option
private JMenuItem saveAs;
// the "Reset" option
private JMenuItem reset;
// the "Quit" option
private JMenuItem quit;
// the ability to choose files
private JFileChooser choose;
// the saved file
// // compiler would not allow "static" keyword
private File fileSaved = null;
private Object opener;
// JDialog object to create a dialog box to prompt
// user for new game information
private JDialog createNewWin;
/**
* Constructs MenuAtTop object.
*
* #param m The SudokuMain object to be referred to.
*/
public MenuAtTop(final SudokuMain m) {
main = m;
opener = null;
choose = new JFileChooser();
// instantiate and bind to reference
fileMenu = new JMenu("File");
add(fileMenu);
// instantiate and bind to reference
newGame = new JMenuItem("New Game");
newGame.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N,
ActionEvent.CTRL_MASK));
fileMenu.add(newGame);
newGame.addActionListener(this);
open = new JMenuItem("Open");
open.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O,
ActionEvent.CTRL_MASK));
fileMenu.add(open);
// add action listener to "Open" option
open.addActionListener(this);
save = new JMenuItem("Save");
save.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
ActionEvent.CTRL_MASK));
fileMenu.add(save);
// add action listener to "Save" option
save.addActionListener(this);
saveAs = new JMenuItem("Save As");
saveAs.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A,
ActionEvent.CTRL_MASK));
fileMenu.add(saveAs);
// add action listener to "Save As" option
saveAs.addActionListener(this);
reset = new JMenuItem("Reset");
reset.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R,
ActionEvent.CTRL_MASK));
fileMenu.add(reset);
// add action listener to "Reset" option
reset.addActionListener(this);
quit = new JMenuItem("Quit");
quit.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q,
ActionEvent.CTRL_MASK));
fileMenu.add(quit);
// add action listener to "Quit" option
quit.addActionListener(this);
}
public void actionPerformed(ActionEvent e) {
if(e.getSource().equals(newGame)) {
setEnabled(false);
// create dialog box prompting for the new board information
createNewWin = new Dialog1(main, "Create New Board", true);
// make it visible
createNewWin.setVisible(true);
fileSaved = null;
} else if(e.getSource().equals(open)) {
int returnVal = choose.showOpenDialog(main.win);
if(returnVal == JFileChooser.APPROVE_OPTION) {
boolean error = false;
File openFile = choose.getSelectedFile();
try {
FileInputStream fis = new FileInputStream(openFile);
ObjectInputStream ois = new ObjectInputStream(fis);
opener = ois.readObject();
SudokuBase sudoku = (SudokuBoard) opener;
ois.close();
} catch (Exception exc) {
exc.printStackTrace();
JOptionPane.showMessageDialog(main.win, "Error opening file.");
error = true;
}
// "opener" reads something and it is of type SudokuBase
if(opener != null && opener instanceof SudokuBase){
main.north.remove(main.rowColRegStates);
main.west.remove(main.symbols);
main.east.remove(main.view);
main.view = new SudokuView((SudokuBase) opener);
main.rowColRegStates = new ShowStates(main.view);
main.symbols = new SetSymbols(main.view);
main.north.add(main.rowColRegStates);
main.west.add(main.symbols);
main.east.add(main.view);
main.win.requestFocus();
fileSaved = openFile;
} else {
if(error) {
JOptionPane.showMessageDialog(main.win, "Incorrect file type.");
}
}
}
} else if(e.getSource().equals(save)) {
if(fileSaved == null) {
saveAsPrompt();
} else {
try {
FileOutputStream fos = new FileOutputStream(fileSaved);
ObjectOutputStream oos = new ObjectOutputStream(fos);
board.writeToStream(fos);
oos.writeObject(board);
oos.close();
} catch (Exception exc) {
JOptionPane.showMessageDialog(main.win, "Error saving file.");
}
}
} else if(e.getSource().equals(saveAs)) {
saveAsPrompt();
} else if(e.getSource().equals(reset)) {
int n = JOptionPane.showConfirmDialog(main.win,
"Any player values will" +
" be lost. Proceed?",
"Warning!", 2);
if(n == JOptionPane.OK_OPTION) {
main.board.reset();
main.view.repaint();
}
} else if(e.getSource().equals(quit)) {
closePrompt();
}
}
// This method prompts the user to choose a file to save to,
// and then saves the file.
private int saveAsPrompt() {
boolean saveError;
int rtn = choose.showSaveDialog(main.win);
if(rtn == JFileChooser.APPROVE_OPTION) {
saveError = false;
File fileSaveAs = choose.getSelectedFile();
try {
board.writeToStream(new FileOutputStream(fileSaveAs));
} catch (Exception e) {
JOptionPane.showMessageDialog(main.win, "Error saving file.");
saveError = true;
}
if(!saveError) {
fileSaved = fileSaveAs;
}
}
return rtn;
}
// This method prompts the user whether they want to save before
// closing, only if changes occurred.
private void closePrompt() {
if(true) {
int n = JOptionPane.showConfirmDialog(main.win, "Save game?");
if(n == JOptionPane.YES_OPTION) {
int saved = saveAsPrompt();
if(saved != JFileChooser.CANCEL_OPTION){
main.win.dispose();
}
} else if(n == JOptionPane.NO_OPTION) {
main.win.dispose();
}
}
else { // no changes were made
main.win.dispose();
}
}
}
Here's the class that holds the data (it is extended by SudokuBoard):
// Allow short name access to following classes
import java.util.Observable;
import java.io.InputStream;
import java.io.OutputStream;
public abstract class SudokuBase extends Observable {
// rows per region
private int rows;
// columns per region
private int columns;
// size of a region (rows * columns)
private int size;
// array of each element of entire sudoku board
private int[] grid;
// the masked 8-bit "given" value constant
private static final int GIVEN_MASK = 0x00000100;
// the bitwise complement of the masked "given" constant,
// which produces an unmasked constant
private static final int GIVEN_UNMASK = ~ GIVEN_MASK;
/**
* Enumerated type to store constants that indicate the "State" of
* a specified row, column, or region.
*/
public enum State {COMPLETE, INCOMPLETE, ERROR};
/**
* Constructs SudokuBase object.
*
* #param layoutRows The number of rows per region.
* #param layoutColumns The number of columns per region.
*/
public SudokuBase(int layoutRows, int layoutColumns) {
rows = layoutRows;
columns = layoutColumns;
size = columns * rows;
grid = new int[size*size];
}
/**
* Gets the number of rows per region.
*
* #return The rows per region.
*/
public int getRowsPerRegion() {
return rows;
}
/**
* Gets the number of columns per region.
*
* #return The columns per region.
*/
public int getColumnsPerRegion() {
return columns;
}
/**
* Gets the size of the region (rows * columns).
*
* #return The size of the region.
*/
public int getBoardSize() {
return size;
}
// gets the index of the specified row and column for the grid
private int getIndex(int row, int col) {
// handle invalid arguments
if(row < 0 || row >= size || col < 0 || col >= size) {
String msg = "Error in location";
throw new IllegalArgumentException(msg);
}
return row * size + col;
}
/**
* Gets the value of the element at the specified row
* and column on the grid.
*
* #param row The specified row.
* #param col The specified column.
* #return The value of the element at the specified row and column.
*/
public int getValue(int row, int col) {
return grid[getIndex(row, col)] & GIVEN_UNMASK;
}
/**
* Sets the desired value at the specified row and column.
*
* #param row The specified row.
* #param col The specified column.
* #param value The specified value to be set.
*/
public void setValue(int row, int col, int value) {
// handle invalid argument
if(value < 0 || value > size) {
String msg = "Value out of range: " + value;
throw new IllegalArgumentException(msg);
}
// handle attempt to set a value for a "given" location
if(isGiven(row, col)) {
String msg = "Cannot set given location: " + row + ", " + col;
throw new IllegalStateException(msg);
}
grid[getIndex(row, col)] = value;
setChanged();
notifyObservers();
}
/**
* This method checks the status of the "givens" bit.
*
* #param row The specified row.
* #param col The specified column.
* #return Whether or not the specified location is a "given" value.
*/
public boolean isGiven(int row, int col) {
return (grid[getIndex(row, col)] & GIVEN_MASK) == GIVEN_MASK;
}
/**
* This method sets non-zero values on the Sudoku board with the
* "givens" bit.
*/
public void fixGivens() {
for(int i = 0; i < grid.length; i++)
if(grid[i] != 0)
grid[i] |= GIVEN_MASK;
setChanged();
notifyObservers();
}
/**
* This abstract method gets the "State" (COMPLETE, INCOMPLETE,
* or ERROR) of the specified row.
*
* #param n The specified row.
* #return The "State" of the row.
*/
public abstract State getRowState(int n);
/**
* This abstract method gets the "State" (COMPLETE, INCOMPLETE,
* or ERROR) of the specified column.
*
* #param n The specified column.
* #return The "State" of the column.
*/
public abstract State getColumnState(int n);
/**
* This abstract method gets the "State" (COMPLETE, INCOMPLETE,
* or ERROR) of the specified region.
*
* #param n The specified region.
* #return The "State" of the region.
*/
public abstract State getRegionState(int n);
/**
* Represents the Sudoku board as a grid of appropriate characters.
*
* #return The string representation of the Sudoku board.
*/
public String toString() {
String board = "";
for(int i = 0; i < size; i ++) {
for(int j = 0; j < size; j ++)
board += charFor(i, j) + " ";
board += "\n";
}
return board;
}
// this method provides a character for all possible values encountered on the
// Sudoku board, to be utilized in "toString()"
private String charFor(int i, int j) {
int v = getValue(i, j);
// negative value (invalid)
if(v < 0) {
return "?";
} else if(v == 0) { // blank or zero value
return ".";
} else if(v < 36) { // value from 1 to (size * size)
return Character.toString(Character.forDigit(v, 36)).toUpperCase();
} else { // non-numeric input or v >= size * size (both invalid)
return "?";
}
}
/**
* This method reads from an input stream.
*
* #param is The input stream to read from.
*/
protected void readFromStream(InputStream is) {
}
/**
* This method writes to an output stream.
*
* #param os The output stream to write to.
*/
protected void writeToStream(OutputStream os) {
try {
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.close();
} catch(IOException e) {
}
}
/**
* Gets the "raw" value directly, not having checked whether there is an
* unfixed error message.
*
* #param row The row where the raw value is located.
* #param col The column where the raw value is located.
* #return The raw value.
*/
protected int getRawValue(int row, int col) {
return grid[getIndex(row, col)];
}
/**
* Sets the raw value directly, not having checked whether there is an
* unfixed error message.
*
* #param row The row where the raw value is to be located.
* #param col The column where the raw value is to be located.
*/
protected void setRawValue(int row, int col, int value) {
grid[getIndex(row, col)] = value;
}
protected void reset() {
for(int row = 0; row < rows; row++) {
for(int col = 0; col < columns; col++) {
if(!isGiven(row, col)) {
grid[getIndex(row, col)] = 0;
}
}
}
}
}
Well I cannot give a full answer and I do not want to browse the full source code. But a few pointers for you to find some solution:
Never catch Exceptions like that while developing an application:
} catch (Exception e) {
JOptionPane.showMessageDialog(main.win, "Error saving file.");
saveError = true;
}
With this, you completely loose the chance to detect errors. At least add the following line to your exception handling:
e.printStackTrace();
Normally you would log the exception and so on, but with that you see the source of your error at the console. Better than nothing.
To your more specific problem:
You seem to write an Object to a file holding all the configuration. In your read method something goes wrong. Probably you do not read the same object as you write or something like that. Hard to say without any code. Try to get the exception stack trace and figure out what the problem is. If you cannot figure it out, edit your question with more specific information and I will try to give better directions.
EDIT:
Here is a small example showing serialization of objects for a Sudoku like game:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
public class SerializationExample {
public static void main(String args[]) throws IOException, ClassNotFoundException {
final File target = new File(System.getProperty("java.io.tmp"), "mySerializedObject.txt");
Map<Integer, Integer> initialState = new HashMap<Integer, Integer>();
initialState.put(1, 1);
initialState.put(21, 3);
// ...
GameState state = new GameState(10, initialState);
state.setField(2, 2);
state.setField(3, 8);
System.out.println("Game state before writing to file: " + state);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(target));
out.writeObject(state);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream(target));
Object gameStateReadFromFile = in.readObject();
GameState readGameState = (GameState)gameStateReadFromFile;
System.out.println("Read from file: " + readGameState);
}
private static class GameState implements Serializable {
private int[] fields;
private int boardSize;
private int[] fixedFields;
public GameState(int boardSize, Map<Integer, Integer> initialState) {
this.boardSize = boardSize;
this.fields = new int[boardSize * boardSize];
this.fixedFields = new int[this.fields.length];
for (Entry<Integer, Integer> entry : initialState.entrySet()) {
this.fixedFields[entry.getKey()] = entry.getValue();
}
}
public void setField(int index, int value) {
this.fields[index] = value;
}
#Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("\nFixed fields: ");
appendArray(builder, this.fixedFields);
builder.append("\nSet fields: ");
appendArray(builder, this.fields);
return builder.toString();
}
private void appendArray(StringBuilder builder, int[] fieldArray) {
for (int i = 0; i < fieldArray.length; ++i) {
if (fieldArray[i] != 0) {
builder.append("row ").append(i / this.boardSize).append(" column ").append(i % this.boardSize)
.append(" has value ")
.append(fieldArray[i]).append(",");
}
}
}
}
}

can't find source of inadvertent loop

I started refactoring this program I was working on and hit a major road block... I have one class that acts as a nucleus, with about 6 other smaller (but still important) classes working together to run the program... I took one method [called 'populate()'] out the nucleus class and made an entirely new class with it [called 'PopulationGenerator'], but when I try to create an object of the newly created class anywhere in the nucleus class I get stuck in a never ending loop of that new class
I've never had this issue when trying to create objects before... Here's the nucleus class before refactoring:
public class Simulator
{
// Constants representing configuration information for the simulation.
// The default width for the grid.
private static final int DEFAULT_WIDTH = 120;
// The default depth of the grid.
private static final int DEFAULT_DEPTH = 80;
// The probability that a fox will be created in any given grid position.
private static final double FOX_CREATION_PROBABILITY = 0.02;
// The probability that a rabbit will be created in any given grid position.
private static final double RABBIT_CREATION_PROBABILITY = 0.08;
// List of animals in the field.
private List<Animal> animals;
// The current state of the field.
private Field field;
// The current step of the simulation.
private int step;
// A graphical view of the simulation.
private SimulatorView view;
/**
* Construct a simulation field with default size.
*/
public Simulator()
{
this(DEFAULT_DEPTH, DEFAULT_WIDTH);
}
/**
* Create a simulation field with the given size.
* #param depth Depth of the field. Must be greater than zero.
* #param width Width of the field. Must be greater than zero.
*/
public Simulator(int depth, int width)
{
if(width <= 0 || depth <= 0) {
System.out.println("The dimensions must be greater than zero.");
System.out.println("Using default values.");
depth = DEFAULT_DEPTH;
width = DEFAULT_WIDTH;
}
animals = new ArrayList<Animal>();
field = new Field(depth, width);
// Create a view of the state of each location in the field.
view = new SimulatorView(depth, width);
view.setColor(Rabbit.class, Color.orange);
view.setColor(Fox.class, Color.blue);
// Setup a valid starting point.
reset();
}
/**
* Run the simulation from its current state for a reasonably long period,
* (4000 steps).
*/
public void runLongSimulation()
{
simulate(4000);
}
/**
* Run the simulation from its current state for the given number of steps.
* Stop before the given number of steps if it ceases to be viable.
* #param numSteps The number of steps to run for.
*/
public void simulate(int numSteps)
{
for(int step = 1; step <= numSteps && view.isViable(field); step++) {
simulateOneStep();
}
}
/**
* Run the simulation from its current state for a single step.
* Iterate over the whole field updating the state of each
* fox and rabbit.
*/
public void simulateOneStep()
{
step++;
// Provide space for newborn animals.
List<Animal> newAnimals = new ArrayList<Animal>();
// Let all rabbits act.
for(Iterator<Animal> it = animals.iterator(); it.hasNext(); ) {
Animal animal = it.next();
animal.act(newAnimals);
if(! animal.isAlive()) {
it.remove();
}
}
// Add the newly born foxes and rabbits to the main lists.
animals.addAll(newAnimals);
view.showStatus(step, field);
}
/**
* Reset the simulation to a starting position.
*/
public void reset()
{
step = 0;
animals.clear();
populate();
// Show the starting state in the view.
view.showStatus(step, field);
}
/**
* Randomly populate the field with foxes and rabbits.
*/
private void populate()
{
Random rand = Randomizer.getRandom();
field.clear();
for(int row = 0; row < field.getDepth(); row++) {
for(int col = 0; col < field.getWidth(); col++) {
if(rand.nextDouble() <= FOX_CREATION_PROBABILITY) {
Location location = new Location(row, col);
Fox fox = new Fox(true, field, location);
animals.add(fox);
}
else if(rand.nextDouble() <= RABBIT_CREATION_PROBABILITY) {
Location location = new Location(row, col);
Rabbit rabbit = new Rabbit(true, field, location);
animals.add(rabbit);
}
// else leave the location empty.
}
}
}
}
EDIT:
Here's this same class AFTER refactoring ...
import java.util.Random;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import java.awt.Color;
/**
* A simple predator-prey simulator, based on a rectangular field
* containing rabbits and foxes.
*
* Update 10.40:
* Now *almost* decoupled from the concrete animal classes.
*
* #TWiSTED_CRYSTALS
*/
public class Simulator
{
// Constants representing configuration information for the simulation.
// The default width for the grid.
private static final int DEFAULT_WIDTH = 120;
// The default depth of the grid.
private static final int DEFAULT_DEPTH = 80;
// The current state of the field.
private Field field;
// The current step of the simulation.
private int step;
// A graphical view of the simulation.
private SimulatorView view;
//Population Generator class... coupled to fox and rabbit classes
private PopulationGenerator popGenerator;
// Lists of animals in the field. Separate lists are kept for ease of iteration.
private List<Animal> animals;
/**
* Construct a simulation field with default size.
*/
public Simulator()
{
this(DEFAULT_DEPTH, DEFAULT_WIDTH);
}
/**
* Create a simulation field with the given size.
* #param depth Depth of the field. Must be greater than zero.
* #param width Width of the field. Must be greater than zero.
*/
public Simulator(int depth, int width)
{
if(width <= 0 || depth <= 0) {
System.out.println("The dimensions must be greater than zero.");
System.out.println("Using default values.");
depth = DEFAULT_DEPTH;
width = DEFAULT_WIDTH;
}
animals = new ArrayList<Animal>();
field = new Field(depth, width);
// Create a view of the state of each location in the field.
//
// view.setColor(Rabbit.class, Color.orange); // PG
// view.setColor(Fox.class, Color.blue); // PG
// Setup a valid starting point.
reset();
}
/**
* Run the simulation from its current state for a reasonably long period,
* (4000 steps).
*/
public void runLongSimulation()
{
simulate(4000);
}
/**
* Run the simulation from its current state for the given number of steps.
* Stop before the given number of steps if it ceases to be viable.
* #param numSteps The number of steps to run for.
*/
public void simulate(int numSteps)
{
for(int step = 1; step <= numSteps && view.isViable(field); step++) {
simulateOneStep();
}
}
/**
* Run the simulation from its current state for a single step.
* Iterate over the whole field updating the state of each
* fox and rabbit.
*/
public void simulateOneStep()
{
step++;
// Provide space for animals.
List<Animal> newAnimals = new ArrayList<Animal>();
// Let all animals act.
for(Iterator<Animal> it = animals.iterator(); it.hasNext(); ) {
Animal animal = it.next();
animal.act(newAnimals);
if(! animal.isAlive()) {
it.remove();
}
}
animals.addAll(newAnimals);
}
/**
* Reset the simulation to a starting position.
*/
public void reset()
{
PopulationGenerator popGenerator = new PopulationGenerator();
step = 0;
animals.clear();
popGenerator.populate();
// Show the starting state in the view.
view.showStatus(step, field);
}
public int getStep()
{
return step;
}
}
... and the new class
import java.util.ArrayList;
import java.util.Random;
import java.util.List;
import java.awt.Color;
public class PopulationGenerator
{
// The default width for the grid.
private static final int DEFAULT_WIDTH = 120;
// The default depth of the grid.
private static final int DEFAULT_DEPTH = 80;
// The probability that a fox will be created in any given grid position.
private static final double FOX_CREATION_PROBABILITY = 0.02;
// The probability that a rabbit will be created in any given grid position.
private static final double RABBIT_CREATION_PROBABILITY = 0.08;
// Lists of animals in the field. Separate lists are kept for ease of iteration.
private List<Animal> animals;
// The current state of the field.
private Field field;
// A graphical view of the simulation.
private SimulatorView view;
/**
* Constructor
*/
public PopulationGenerator()
{
animals = new ArrayList<Animal>();
field = new Field(DEFAULT_DEPTH, DEFAULT_WIDTH);
}
/**
* Randomly populate the field with foxes and rabbits.
*/
public void populate()
{
// Create a view of the state of each location in the field.
view = new SimulatorView(DEFAULT_DEPTH, DEFAULT_WIDTH);
view.setColor(Rabbit.class, Color.orange); // PG
view.setColor(Fox.class, Color.blue); // PG
Simulator simulator = new Simulator();
Random rand = Randomizer.getRandom();
field.clear();
for(int row = 0; row < field.getDepth(); row++) {
for(int col = 0; col < field.getWidth(); col++) {
if(rand.nextDouble() <= FOX_CREATION_PROBABILITY) {
Location location = new Location(row, col);
Fox fox = new Fox(true, field, location);
animals.add(fox);
}
else if(rand.nextDouble() <= RABBIT_CREATION_PROBABILITY) {
Location location = new Location(row, col);
Rabbit rabbit = new Rabbit(true, field, location);
animals.add(rabbit);
}
// else leave the location empty.
}
}
view.showStatus(simulator.getStep(), field);
}
}
here's the Field class that the PopulationGenerator calls... I havent changed this class in any way
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
/**
* Represent a rectangular grid of field positions.
* Each position is able to store a single animal.
*
* #TWiSTED_CRYSTALS
*/
public class Field
{
// A random number generator for providing random locations.
private static final Random rand = Randomizer.getRandom();
// The depth and width of the field.
private int depth, width;
// Storage for the animals.
private Object[][] field;
/**
* Represent a field of the given dimensions.
* #param depth The depth of the field.
* #param width The width of the field.
*/
public Field(int depth, int width)
{
this.depth = depth;
this.width = width;
field = new Object[depth][width];
}
/**
* Empty the field.
*/
public void clear()
{
for(int row = 0; row < depth; row++) {
for(int col = 0; col < width; col++) {
field[row][col] = null;
}
}
}
/**
* Clear the given location.
* #param location The location to clear.
*/
public void clear(Location location)
{
field[location.getRow()][location.getCol()] = null;
}
/**
* Place an animal at the given location.
* If there is already an animal at the location it will
* be lost.
* #param animal The animal to be placed.
* #param row Row coordinate of the location.
* #param col Column coordinate of the location.
*/
public void place(Object animal, int row, int col)
{
place(animal, new Location(row, col));
}
/**
* Place an animal at the given location.
* If there is already an animal at the location it will
* be lost.
* #param animal The animal to be placed.
* #param location Where to place the animal.
*/
public void place(Object animal, Location location)
{
field[location.getRow()][location.getCol()] = animal;
}
/**
* Return the animal at the given location, if any.
* #param location Where in the field.
* #return The animal at the given location, or null if there is none.
*/
public Object getObjectAt(Location location)
{
return getObjectAt(location.getRow(), location.getCol());
}
/**
* Return the animal at the given location, if any.
* #param row The desired row.
* #param col The desired column.
* #return The animal at the given location, or null if there is none.
*/
public Object getObjectAt(int row, int col)
{
return field[row][col];
}
/**
* Generate a random location that is adjacent to the
* given location, or is the same location.
* The returned location will be within the valid bounds
* of the field.
* #param location The location from which to generate an adjacency.
* #return A valid location within the grid area.
*/
public Location randomAdjacentLocation(Location location)
{
List<Location> adjacent = adjacentLocations(location);
return adjacent.get(0);
}
/**
* Get a shuffled list of the free adjacent locations.
* #param location Get locations adjacent to this.
* #return A list of free adjacent locations.
*/
public List<Location> getFreeAdjacentLocations(Location location)
{
List<Location> free = new LinkedList<Location>();
List<Location> adjacent = adjacentLocations(location);
for(Location next : adjacent) {
if(getObjectAt(next) == null) {
free.add(next);
}
}
return free;
}
/**
* Try to find a free location that is adjacent to the
* given location. If there is none, return null.
* The returned location will be within the valid bounds
* of the field.
* #param location The location from which to generate an adjacency.
* #return A valid location within the grid area.
*/
public Location freeAdjacentLocation(Location location)
{
// The available free ones.
List<Location> free = getFreeAdjacentLocations(location);
if(free.size() > 0) {
return free.get(0);
}
else {
return null;
}
}
/**
* Return a shuffled list of locations adjacent to the given one.
* The list will not include the location itself.
* All locations will lie within the grid.
* #param location The location from which to generate adjacencies.
* #return A list of locations adjacent to that given.
*/
public List<Location> adjacentLocations(Location location)
{
assert location != null : "Null location passed to adjacentLocations";
// The list of locations to be returned.
List<Location> locations = new LinkedList<Location>();
if(location != null) {
int row = location.getRow();
int col = location.getCol();
for(int roffset = -1; roffset <= 1; roffset++) {
int nextRow = row + roffset;
if(nextRow >= 0 && nextRow < depth) {
for(int coffset = -1; coffset <= 1; coffset++) {
int nextCol = col + coffset;
// Exclude invalid locations and the original location.
if(nextCol >= 0 && nextCol < width && (roffset != 0 || coffset != 0)) {
locations.add(new Location(nextRow, nextCol));
}
}
}
}
// Shuffle the list. Several other methods rely on the list
// being in a random order.
Collections.shuffle(locations, rand);
}
return locations;
}
/**
* Return the depth of the field.
* #return The depth of the field.
*/
public int getDepth()
{
return depth;
}
/**
* Return the width of the field.
* #return The width of the field.
*/
public int getWidth()
{
return width;
}
}
your problem is not in the Field class but below it. The Simulator constructor calls reset() which creates a new PopulationGenerator object, then calls populate() on that object. The populate() method calls Simulator simulator = new Simulator(); which creates a new Simulator object which continues the cycle. Solution: don't create a new Simulator object in PopulationGenerator, but instead pass the existing simulator to PopulationGenerator through its constructor or through a setSimulator(...) method.
e.g.,
class PopulationGenerator {
// ... etc...
private Simulator simulator; // null to begin with
// pass Simulator instance into constructor.
// Probably will need to do the same with SimulatorView
public PopulationGenerator(Simulator simulator, int depth, int width) {
this.simulator = simulator; // set the instance
// ... more code etc...
}
public void populate() {
// don't re-create Simulator here but rather use the instance passed in
}
}

Categories

Resources