Within polygon region calculation - java

I got the following code where I read GPS coordinates from a XML file then i want to check that my current location (lat and long) is within this polygon region... Not sure if am doing something wrong :( Look like my IsInsideRegion method is failing. Any advise welcome.
public class Point {
private double _latitude = 0d;
private double _longitude = 0d;
public Point() {
}
public Point(double aLatitude, double aLongitude) {
_latitude = aLatitude;
_longitude = aLongitude;
}
public Point(String pointData) throws Exception {
FromString(pointData);
}
public static String ToString(Point point) {
StringBuilder result = new StringBuilder();
result.append(point._latitude).append(",").append(point._longitude);
return result.toString();
}
public void FromString(String pointData) throws Exception {
String[] fields = pointData.split(",");
if (fields.length != 2)
throw new FormatException("Invalid input data: " + pointData + " in " + this.getClass().getName());
_latitude = parseDouble(fields[0]);
_longitude = parseDouble(fields[1]);
}
private double parseDouble(String aValue) {
double result = 0d;
try {
result = Double.parseDouble(aValue);
} catch (Exception ex) {
}
return result;
}
public double getLatitude() {
return _latitude;
}
public double getLongitude() {
return _longitude;
}
public boolean IsInsideRegion(double latitude, double longitude)
{
//Log.d("<GPD><POLY>","PR :" + String.valueOf(_pointList));
Boolean result = false;
if (_pointList != null && _pointList.size() > 0) {
int Index1 = 0;
int Index2 = _pointList.size() - 1;
while (Index1 < _pointList.size())
{
if ((((_pointList.get(Index1).getLongitude() <= longitude) && (longitude < _pointList.get(Index2).getLongitude())) || ((_pointList.get(Index2).getLongitude() <= longitude) && (longitude < _pointList.get(Index1).getLongitude()))))
{
if (latitude < (_pointList.get(Index2).getLatitude() - _pointList.get(Index1).getLatitude()) * (longitude - _pointList.get(Index1).getLongitude()) / (_pointList.get(Index2).getLongitude() - _pointList.get(Index1).getLongitude()) + _pointList.get(Index1).getLatitude())
result = !result;
}
Index2 = Index1;
Index1++;
}
}
if (result)
Log.d("<GPD><POLY>", "In Polygon Region:" + this.getId());
return result;
}
private void calculateMinMaxLatLong(double aMinLatitude, double aMaxLatitude, double aMinLongitude, double aMaxLongitude)
{
/*
_minLatitude = 0d;
_maxLatitude = 0d;
_minLongitude = 0d;
_maxLongitude = 0d;
for (Point currentPoint: _pointList)
{
if (currentPoint.getLatitude() < aMinLatitude)
_minLatitude = currentPoint.getLatitude();
if (currentPoint.getLatitude() > aMaxLatitude)
_maxLatitude = currentPoint.getLatitude();
if (currentPoint.getLongitude() < aMinLongitude)
_minLongitude = currentPoint.getLongitude();
if (currentPoint.getLongitude() > aMaxLongitude)
_maxLongitude = currentPoint.getLongitude();
}*/
double smallestLat = _pointList.get(0).getLatitude();
double largestLat = _pointList.get(0).getLatitude();
double smallestLong = _pointList.get(0).getLongitude();
double largestLong = _pointList.get(0).getLongitude();
for (int x = 0; x < _pointList.size(); x++){
if(smallestLat > _pointList.get(x).getLatitude())
smallestLat = _pointList.get(x).getLatitude();
if(largestLat < _pointList.get(x).getLatitude())
largestLat = _pointList.get(x).getLatitude();
if(smallestLong > _pointList.get(x).getLongitude())
smallestLong = _pointList.get(x).getLongitude();
if(largestLong < _pointList.get(x).getLongitude())
largestLong = _pointList.get(x).getLongitude();
}
_minLatitude = smallestLat;
_maxLatitude = largestLat;
_minLongitude = smallestLong;
_maxLongitude = largestLong;
}
public RegionChecker()
{}
public RegionChecker(boolean aEnterFirstOnly) {
_enterFirstOnly = aEnterFirstOnly;
}
public List<Region> CheckGpsPosition(List<Region> aActiveRegion, List<Region> aRegionList, MyLocation aLocation, IRegionEventListener aListener) {
List<Region> result = new ArrayList<Region>();
Log.i("RegionChecker", "aRegionList " + String.valueOf(aRegionList.size()));
boolean regionChanges = false;
List<Region> overlappingPositionsList = new Regions().InsideRegions(aRegionList, aLocation.getLatitude(), aLocation.getLongitude());
if (_enterFirstOnly)
{
if (overlappingPositionsList != null && overlappingPositionsList.size() > 0)
{
Region firstRegion = overlappingPositionsList.get(0);
overlappingPositionsList.clear();
overlappingPositionsList.add(firstRegion);
}
}
Log.i("RegionChecker", "_activeRegions " + String.valueOf(aActiveRegion.size()));
Log.i("RegionChecker", "overlappingPositionsList " + String.valueOf(overlappingPositionsList.size()));
for(Region currentRegion: overlappingPositionsList) {
Log.i("overlap check", String.valueOf(aActiveRegion.contains(currentRegion)));
if (!aActiveRegion.contains(currentRegion)) {
regionChanges = true;
aListener.onRegionEnter(currentRegion, aLocation);
}
}
for(Region currentRegion: aActiveRegion) {
if (!overlappingPositionsList.contains(currentRegion)) {
regionChanges = true;
aListener.OnRegionExit(currentRegion, aLocation);
}
}
result = overlappingPositionsList;
if (regionChanges)
aListener.OnCurrentRegionsChanged(result);
return result;
}

I think you have to modify your region checker.
It should use longitude or latitude but not both. So it checks how many times a horizontal ray crosses the lines between vertices. Or it checks how many times a vertical line does.
It needs to handle the point pairs and indices 0 & 1, 1 & 2 ... n-2 & n-1 and n-1 & 0.
I didn't see where you do that last pair, but there is a lot of code to read.

Related

A* Pathfinding really slow

I have an A* pathfinding algorithm that I've used for a Spigot plugin which worked fine. I then added a requirements system so it won't try and pathfind through places it shouldn't. Now it seems REALLY slow, and it looks like it has nothing to do with the requirements code itself, and more to do with the algorithm having many more incorrect paths when calculating. I seem to be getting 1500ms+ on this, which is definitely not good xD
Here is the pathfinder code:
public Path calculate(PathfinderGoal goal, PathScorer scorer, List<PathRequirement> requirements, int maxNodes) {
PathNode start = toNode(npc.getLocation());
PathNode end = toNode(goal.getLocation());
List<PathNode> open = new ArrayList<>() {{ add(start); }};
List<PathNode> navigated = new ArrayList<>();
start.setF(scorer.computeCost(start, end));
Timer timer = new Timer().start();
while (!open.isEmpty()) {
PathNode current = null;
for (PathNode node : open) {
if (current == null || node.getH() < current.getH()) {
current = node;
}
}
if (scorer.computeCost(current, end) < 1 || (navigated.size() >= maxNodes && maxNodes != -1)) {
navigated.add(navigated.size() < maxNodes ? end : current);
return reconstruct(navigated, navigated.size() - 1);
}
open.remove(current);
current.close();
for (PathNode node : current.getNeighbors()) {
if (node.isClosed()) {
continue;
}
double tentG = current.getG() + scorer.computeCost(current, node);
if (!open.contains(node) || tentG < node.getG()) {
boolean requirementsMet = true;
for (PathRequirement requirement : requirements) {
requirement.setNavigated(navigated);
if (!navigated.isEmpty() && !requirement.canMoveToNewNode(navigated.get(navigated.size() - 1), node)) {
requirementsMet = false;
break;
}
}
if (!navigated.contains(current)) {
navigated.add(current);
}
node.setG(tentG);
node.setH(scorer.computeCost(node, end));
node.setF(tentG + node.getH());
if (!open.contains(node) && requirementsMet) {
open.add(node);
}
}
}
Bukkit.broadcastMessage("Open Set Size: " + open.size());
Bukkit.broadcastMessage(timer.stop() + "ms");
}
return null;
}
private Path reconstruct(List<PathNode> navigated, int index) {
final PathNode current = navigated.get(index);
Path withCurrent = new Path(new ArrayList<>() {{ add(current); }});
if (index > 0 && navigated.contains(current)) {
return reconstruct(navigated, index - 1).append(withCurrent);
}
return withCurrent;
}
And here is the PathNode class:
public PathNode(Pathfinder pathfinder, int x, int y, int z) {
this.pathfinder = pathfinder;
this.x = x;
this.y = y;
this.z = z;
}
#Override
public boolean equals(Object other) {
if (!(other instanceof PathNode otherNode)) {
return false;
}
return otherNode.x == x && otherNode.y == y && otherNode.z == z;
}
public List<PathNode> getNeighbors() {
return new ArrayList<>() {
{
for (int x = -1; x <= 1; x++) {
for (int y = -1; y <= 1; y++) {
for (int z = -1; z <= 1; z++) {
add(new PathNode(pathfinder, PathNode.this.x + x, PathNode.this.y + y, PathNode.this.z + z));
}
}
}
}
};
}
public Location getLocation() {
return new Location(pathfinder.getNPC().getLocation().getWorld(), x, y, z);
}
public double getF() {
return F;
}
public void setF(double f) {
this.F = f;
}
public double getG() {
return G;
}
public void setG(double g) {
this.G = g;
}
public double getH() {
return H;
}
public void setH(double h) {
this.H = h;
}
public boolean isClosed() {
return closed;
}
public void close() {
this.closed = true;
}
Valid Requirements Class:
public class ValidPathRequirement extends PathRequirement {
#Override
public boolean canMoveToNewNode(PathNode from, PathNode to) {
Block fromBlock = from.getLocation().getBlock();
Block toBlock = to.getLocation().getBlock();
boolean validHeight = toBlock.getType().isAir() && toBlock.getRelative(BlockFace.UP).getType().isAir(); // checks if is player height
boolean validGround = toBlock.getRelative(BlockFace.DOWN).getType().isSolid(); // is there a block underneath that they can stand on?
boolean validFromPrev = toBlock.getLocation().subtract(fromBlock.getLocation()).getY() <= 1; // is it max one block higher than the last one?
// is this one causing issues?
Location fromLocDist = from.getLocation().clone();
Location toLocDist = to.getLocation().clone();
toLocDist.setY(fromLocDist.getY());
boolean validDistance = fromLocDist.distance(toLocDist) <= 1;
return validHeight && validGround && validFromPrev;
}
}
Without looking at the rest of the algorithm, the first thing that stands out is that your data structures are incorrect. The "open" list needs to be a Priority Queue, and "closed" (or "navigated") should be a set.

Error: Could not find or load main class classname [duplicate]

This question already has answers here:
What does "Could not find or load main class" mean?
(61 answers)
Closed 2 years ago.
I've been working on a project recently and I have come to the end of my compilation, moving out of Eclipse and into cmd. I have a single .java names PA3 containing multiple classes.
My task is to compile the program with:
javac PA3.java
And to run the program with the command:
java PA3 test.dat
where test.dat is a placeholder (no need to worry about that)
When I compile, all the classes compile properly as shown:
here
All I seem to run into is issues regarding
Error: Could not find or load main class PA3
Caused by: java.lang.ClassNotFoundException: PA3
And my code is below:
(Mind you it is messy and a little over the place at the moment)
'''
import linkedlist.MyPolygons;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class PA3
{
//================================MAIN================================//
public static void main(String[] args)
{
MyPolygons.main(args);
};
}
class MyPolygons{
//================================MAIN================================//
public static void main(String[] args)
{
MyPolygons poly = Import(new ExtendedFile("src\\linkedlist\\text.txt"));
poly.display();
}
//Represent a node of the doubly linked list
class Node{
PlanarShape data;
String type;
Node previous;
Node next;
public Node(PlanarShape data, String type) {
this.data = data;
this.type = type;
}
}
//Represent the head and tail of the doubly linked list
Node head, tail = null;
public void sort()
{
Node current = head;
boolean changed = true;
while(changed)
{
changed = false;
Node next;
for(int i = 0; i < 50; i++)
{
current = head;
while((next = current.next) != null)
{
if(current.data.getArea(current.type) > next.data.getArea(next.type))
{
//System.out.println("Current.data: " + current.data.getArea(current.type) + " Next.data: " + next.data.getArea(next.type));
PlanarShape temp;
temp = current.data;
current.data = next.data;
next.data = temp;
String temps;
temps = current.type;
current.type = next.type;
next.type = temps;
current = next;
}
current = next;
}
}
}
}
//addNode() will add a node to the list
public void addNode(PlanarShape data, String type) {
//Create a new node
Node newNode = new Node(data, type);
//If list is empty
if(head == null) {
//Both head and tail will point to newNode
head = tail = newNode;
//head's previous will point to null
head.previous = null;
//tail's next will point to null, as it is the last node of the list
tail.next = null;
}
else {
//newNode will be added after tail such that tail's next will point to newNode
tail.next = newNode;
//newNode's previous will point to tail
newNode.previous = tail;
//newNode will become new tail
tail = newNode;
//As it is last node, tail's next will point to null
tail.next = null;
}
}
//display() will print out the nodes of the list
public void display()
{
//Node current will point to head
Node current = head;
if(head == null)
{
System.out.println("List is empty");
return;
}
System.out.println("Nodes of a Double linked list: ");
System.out.println("Unsorted List: ");
while(current != null)
{
System.out.print(current.type);
System.out.print("[");
//Prints each node by incrementing the pointer.
System.out.print(current.data);
if (current.type == "CIRC= ")
{
System.out.print(", ");
System.out.print(getRad());
}
System.out.println(String.format("]: %.2f", current.data.getArea(current.type)));
current = current.next;
}
sort();
System.out.println("Sorted List: ");
current = head;
while(current != null)
{
System.out.print(current.type);
System.out.print("[");
//Prints each node by incrementing the pointer.
System.out.print(current.data);
if (current.type == "CIRC= ")
{
System.out.print(", ");
System.out.print(PlanarShape.returnRadius());
}
System.out.println(String.format("]: %.2f", current.data.getArea(current.type)));
//System.out.println(current.data.originDistance());
current = current.next;
}
}
static float radius;
static float vertlength;
static PlanarShape PlanarShape = new PlanarShape();
public static MyPolygons Import(ExtendedFile f)
{
String[] lines = f.readLines();
MyPolygons poly = new MyPolygons();
for (int i = 0; i < lines.length; i++)
{
PlanarShape PlanarShape = new PlanarShape();
String[] array = lines[i].split(" ");
String shapetype = array[0];
switch(shapetype)
{
case "P":
for(int k = 2; k < array.length-1; k++)
{
float x = Float.parseFloat(array[k]);
k++;
float y = Float.parseFloat(array[k]);
PlanarShape.addVerticy(new Point(x, y));
vertlength = ((array.length-2)/2);
}
//Add nodes to the list
poly.addNode(PlanarShape, "POLY= ");
break;
case "C":
for(int k = 1; k < array.length-1; k++)
{
PlanarShape.addRadius(Float.parseFloat(array[array.length-1]));
float x = Float.parseFloat(array[k]);
k++;
float y = Float.parseFloat(array[k]);
PlanarShape.addVerticy(new Point(x, y));
}
poly.addNode(PlanarShape, "CIRC= ");
break;
case "S":
for(int k = 1; k < array.length-1; k++)
{
float x = Float.parseFloat(array[k]);
k++;
float y = Float.parseFloat(array[k]);
PlanarShape.addVerticy(new Point(x, y));
}
//Add nodes to the list
poly.addNode(PlanarShape, "SEMI= ");
break;
}
}
return poly;
}
public float getRad()
{
return PlanarShape.returnRadius();
}
public float getLength()
{
return vertlength;
}
}
#SuppressWarnings("serial")
class ExtendedFile extends File
{
public ExtendedFile(String pathname) {
super(pathname);
}
public String[] readLines()
{
try
{
FileReader fr = new FileReader(this);
BufferedReader br = new BufferedReader(fr);
Object[] buffer = br.lines().toArray();
String[] lines = Arrays.copyOf(buffer, buffer.length, String[].class);
fr.close(); br.close();
return lines ;
}
catch (IOException e)
{
return new String[0];
}
}
}
class Point
{
float x;
float y;
Point(float ex, float why)
{
x = ex;
y = why;
}
public String toString()
{
return String.format("(%.2f , %.2f)", x, y);
}
public float getX()
{
return x;
}
public float getY()
{
return y;
}
public double dto()
{
return Math.sqrt(Math.pow(x, 2)+Math.pow(y, 2));
}
}
class PlanarShape
{
List < Point > verticies = new ArrayList < Point > ();
float radius;
public void addRadius(float rad)
{
radius = rad;
}
public float returnRadius()
{
return radius;
}
public void addVerticy(Point toAdd)
{
verticies.add(toAdd);
}
public String toString()
{
String buffer = "";
for (int i = 0; i < verticies.size(); i++)
{
buffer += verticies.get(i).toString();
}
return buffer;
}
public Point[] getVerticies()
{
Object[] buffer = verticies.toArray();
return Arrays.copyOf(buffer, buffer.length, Point[].class);
}
public float polyarea;
public float circarea;
public float semiarea;
public float getArea(String type)
{
float area = 0;
switch(type)
{
case "POLY= ":
{
for(int n = 0; n < verticies.size(); n++)
{
area += verticies.get(n).getY()*verticies.get(n == verticies.size()-1?0:n+1).getX() - verticies.get(n).getX()*verticies.get(n == verticies.size()-1?0:n+1).getY();
}
if(area < 0)
{
area = -area;
}
area /= 2;
polyarea = area;
break;
}
case "CIRC= ":
{
float pi = 3.14159265359F;
float radius = returnRadius();
area = pi * radius * radius;
if(area < 0)
{
area = -area;
}
circarea = area;
break;
}
case "SEMI= ":
{
float x0 = verticies.get(0).getX();
float y0 = verticies.get(0).getY();
float x1 = verticies.get(1).getX();
float y1 = verticies.get(1).getY();
float y0y1;
if ((y0 - y1) < 0)
{
y0y1 = -(y0 - y1);
}
else
{
y0y1 = (y0 - y1);
}
float x0x1;
if ((x0 - x1) < 0)
{
x0x1 = -(x0 - x1);
}
else
{
x0x1 = (x0 - x1);
}
float x2 = x0 - y0y1;
float x3 = x0 + y0y1;
float y2 = y0 + x0x1;
float y3 = y0 - x0x1;
double diam = Math.sqrt((Math.pow((x2-x3), 2)) + ((Math.pow((y3 - y2), 2))));
float radius2 = ((float)diam)/2;
float pi = 3.14159265359F;
area = (pi * radius2 * radius2)/2;
if(area < 0)
{
area = -area;
}
break;
}
}
return area;
}
public float returnArea(String type)
{
switch(type)
{
case("POLY= "):
{
return polyarea;
}
case("CIRC= "):
{
return circarea;
}
case("SEMI= "):
{
return semiarea;
}
}
return 0;
}
}
class SemiCircle
{
List < Point > verticies = new ArrayList < Point > ();
public void addVerticy(Point toAdd)
{
verticies.add(toAdd);
}
public String toString()
{
String buffer = "";
for (int i = 0; i < verticies.size(); i++)
{
buffer += verticies.get(i).toString();
}
return buffer;
}
public Point[] getVerticies()
{
Object[] buffer = verticies.toArray();
return Arrays.copyOf(buffer, buffer.length, Point[].class);
}
public float getArea()
{
float area = 0;
for(int n = 0; n < verticies.size(); n++)
{
area += verticies.get(n).getY()*verticies.get(n == verticies.size()-1?0:n+1).getX() - verticies.get(n).getX()*verticies.get(n == verticies.size()-1?0:n+1).getY();
}
return area;
}
public double originDistance()
{
double distance;
distance = Math.sqrt((Math.pow((verticies.get(1).getX()),2))+(Math.pow((verticies.get(1).getY()),2)));
System.out.println(distance);
return distance;
}
}
class Polygon
{
List < Point > verticies = new ArrayList < Point > ();
public void addVerticy(Point toAdd)
{
verticies.add(toAdd);
}
public String toString()
{
String buffer = "";
for (int i = 0; i < verticies.size(); i++)
{
buffer += verticies.get(i).toString();
}
return buffer;
}
public Point[] getVerticies()
{
Object[] buffer = verticies.toArray();
return Arrays.copyOf(buffer, buffer.length, Point[].class);
}
public float getArea()
{
float area = 0;
for(int n = 0; n < verticies.size(); n++)
{
area += verticies.get(n).getY()*verticies.get(n == verticies.size()-1?0:n+1).getX() - verticies.get(n).getX()*verticies.get(n == verticies.size()-1?0:n+1).getY();
}
if(area < 0)
{
area = -area;
}
area /= 2;
return area;
}
public double originDistance()
{
double distance = Math.sqrt(Math.pow((verticies.get(1).getX()), 2)+Math.pow((verticies.get(1).getY()), 2));
System.out.println(distance);
return distance;
}
}
class Circle
{
List < Point > verticies = new ArrayList < Point > ();
public void addVerticy(Point toAdd)
{
verticies.add(toAdd);
}
public String toString()
{
String buffer = "";
for (int i = 0; i < verticies.size(); i++)
{
buffer += verticies.get(i).toString();
}
return buffer;
}
public Point[] getVerticies()
{
Object[] buffer = verticies.toArray();
return Arrays.copyOf(buffer, buffer.length, Point[].class);
}
public float getArea()
{
float pi = 3.14159265359F;
MyPolygons polygon = new MyPolygons();
float radius = polygon.getRad();
float area = 0;
area = pi * radius * radius;
if(area < 0)
{
area = -area;
}
return area;
}
public double originDistance()
{
double distance;
distance = Math.sqrt((Math.pow((verticies.get(0).getX()),2))+(Math.pow((verticies.get(0).getY()),2)));
System.out.println(distance);
return distance;
}
}
Edit: Removing the 'linkedlist.package' and pulling the .java and .text files, they still retain the same error when compiling and running
If you want to run files that are in a package, make sure all your java files that are part of the package are in a folder named the package name (i.e. linkedlist).
Then, in the directory outside your package folder, do:
javac linkedlist/*.java
java linkedlist.PA3

Minimum Spanning Tree in Java

This is a ACM-ICPC practicing problem: http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=975. And the basic idea is to find the shortest weight of a minimum spanning tree. And I tried to solve it with Kruskal algorithm. I tested it myself and it seems to be working fine. However, when I submit it onto the uva online judge, the result is "wrong answer". Could any body see where is not correct? Thanks!
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
//import java.util.Comparator;
import java.util.List;
//import java.math.*;
public class Main {
public static void main(String[] args) throws Exception, IOException {
BufferedReader cin = new BufferedReader(new InputStreamReader(System.in));
int numOfCases = Integer.parseInt(cin.readLine());
while (numOfCases > 0) {
cin.readLine();
int numOfPoints = Integer.parseInt(cin.readLine());
List<Points> listOfPoints = new ArrayList<Points>();
List<Edge> listOfEdges = new ArrayList<Edge>();
double inkUsed = 0;
for (int i = 0; i < numOfPoints; i++) {
String[] tokens = cin.readLine().split(" ");
Points point = new Points(Double.parseDouble(tokens[0]),Double.parseDouble(tokens[1]));
listOfPoints.add(point);
}
for (int i = 0; i < listOfPoints.size(); i++) {
for (int j = i+1; j < listOfPoints.size(); j++) {
Edge edge = new Edge(listOfPoints.get(i), listOfPoints.get(j));
listOfEdges.add(edge);
}
}
Collections.sort(listOfEdges);
for (Edge e: listOfEdges) {
Points pointA = null;
Points pointB = null;
// Find pointA and pointB in the list "listOfPoints"
for (int i = 0; i < listOfPoints.size(); i++) {
if (listOfPoints.get(i).x == e.A.x && listOfPoints.get(i).y == e.A.y) {
pointA = listOfPoints.get(i);
}
if (listOfPoints.get(i).x == e.B.x && listOfPoints.get(i).y == e.B.y) {
pointB = listOfPoints.get(i);
}
}
Points rootOfA = findSet(pointA);
Points rootOfB = findSet(pointB);
if (rootOfA.x != rootOfB.x || rootOfA.y != rootOfB.y){ //add this edge
//System.out.println( "[" + "(" + pointA.x +"," + pointA.y+"); (" + pointB.x +"," + pointB.y+")"+ "]");
inkUsed += e.weight;
rootOfA.parent = rootOfB;
}
}
DecimalFormat df = new DecimalFormat("#.00");
System.out.println(df.format(inkUsed));
numOfCases--;
}
}
public static Points findSet(Points A) {
Points rootOfA = A;
while (rootOfA.parent != null) {
rootOfA = rootOfA.parent;
}
return rootOfA;
}
}
class Points {
public double x;
public double y;
public Points parent = null;
Points (double x, double y) {
this.x = x;
this.y = y;
}
}
class Edge implements Comparable<Edge> {
Points A;
Points B;
double weight;
Edge (Points var1, Points var2) {
A = var1;
B = var2;
weight = Math.sqrt(Math.pow((A.x-B.x), 2) + Math.pow((A.y-B.y), 2));
}
public String toString() {
return Double.toString(weight);
}
public int compareTo(Edge anotherEdge) {
if (this.weight < anotherEdge.weight) {
return -1;
} else if (this.weight == anotherEdge.weight) {
return 0;
} else {
return 1;
}
}
}

Viterbi algorithm in java

I'm doing the coursera NLP course and the first programming assignment is to build a Viterbi decoder. I think I'm really close to finishing it but there is some elusive bug which I cannot seem to be able to trace. Here is my code:
http://pastie.org/private/ksmbns3gjctedu1zxrehw
http://pastie.org/private/ssv6tc8dwnamn2qegdvww
So far I've debugged the "teaching" related functions so I can say that the parameters for the algorithms are being correctly estimated. Of particular interest is the viterbi() and findW() methods. The definition of the algorithm I'm using can be found here: http://www.cs.columbia.edu/~mcollins/hmms-spring2013.pdf on page 18.
One thing which I'm having hard time wrapping my head around is how am I supposed to update the backpointers for the special cases when K = {1, 2} (in my case this is 0 and 1, since I'm zero-indexing my array) respectively the parameters I'm using in those cases are q({TAGSET} | *, *) and q ({TAGSET} | *, {TAGSET}).
Hints rather than spoon-fed answers will also be highly appreciated!
Here's a simple implementation of viterbi decoder by Yusuke Shunyama =) http://cs.nyu.edu/yusuke/course/NLP/viterbi/Viterbi.java
/*
* Viterbi.java
* Toy Viterbi Decorder
*
* by Yusuke Shinyama <yusuke at cs . nyu . edu>
*
* Permission to use, copy, modify, distribute this software
* for any purpose is hereby granted without fee, provided
* that the above copyright notice appear in all copies and
* that both that copyright notice and this permission notice
* appear in supporting documentation.
*/
import java.awt.*;
import java.util.*;
import java.text.*;
import java.awt.event.*;
import java.applet.*;
class Symbol {
public String name;
public Symbol(String s) {
name = s;
}
}
class SymbolTable {
Hashtable table;
public SymbolTable() {
table = new Hashtable();
}
public Symbol intern(String s) {
s = s.toLowerCase();
Object sym = table.get(s);
if (sym == null) {
sym = new Symbol(s);
table.put(s, sym);
}
return (Symbol)sym;
}
}
class SymbolList {
Vector list;
public SymbolList() {
list = new Vector();
}
public int size() {
return list.size();
}
public void set(int index, Symbol sym) {
list.setElementAt(sym, index);
}
public void add(Symbol sym) {
list.addElement(sym);
}
public Symbol get(int index) {
return (Symbol) list.elementAt(index);
}
}
class IntegerList {
Vector list;
public IntegerList() {
list = new Vector();
}
public int size() {
return list.size();
}
public void set(int index, int i) {
list.setElementAt(new Integer(i), index);
}
public void add(int i) {
list.addElement(new Integer(i));
}
public int get(int index) {
return ((Integer)list.elementAt(index)).intValue();
}
}
class ProbTable {
Hashtable table;
public ProbTable() {
table = new Hashtable();
}
public void put(Object obj, double prob) {
table.put(obj, new Double(prob));
}
public double get(Object obj) {
Double prob = (Double)table.get(obj);
if (prob == null) {
return 0.0;
}
return prob.doubleValue();
}
// normalize probability
public void normalize() {
double total = 0.0;
for(Enumeration e = table.elements() ; e.hasMoreElements() ;) {
total += ((Double)e.nextElement()).doubleValue();
}
if (total == 0.0) {
return; // div by zero!
}
for(Enumeration e = table.keys() ; e.hasMoreElements() ;) {
Object k = e.nextElement();
double prob = ((Double)table.get(k)).doubleValue();
table.put(k, new Double(prob / total));
}
}
}
class State {
public String name;
ProbTable emits;
ProbTable linksto;
public State(String s) {
name = s;
emits = new ProbTable();
linksto = new ProbTable();
}
public void normalize() {
emits.normalize();
linksto.normalize();
}
public void addSymbol(Symbol sym, double prob) {
emits.put(sym, prob);
}
public double emitprob(Symbol sym) {
return emits.get(sym);
}
public void addLink(State st, double prob) {
linksto.put(st, prob);
}
public double transprob(State st) {
return linksto.get(st);
}
}
class StateTable {
Hashtable table;
public StateTable() {
table = new Hashtable();
}
public State get(String s) {
s = s.toUpperCase();
State st = (State)table.get(s);
if (st == null) {
st = new State(s);
table.put(s, st);
}
return st;
}
}
class StateIDTable {
Hashtable table;
public StateIDTable() {
table = new Hashtable();
}
public void put(State obj, int i) {
table.put(obj, new Integer(i));
}
public int get(State obj) {
Integer i = (Integer)table.get(obj);
if (i == null) {
return 0;
}
return i.intValue();
}
}
class StateList {
Vector list;
public StateList() {
list = new Vector();
}
public int size() {
return list.size();
}
public void set(int index, State st) {
list.setElementAt(st, index);
}
public void add(State st) {
list.addElement(st);
}
public State get(int index) {
return (State) list.elementAt(index);
}
}
class HMMCanvas extends Canvas {
static final int grid_x = 60;
static final int grid_y = 40;
static final int offset_x = 70;
static final int offset_y = 30;
static final int offset_y2 = 10;
static final int offset_y3 = 65;
static final int col_x = 40;
static final int col_y = 10;
static final int state_r = 10;
static final Color state_fill = Color.white;
static final Color state_fill_maximum = Color.yellow;
static final Color state_fill_best = Color.red;
static final Color state_boundery = Color.black;
static final Color link_normal = Color.green;
static final Color link_processed = Color.blue;
static final Color link_maximum = Color.red;
HMMDecoder hmm;
public HMMCanvas() {
setBackground(Color.white);
setSize(400,300);
}
public void setHMM(HMMDecoder h) {
hmm = h;
}
private void drawState(Graphics g, int x, int y, Color c) {
x = x * grid_x + offset_x;
y = y * grid_y + offset_y;
g.setColor(c);
g.fillOval(x-state_r, y-state_r, state_r*2, state_r*2);
g.setColor(state_boundery);
g.drawOval(x-state_r, y-state_r, state_r*2, state_r*2);
}
private void drawLink(Graphics g, int x, int y0, int y1, Color c) {
int x0 = grid_x * x + offset_x;
int x1 = grid_x * (x+1) + offset_x;
y0 = y0 * grid_y + offset_y;
y1 = y1 * grid_y + offset_y;
g.setColor(c);
g.drawLine(x0, y0, x1, y1);
}
private void drawCenterString(Graphics g, String s, int x, int y) {
x = x - g.getFontMetrics().stringWidth(s)/2;
g.setColor(Color.black);
g.drawString(s, x, y+5);
}
private void drawRightString(Graphics g, String s, int x, int y) {
x = x - g.getFontMetrics().stringWidth(s);
g.setColor(Color.black);
g.drawString(s, x, y+5);
}
public void paint(Graphics g) {
if (hmm == null) {
return;
}
DecimalFormat form = new DecimalFormat("0.0000");
int nsymbols = hmm.symbols.size();
int nstates = hmm.states.size();
// complete graph.
for(int i = 0; i < nsymbols; i++) {
int offset_ymax = offset_y2+nstates*grid_y;
if (i < nsymbols-1) {
for(int y1 = 0; y1 < nstates; y1++) {
for(int y0 = 0; y0 < nstates; y0++) {
Color c = link_normal;
if (hmm.stage == i+1 && hmm.i0 == y0 && hmm.i1 == y1) {
c = link_processed;
}
if (hmm.matrix_prevstate[i+1][y1] == y0) {
c = link_maximum;
}
drawLink(g, i, y0, y1, c);
if (c == link_maximum && 0 < i) {
double transprob = hmm.states.get(y0).transprob(hmm.states.get(y1));
drawCenterString(g, form.format(transprob),
offset_x + i*grid_x + grid_x/2, offset_ymax);
offset_ymax = offset_ymax + 16;
}
}
}
}
// state circles.
for(int y = 0; y < nstates; y++) {
Color c = state_fill;
if (hmm.matrix_prevstate[i][y] != -1) {
c = state_fill_maximum;
}
if (hmm.sequence.size() == nsymbols &&
hmm.sequence.get(nsymbols-1-i) == y) {
c = state_fill_best;
}
drawState(g, i, y, c);
}
}
// max probability.
for(int i = 0; i < nsymbols; i++) {
for(int y1 = 0; y1 < nstates; y1++) {
if (hmm.matrix_prevstate[i][y1] != -1) {
drawCenterString(g, form.format(hmm.matrix_maxprob[i][y1]),
offset_x+i*grid_x, offset_y+y1*grid_y);
}
}
}
// captions (symbols atop)
for(int i = 0; i < nsymbols; i++) {
drawCenterString(g, hmm.symbols.get(i).name, offset_x+i*grid_x, col_y);
}
// captions (states in left)
for(int y = 0; y < nstates; y++) {
drawRightString(g, hmm.states.get(y).name, col_x, offset_y+y*grid_y);
}
// status bar
g.setColor(Color.black);
g.drawString(hmm.status, col_x, offset_y3+nstates*grid_y);
g.drawString(hmm.status2, col_x, offset_y3+nstates*grid_y+16);
}
}
class HMMDecoder {
StateList states;
int state_start;
int state_end;
public IntegerList sequence;
public double[][] matrix_maxprob;
public int[][] matrix_prevstate;
public SymbolList symbols;
public double probmax;
public int stage, i0, i1;
public boolean laststage;
public String status, status2;
public HMMDecoder() {
status = "Not initialized.";
status2 = "";
states = new StateList();
}
public void addStartState(State st) {
state_start = states.size(); // get current index
states.add(st);
}
public void addNormalState(State st) {
states.add(st);
}
public void addEndState(State st) {
state_end = states.size(); // get current index
states.add(st);
}
// for debugging.
public void showmatrix() {
for(int i = 0; i < symbols.size(); i++) {
for(int j = 0; j < states.size(); j++) {
System.out.print(matrix_maxprob[i][j]+" "+matrix_prevstate[i][j]+", ");
}
System.out.println();
}
}
// initialize for decoding
public void initialize(SymbolList syms) {
// symbols[syms.length] should be END
symbols = syms;
matrix_maxprob = new double[symbols.size()][states.size()];
matrix_prevstate = new int[symbols.size()][states.size()];
for(int i = 0; i < symbols.size(); i++) {
for(int j = 0; j < states.size(); j++) {
matrix_prevstate[i][j] = -1;
}
}
State start = states.get(state_start);
for(int i = 0; i < states.size(); i++) {
matrix_maxprob[0][i] = start.transprob(states.get(i));
matrix_prevstate[0][i] = 0;
}
stage = 0;
i0 = -1;
i1 = -1;
sequence = new IntegerList();
status = "Ok, let's get started...";
status2 = "";
}
// forward procedure
public boolean proceed_decoding() {
status2 = "";
// already end?
if (symbols.size() <= stage) {
return false;
}
// not started?
if (stage == 0) {
stage = 1;
i0 = 0;
i1 = 0;
matrix_maxprob[stage][i1] = 0.0;
} else {
i0++;
if (states.size() <= i0) {
// i0 should be reinitialized.
i0 = 0;
i1++;
if (states.size() <= i1) {
// i1 should be reinitialized.
// goto next stage.
stage++;
if (symbols.size() <= stage) {
// done.
status = "Decoding finished.";
return false;
}
laststage = (stage == symbols.size()-1);
i1 = 0;
}
matrix_maxprob[stage][i1] = 0.0;
}
}
// sym1: next symbol
Symbol sym1 = symbols.get(stage);
State s0 = states.get(i0);
State s1 = states.get(i1);
// precond: 1 <= stage.
double prob = matrix_maxprob[stage-1][i0];
DecimalFormat form = new DecimalFormat("0.0000");
status = "Prob:" + form.format(prob);
if (1 < stage) {
// skip first stage.
double transprob = s0.transprob(s1);
prob = prob * transprob;
status = status + " x " + form.format(transprob);
}
double emitprob = s1.emitprob(sym1);
prob = prob * emitprob;
status = status + " x " + form.format(emitprob) + "(" + s1.name+":"+sym1.name + ")";
status = status + " = " + form.format(prob);
// System.out.println("stage: "+stage+", i0:"+i0+", i1:"+i1+", prob:"+prob);
if (matrix_maxprob[stage][i1] < prob) {
matrix_maxprob[stage][i1] = prob;
matrix_prevstate[stage][i1] = i0;
status2 = "(new maximum found)";
}
return true;
}
// backward proc
public void backward() {
int probmaxstate = state_end;
sequence.add(probmaxstate);
for(int i = symbols.size()-1; 0 < i; i--) {
probmaxstate = matrix_prevstate[i][probmaxstate];
if (probmaxstate == -1) {
status2 = "Decoding failed.";
return;
}
sequence.add(probmaxstate);
//System.out.println("stage: "+i+", state:"+probmaxstate);
}
}
}
public class Viterbi extends Applet implements ActionListener, Runnable {
SymbolTable symtab;
StateTable sttab;
HMMDecoder myhmm = null;
HMMCanvas canvas;
Panel p;
TextArea hmmdesc;
TextField sentence;
Button bstart, bskip;
static final String initialHMM =
"start: go(cow,1.0)\n" +
"cow: emit(moo,0.9) emit(hello,0.1) go(cow,0.5) go(duck,0.3) go(end,0.2)\n" +
"duck: emit(quack,0.6) emit(hello,0.4) go(duck,0.5) go(cow,0.3) go(end,0.2)\n";
final int sleepmillisec = 100; // 0.1s
// setup hmm
// success:true.
boolean setupHMM(String s) {
myhmm = new HMMDecoder();
symtab = new SymbolTable();
sttab = new StateTable();
State start = sttab.get("start");
State end = sttab.get("end");
myhmm.addStartState(start);
boolean success = true;
StringTokenizer lines = new StringTokenizer(s, "\n");
while (lines.hasMoreTokens()) {
// foreach line.
String line = lines.nextToken();
int i = line.indexOf(':');
if (i == -1) break;
State st0 = sttab.get(line.substring(0,i).trim());
if (st0 != start && st0 != end) {
myhmm.addNormalState(st0);
}
//System.out.println(st0.name+":"+line.substring(i+1));
StringTokenizer tokenz = new StringTokenizer(line.substring(i+1), ", ");
while (tokenz.hasMoreTokens()) {
// foreach token.
String t = tokenz.nextToken().toLowerCase();
if (t.startsWith("go(")) {
State st1 = sttab.get(t.substring(3).trim());
// fetch another token.
if (!tokenz.hasMoreTokens()) {
success = false; // err. nomoretoken
break;
}
String n = tokenz.nextToken().replace(')', ' ');
double prob;
try {
prob = Double.valueOf(n).doubleValue();
} catch (NumberFormatException e) {
success = false; // err.
prob = 0.0;
}
st0.addLink(st1, prob);
//System.out.println("go:"+st1.name+","+prob);
} else if (t.startsWith("emit(")) {
Symbol sym = symtab.intern(t.substring(5).trim());
// fetch another token.
if (!tokenz.hasMoreTokens()) {
success = false; // err. nomoretoken
break;
}
String n = tokenz.nextToken().replace(')', ' ');
double prob;
try {
prob = Double.valueOf(n).doubleValue();
} catch (NumberFormatException e) {
success = false; // err.
prob = 0.0;
}
st0.addSymbol(sym, prob);
//System.out.println("emit:"+sym.name+","+prob);
} else {
// illegal syntax, just ignore
break;
}
}
st0.normalize(); // normalize probability
}
end.addSymbol(symtab.intern("end"), 1.0);
myhmm.addEndState(end);
return success;
}
// success:true.
boolean setup() {
if (! setupHMM(hmmdesc.getText()))
return false;
// initialize words
SymbolList words = new SymbolList();
StringTokenizer tokenz = new StringTokenizer(sentence.getText());
words.add(symtab.intern("start"));
while (tokenz.hasMoreTokens()) {
words.add(symtab.intern(tokenz.nextToken()));
}
words.add(symtab.intern("end"));
myhmm.initialize(words);
canvas.setHMM(myhmm);
return true;
}
public void init() {
canvas = new HMMCanvas();
setLayout(new BorderLayout());
p = new Panel();
sentence = new TextField("moo hello quack", 20);
bstart = new Button(" Start ");
bskip = new Button("Auto");
bstart.addActionListener(this);
bskip.addActionListener(this);
p.add(sentence);
p.add(bstart);
p.add(bskip);
hmmdesc = new TextArea(initialHMM, 4, 20);
add("North", canvas);
add("Center", p);
add("South", hmmdesc);
}
void setup_fallback() {
// adjustable
State cow = sttab.get("cow");
State duck = sttab.get("duck");
State end = sttab.get("end");
cow.addLink (cow, 0.5);
cow.addLink (duck, 0.3);
cow.addLink (end, 0.2);
duck.addLink (cow, 0.3);
duck.addLink (duck, 0.5);
duck.addLink (end, 0.2);
cow.addSymbol(symtab.intern("moo"), 0.9);
cow.addSymbol(symtab.intern("hello"), 0.1);
duck.addSymbol(symtab.intern("quack"), 0.6);
duck.addSymbol(symtab.intern("hello"), 0.4);
}
public void destroy() {
remove(p);
remove(canvas);
}
public void processEvent(AWTEvent e) {
if (e.getID() == Event.WINDOW_DESTROY) {
System.exit(0);
}
}
public void run() {
if (myhmm != null) {
while (myhmm.proceed_decoding()) {
canvas.repaint();
try {
Thread.sleep(sleepmillisec);
} catch (InterruptedException e) {
;
}
}
myhmm.backward();
canvas.repaint();
bstart.setLabel(" Start ");
bstart.setEnabled(true);
bskip.setEnabled(true);
myhmm = null;
}
}
public void actionPerformed(ActionEvent ev) {
String label = ev.getActionCommand();
if (label.equalsIgnoreCase(" start ")) {
if (!setup()) {
// error
return;
}
bstart.setLabel("Proceed");
canvas.repaint();
} else if (label.equalsIgnoreCase("proceed")) {
// next
if (! myhmm.proceed_decoding()) {
myhmm.backward();
bstart.setLabel(" Start ");
myhmm = null;
}
canvas.repaint();
} else if (label.equalsIgnoreCase("auto")) {
// skip
if (myhmm == null) {
if (!setup()) {
// error
return;
}
}
bstart.setEnabled(false);
bskip.setEnabled(false);
Thread me = new Thread(this);
me.setPriority(Thread.MIN_PRIORITY);
// start animation.
me.start();
}
}
public static void main(String args[]) {
Frame f = new Frame("Viterbi");
Viterbi v = new Viterbi();
f.add("Center", v);
f.setSize(400, 400);
f.show();
v.init();
v.start();
}
public String getAppletInfo() {
return "A Sample Viterbi Decoder Applet";
}
}
Here are some suggestions:
You should draw out the HMM lattice if you have any confusion about how transitions are occurring in your model.
For instance, you can view transitions to the first hidden state as originating from a single start hidden state, so the backpointer for k=0 would always have to point to this start state. In your code, you probably should not be looping over hidden states for k=0 in findW (which seems correct), but you probably should be looping for k=1.
Usually multiplying transition and emission probabilities in HMM inference lead to very small floating point values, which can lead to numerical errors. You should add log probabilities instead of multiplying probabilities.
To check viterbi or forward-backward implementations, I usually also write a brute force method and compare the output of each. If the brute-force and dynamic-programming algorithm match on short sequences, then that gives a reasonable measure of confidence that both are correct.

A* implementation issue

I am using A* for pathfinding in a Java project that I am working on. My attempt at implementing it though, has some issues. Not only is it slightly slow, but it sometimes runs into infinite loop issues where it never finds a path. I believe this to be the result of a bug somewhere in my code. The pathfinder operates on 3d space, and uses a basic heuristic (I can provide the code if necessary, however, it does little more than just calculate the distance squared between the two points).
The code is here:
public class BinaryPathFinder extends PathFinder {
private final PriorityBuffer<PathNode> paths;
private final HashMap<Point, Integer> mindists = new HashMap<Point, Integer>();
private final ComparableComparator<PathNode> comparator = new ComparableComparator<PathNode>();
private Path lastPath;
private Point start, end;
private final byte SIZE_INCREMENT = 20;
public BinaryPathFinder(PathHeuristic heuristic,
Pathplayer player, PathWorld pathWorld) {
super(heuristic, player, pathWorld);
this.world = pathWorld.getWorld();
paths = new PriorityBuffer<PathNode>(8000, comparator);
}
#Override
public boolean find() {
try {
PathNode root = new PathNode();
root.point = start;
calculateTotalCost(root, start, start, false);
expand(root);
while (true) {
PathNode p = paths.remove();
if (p == null)
return false;
Point last = p.point;
if (isGoal(last)) {
calculatePath(p); // Iterate back.
this.mindists.clear();
this.paths.clear();
return true;
}
expand(p);
}
} catch (Exception e) {
e.printStackTrace();
}
this.mindists.clear();
this.paths.clear();
return false;
}
#Override
public Path path() {
return this.lastPath;
}
#Override
public void recalculate(Point start, Point end) {
this.start = start;
this.end = end;
this.lastPath = null;
}
private void expand(PathNode path) {
Point p = path.point;
Integer min = mindists.get(p);
if (min == null || min > path.totalCost)
mindists.put(p, path.totalCost);
else
return;
Point[] successors = generateSuccessors(p);
for (Point t : successors) {
if (t == null)
continue;
PathNode newPath = new PathNode(path);
newPath.point = t;
calculateTotalCost(newPath, p, t, false);
paths.add(newPath);
}
}
private void calculatePath(PathNode p) {
Point[] retPath = new Point[20];
Point[] copy = null;
short added = 0;
for (PathNode i = p; i != null; i = i.parent) {
if (added >= retPath.length) {
copy = new Point[retPath.length + SIZE_INCREMENT];
System.arraycopy(retPath, 0, copy, 0, retPath.length);
retPath = copy;
}
retPath[added++] = i.point;
}
this.lastPath = new Path(retPath);
}
private int calculateHeuristic(Point start, Point end, boolean endPoint) {
return this.heuristic.calculate(start, end, this.pathWorld,
this.player, endPoint);
}
private int calculateTotalCost(PathNode p, Point from, Point to,
boolean endPoint) {
int g = (calculateHeuristic(from, to, endPoint) + ((p.parent != null) ? p.parent.cost
: 0));
int h = calculateHeuristic(from, to, endPoint);
p.cost = g;
p.totalCost = (g + h);
return p.totalCost;
}
private Point[] generateSuccessors(Point point) {
Point[] points = new Point[27];
Point temp = null;
byte counter = -1;
for (int x = point.x - 1; x <= point.x + 1; ++x) {
for (int y = point.y + 1; y >= point.y - 1; --y) {
for (int z = point.z - 1; z <= point.z + 1; ++z) {
++counter;
if (x == 0 && y == 0 && z == 0)
continue;
temp = new Point(x, y, z);
if (valid(temp))
points[counter] = temp;
}
}
}
return points;
}
private boolean isGoal(Point last) {
return last.equals(this.end);
}
}
PathNode is here:
public class PathNode implements Comparable<PathNode> {
public Point point;
public final PathNode parent;
public int cost;
public int totalCost;
public PathNode(Point point, PathNode parent, short cost, short totalCost) {
this.point = point;
this.parent = parent;
this.cost = cost;
this.totalCost = totalCost;
}
public PathNode() {
this.point = null;
this.parent = null;
this.cost = this.totalCost = 0;
}
public PathNode(PathNode path) {
this.parent = path;
this.cost = path.cost;
this.totalCost = path.totalCost;
}
#Override
public int compareTo(PathNode node) {
int result = node.totalCost - node.cost;
if (result > node.totalCost)
return 1;
else if (result == 0)
return 0;
else
return -1;
}
}
Point is just a wrapper class that defines integer x, y, z values.
I am using the Apache Commons PriorityBuffer for storing path nodes.
Help would be appreciated - thanks in advance!
just from a programming point of view - are you sure that
for(PathNode i = p; i != null; i = i.parent) always terminates? this looks like the only place it could really hang.

Categories

Resources