Related
The algorithm:
Uses a PQ that supports change priority operations.
Assume all the ADTs work correctly.
Example problem: Find the shortest path of operations to get from integer x to integer y using the following operations and weights; add/subtract 1 : 1, multiply/divide by 2 : 5, square : 10.
Tested against other types of graphs and inputs, sometimes it gets the shortest path, sometimes it gets a suboptimal, sometimes it times out.
import java.util.*;
/**
* Represents a graph of vertices.
*/
public interface AStarGraph<Vertex> {
List<WeightedEdge<Vertex>> neighbors(Vertex v);
double estimatedDistanceToGoal(Vertex s, Vertex goal);
}
public interface ShortestPathsSolver<Vertex> {
SolverOutcome outcome();
List<Vertex> solution();
double solutionWeight();
int numStatesExplored();
double explorationTime();
}
public interface ExtrinsicMinPQ<T> {
/* Inserts an item with the given priority value. */
void add(T item, double priority);
/* Returns true if the PQ contains the given item. */
boolean contains(T item);
/* Returns the minimum item. */
T getSmallest();
/* Removes and returns the minimum item. */
T removeSmallest();
/* Changes the priority of the given item. Behavior undefined if the item doesn't exist. */
void changePriority(T item, double priority);
/* Returns the number of itemToPriority in the PQ. */
int size();
}
public enum SolverOutcome {
SOLVED, TIMEOUT, UNSOLVABLE
}
public class ArrayHeapMinPQ<T> implements ExtrinsicMinPQ<T> {
private ArrayList<PriorityNode> heap;
int count;
private HashMap<T, PriorityNode> items;
public ArrayHeapMinPQ() {
heap = new ArrayList<>();
heap.add(0, new PriorityNode(null, Double.NEGATIVE_INFINITY));
count = 0; // For convenient math
items = new HashMap<>();
}
#Override
public void add(T item, double priority) {
if(contains(item)){
throw new IllegalArgumentException();
}
PriorityNode pn = new PriorityNode(item, priority);
if(count == 0){
heap.add(1, pn);
count++;
}else{
heap.add(count+1, pn);
swim(count+1);
count++;
}
items.put(item, pn);
}
private void swap(int i, int j){
Collections.swap(heap, i, j);
}
private void swim(int i){
while(i > 1){
PriorityNode cur = heap.get(i);
if((cur.compareTo(heap.get(i/2)) >= 0)){
break;
}
swap(i, i/2);
i = i/2;
}
}
private void sink(int k){
while (2*k <= size()-1) {
if(2*k+1 <= size()-1) {
if (heap.get(2 * k).compareTo(heap.get(2 * k + 1)) < 0) {
if (heap.get(k).compareTo(heap.get(2 * k)) > 0) {
swap(k, 2 * k);
k = 2 * k;
continue;
}
} else if(heap.get(k).compareTo(heap.get(2*k+1)) > 0){
swap(2*k+1, k);
k = 2*k +1;
continue;}
}
else if (heap.get(k).compareTo(heap.get(2 * k)) > 0) {
swap(k, 2 * k);
k = 2 * k;
continue;}
break;
}
}
#Override
public int size(){
return heap.size();
}
public PriorityNode getRoot(){
return heap.get(1);
}
#Override
public boolean contains(T item) {
return items.containsKey(item);}
#Override
public T getSmallest() {
if(heap.size() == 0){
throw new NoSuchElementException();
}
return getRoot().getItem();
}
#Override
public T removeSmallest() {
if(heap.size() == 1){
throw new NoSuchElementException();
}
T item = heap.get(1).item;
swap(1, size()-1);
heap.remove(size()-1);
if(size() > 1){
sink(1);
}
items.remove(item);
count--;
return item;
}
public boolean isEmpty(){
return heap.size() == 1;
}
public int getCount() {
return count;
}
#Override
public void changePriority(T T, double priority) {
if(heap.size() == 0){
throw new NoSuchElementException();
}
PriorityNode tochange = items.get(T);
double prioritysearch = tochange.getPriority();
double currprior = getRoot().getPriority();
int left = 0;
int right = 0;
if((prioritysearch != currprior)){
if (currprior > prioritysearch){
add(T, priority);
return;
}
left = heapTraverse(prioritysearch, tochange, 2);
right = heapTraverse(prioritysearch, tochange, 3);
}
if(left == -1 && right == -1){
throw new NoSuchElementException();
}
else if(left > 0){
PriorityNode toChange = heap.get(left);
toChange.setPriority(priority);
if(priority < heap.get(left/2).getPriority()){
swim(left);
}else
sink(left);
}
else {
PriorityNode toChange = heap.get(right);
toChange.setPriority(priority);
if (priority < heap.get(right / 2).getPriority()) {
swim(right);
} else
sink(right);
}
}
private int heapTraverse(double priority, PriorityNode node, int index){
if(index > heap.size()-1){
return -1;
}
PriorityNode curr = heap.get(index);
double currprior = curr.getPriority();
if(currprior == priority && node.equals(curr)){
return index;
} else if(currprior > priority){
return -1;
}else{
if(heapTraverse(priority, node, index*2) == -1){
return heapTraverse(priority, node, index*2 +1);}
else {return heapTraverse(priority, node, index*2);}
}
}
private class PriorityNode implements Comparable<PriorityNode> {
private T item;
private double priority;
PriorityNode(T e, double p) {
this.item = e;
this.priority = p;
}
T getItem() {
return item;
}
double getPriority() {
return priority;
}
void setPriority(double priority) {
this.priority = priority;
}
#Override
public int compareTo(PriorityNode other) {
if (other == null) {
return -1;
}
return Double.compare(this.getPriority(), other.getPriority());
}
#Override
public boolean equals(Object o) {
if( o == null) {
throw new NullPointerException();
}
if (o.getClass() != this.getClass()) {
return false;
} else {
return ((PriorityNode) o).getItem().equals(getItem());
}
}
#Override
public int hashCode() {
return item.hashCode();
}
}
public class WeightedEdge<Vertex> {
private Vertex v;
private Vertex w;
private double weight;
public WeightedEdge(Vertex v, Vertex w, double weight) {
this.v = v;
this.w = w;
this.weight = weight;
}
public Vertex from() {
return v;
}
public Vertex to() {
return w;
}
public double weight() {
return weight;
}
}
public class SolutionPrinter {
/** Summarizes the result of the search made by this solver without actually
* printing the solution itself (if any).
*/
public static <Vertex> void summarizeOutcome(ShortestPathsSolver<Vertex> solver) {
summarizeSolution(solver, "", false);
}
/** Summarizes the result of the search made by this solver and also
* prints each vertex of the solution, connected by the given delimiter,
* e.g. delimiter = "," would return all states separated by commas.
*/
public static <Vertex> void summarizeSolution(ShortestPathsSolver<Vertex> solver,
String delimiter) {
summarizeSolution(solver, delimiter, true);
}
private static <Vertex> String solutionString(ShortestPathsSolver<Vertex> solver,
String delimiter) {
List<String> solutionVertices = new ArrayList<>();
for (Vertex v : solver.solution()) {
solutionVertices.add(v.toString());
}
return String.join(delimiter, solutionVertices);
}
private static <Vertex> void summarizeSolution(ShortestPathsSolver<Vertex> solver,
String delimiter, boolean printSolution) {
System.out.println("Total states explored in " + solver.explorationTime()
+ "s: " + solver.numStatesExplored());
if (solver.outcome() == SolverOutcome.SOLVED) {
List<Vertex> solution = solver.solution();
System.out.println("Search was successful.");
System.out.println("Solution was of length " + solution.size()
+ ", and had total weight " + solver.solutionWeight() + ":");
if (printSolution) {
System.out.println(solutionString(solver, delimiter));
}
} else if (solver.outcome() == SolverOutcome.TIMEOUT) {
System.out.println("Search timed out, considered " + solver.numStatesExplored()
+ " vertices before timing out.");
} else { // (solver.outcome() == SolverOutcome.UNSOLVABLE)
System.out.println("Search determined that the goal is unreachable from source.");
}
}
}
public class AStarSolver implements ShortestPathsSolver {
private final AStarGraph<Vertex> graph;
private Vertex source;
private Vertex dest;
private SolverOutcome result;
private HashMap<Vertex, Double> distTo = new HashMap<>();
private ArrayHeapMinPQ<Vertex> fringe = new ArrayHeapMinPQ<>();
private HashMap<Vertex, WeightedEdge<Vertex>> edgeTo = new HashMap<>(); // answers the question which vertex to ge to this vertex
private double solutionweight;
private List<Vertex> solution;
private ArrayList<Vertex> marked = new ArrayList<>();
private double timetosolve;
private int numofstates = 0;
public AStarSolver(AStarGraph<Vertex> input, Vertex start, Vertex end, double timeout ){
graph = input;
source = start;
dest = end;
if(start.equals(end)){
solutionweight = 0;
solution = List.of(start);
result = SolverOutcome.SOLVED;
numofstates = 0;
timetosolve = 0;
return;
}
fringe.add(start, 0.0);
distTo.put(start, 0.0);
while (!fringe.isEmpty()) {
Vertex src = fringe.removeSmallest();
numofstates++;
marked.add(src);
List<WeightedEdge<Vertex>> neighbors = graph.neighbors(src);
for(WeightedEdge<Vertex> e: neighbors){
double heur = graph.estimatedDistanceToGoal(e.to(), dest);
if ((heur == Double.POSITIVE_INFINITY || marked.contains(e.to())) && !e.to().equals(dest)) {
continue;
}
double distFr = distTo.get(e.from()) + e.weight();
if(!distTo.containsKey(e.to())){
distTo.put(e.to(), distFr);
}
if (!fringe.contains(e.to())) {
fringe.add(e.to(), distFr + heur);
edgeTo.put(e.to(), e);
}
if (distTo.get(e.to()) > distFr) {
fringe.changePriority(e.to(), heur + distFr);
edgeTo.put(e.to(), e);
distTo.put(e.to(), distFr);
}
if (e.to().equals(dest)) {
solutionweight = distTo.get(e.to());
solution = pathTracer(e);
timetosolve = sw.elapsedTime();
result = SolverOutcome.SOLVED;
return;
}
if (e.to().equals(dest)) {
solutionweight = distTo.get(e.to());
solution = pathTracer(e);
timetosolve = sw.elapsedTime();
result = SolverOutcome.SOLVED;
return;
}
}
if (timeout < sw.elapsedTime()){
result = SolverOutcome.TIMEOUT;
return;
}
}
result = SolverOutcome.UNSOLVABLE;
solution = List.of();
}
private List<Vertex> pathTracer(WeightedEdge<Vertex> e) {
ArrayList<Vertex> path = new ArrayList<>();
path.add(e.to());
path.add(e.from());
while (!e.from().equals(source)) {
e = edgeTo.get(e.from());
path.add(e.from());
}
Collections.reverse(path);
return path;
}
#Override
public SolverOutcome outcome() {
return result;
}
#Override
public List solution() {
return solution;
}
#Override
public double solutionWeight() {
return solutionweight;
}
#Override
public int numStatesExplored() {
return numofstates;
}
#Override
public double explorationTime() {
return timetosolve;
}
public class IntegerHopGraph implements AStarGraph<Integer> {
#Override
public List<WeightedEdge<Integer>> neighbors(Integer v) {
ArrayList<WeightedEdge<Integer>> neighbors = new ArrayList<>();
neighbors.add(new WeightedEdge<>(v, v * v, 10));
neighbors.add(new WeightedEdge<>(v, v * 2, 5));
neighbors.add(new WeightedEdge<>(v, v / 2, 5));
neighbors.add(new WeightedEdge<>(v, v - 1, 1));
neighbors.add(new WeightedEdge<>(v, v + 1, 1));
return neighbors;
}
#Override
public double estimatedDistanceToGoal(Integer s, Integer goal) {
// possibly fun challenge: Try to find an admissible heuristic that
// speeds up your search. This is tough!
return 0;
}
}
public class DemoIntegerHopPuzzleSolution {
public static void main(String[] args) {
int start = 17;
int goal = 111;
IntegerHopGraph ahg = new IntegerHopGraph();
ShortestPathsSolver<Integer> solver = new AStarSolver<>(ahg, start, goal, 10);
SolutionPrinter.summarizeSolution(solver, " => ");
}
}
}
To get from x = 11, to y = 117 the algorithm gives this result:
Total states explored in 0.019s: 110
Search was successful.
Solution was of length 7, and had total weight 19.0:
17 => 16 => 15 => 225 => 224 => 223 => 111
The correct result should is:
Total states explored in 0.018s: 338 <--- may vary.
Search was successful.
Solution was of length 6, and had total weight 18.0:
17 => 16 => 15 => 225 => 112 => 111
Thanks for all the help guys but I figured it out. My algorithm terminates prematurely. It stops when it sees the finish, not when it is on the top of the heap like it should.
I'm attempting to create a game "dungeon" map generator in Java in the style of roguelike etc. games. I generate rooms randomly, and then connect them with corridors. I'm trying to use A* pathfinding in corridor creation. I currently am creating just one corridor between rooms in the indices 1 and 2, if there is more than one room.
For some reason, the corridor creation seems to fail when I try to generate more than 1 maps ("floors"). The amount of floors is specified as a command-line parameter. Thus far, when I've generated just one floor, everything works perfectly. My gut feeling says that there's something about the floors that messes up my algorithm.
I thought that maybe an outside view of the project could help. There is quite a lot of code, but I'd be very grateful if someone took the time to review it. I can provide more information if it's needed.
THE RESULTS
The result, when correct, should look like this:
Map 1
# wall
. floor
+ door
$ corridor
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
...........$$$$$$$$$$.........
...........$......##+#######..
.....######$......#........#..
.....#....#$......#........#..
.....#....#$......#........#..
.....#....#$......#........#..
.....#....#$......#........#..
.....#....#$......#........#..
.....#....#$......#........#..
.....##+###$......##########..
.......$$$$$..................
..............................
The buggy result looks like this (the corridor does not go door-to-door, it just ends in a random location):
...........$$$...#########....
...........$#+##.#.......#....
...........$#..#.#.......#....
...........$#..#.#.......+....
###+###....$#..#.#.......#....
#.....#....$#..#.#.......#....
#.....#....$#..#.#.......#....
#.....#....$#..#.#########....
#.....#....$####..............
#.....#....$..................
#.....#....$..................
#######....$..................
...........$..................
...........$..................
...........$..................
...........$..................
...........$..................
...........$..................
.......$$$$$..................
..............................
THE CODE
AStar.java:
/**
* See https://www.raywenderlich.com/4946/introduction-to-a-pathfinding
*/
public class AStar {
private List<AStarSquare> openList;
private List<AStarSquare> closedList;
private Exporter debugExporter;
private static final Coords[] squareOffsetsToCheck = new Coords[] {
new Coords(0, 1),
new Coords(1, 0),
new Coords(0, -1),
new Coords(-1, 0)
};
public AStar() {
openList = new ArrayList<>();
closedList = new ArrayList<>();
debugExporter = new Exporter();
}
public List<Coords> findPath(Coords start, Coords end, Map map) {
List<Coords> path = new ArrayList<>(); // each square on the generated path
AStarSquare currentSquare = new AStarSquare(start, null); // current square around which possible squares are evaluated - start point
closedList.add(currentSquare); // add start point to closed list
createUpdateOpenSquares(currentSquare, start, end, map); // create open squares for first iteration
calculateScores(start, end, map); // calculate scores for first iteration
int loopGuard = 0;
// loop until break
while(true) {
if(openList.size() == 0) {
break;
}
currentSquare = getLowestOpenSquare(); // get the square with the lowest score
if(isAdjacentToDoor(currentSquare.getCoords(), end) /*|| currentSquare.getCoords().equalz(end) || loopGuard >= 1000*/) // end point reached or no possible next squares
break; // - exclude last square (door)
openList.remove(currentSquare);
closedList.add(currentSquare);
createUpdateOpenSquares(currentSquare, start, end, map); // create and/or update squares next to the current square
calculateScores(start, end, map);
map.setDebugCorridor(formulatePath(currentSquare));
loopGuard++;
}
path = formulatePath(currentSquare);
return path;
}
private void createUpdateOpenSquares(AStarSquare currentSquare, Coords start, Coords end, Map map) {
for(Coords squareOffsetToCheck : squareOffsetsToCheck) {
Coords coordsToCheck = currentSquare.getCoords().vectorAdd(squareOffsetToCheck);
if(map.isFloor(coordsToCheck)
&& !map.isInsideRoom(coordsToCheck)
&& isWithinMap(map, coordsToCheck)
&& !isClosed(coordsToCheck)) {
AStarSquare openSquare = getOpen(coordsToCheck);
if(openSquare == null)
openList.add(new AStarSquare(coordsToCheck, currentSquare));
else // is open
openSquare.setPrevious(currentSquare);
}
}
}
private boolean isClosed(Coords coords) {
for(AStarSquare closed : closedList) {
if(closed.getCoords().equalz(coords))
return true;
}
return false;
}
private AStarSquare getOpen(Coords coords) {
for(AStarSquare open : openList) {
if(open.getCoords().equalz(coords))
return open;
}
return null;
}
private boolean isWithinMap(Map map, Coords coords) {
if(coords.getX() < 0
|| coords.getY() < 0
|| coords.getX() >= map.getW()
|| coords.getY() >= map.getH())
return false;
return true;
}
private boolean isAdjacentToDoor(Coords coords, Coords end) {
for(Coords squareOffset : squareOffsetsToCheck) {
Coords offsetSquare = coords.vectorAdd(squareOffset);
if(offsetSquare.equalz(end))
return true;
}
return false;
}
private void calculateScores(Coords start, Coords end, Map map) {
for(AStarSquare square : openList) {
square.calculateScores(map, start, end);
}
}
private AStarSquare getLowestOpenSquare() {
AStarSquare lowestScore = null;
for(AStarSquare square : openList) {
// if lowestScore not set or if square.f is lower than lowestScore.f, set square to lowestScore
if(lowestScore == null || lowestScore.getF() > square.getF())
lowestScore = square;
}
return lowestScore;
}
// exclude first square (door)
private List<Coords> formulatePath(AStarSquare currentSquare) {
List<Coords> path = new ArrayList<>();
while(currentSquare.getPrevious() != null) {
path.add(currentSquare.getCoords());
currentSquare = currentSquare.getPrevious();
}
return path;
}
}
AStarSquare.java:
/**
* See https://www.raywenderlich.com/4946/introduction-to-a-pathfinding
*/
public class AStarSquare {
private Coords coords;
private AStarSquare previous;
private int g, h;
private boolean calculated;
public AStarSquare() {
g = h = 0;
calculated = false;
}
public AStarSquare(Coords coords) {
this();
this.coords = coords;
previous = null;
}
public AStarSquare(Coords coords, AStarSquare previous) {
this();
this.coords = coords;
this.previous = previous;
}
public void calculateScores(Map map, Coords start, Coords destination) {
g = previous.getG() + 1; // g = distance from start point
h = destination.getDistance(coords); // h = estimated (=shortest) distance from the current location to the destination
calculated = true;
}
}
Main class:
public class DungeonMapGenerator {
public static void main(String[] args) {
List<String> argsList = Arrays.asList(args);
if(!argsList.contains("-w")
|| !argsList.contains("-h")
|| !argsList.contains("-f")) {
System.out.println("Usage: java -jar DungeonMapGenerator.jar -w [width] -h [height] -f [floors] -[export option]");
System.exit(1);
}
int width = 0, height = 0, floors = 0;
for(int i = 0; i < args.length; i++) {
if(args[i].equalsIgnoreCase("-w"))
width = tryParseInt(args, i + 1, 30);
else if(args[i].equalsIgnoreCase("-h"))
height = tryParseInt(args, i + 1, 20);
else if(args[i].equalsIgnoreCase("-f"))
floors = tryParseInt(args, i + 1, 1);
}
Generator mapGenerator = new Generator(width, height, floors);
List<Map> maps = mapGenerator.generateMaps();
Exporter mapExporter = new Exporter();
if(argsList.contains("-c"))
mapExporter.exportToConsole(maps);
else
System.out.println("No export option selected, quitting");
}
private static int tryParseInt(String[] args, int index, int deflt) {
int res;
if(index >= args.length) // index out of range
res = deflt;
try {
res = Integer.parseInt(args[index], 10);
} catch(NumberFormatException ex) {
res = deflt;
}
return res;
}
}
Generator.java
public class Generator {
private static final int
MIN_ROOMS = 1,
MAX_ROOMS = 5,
MIN_DIM = 3, // dim = min and max room dimensions
MAX_DIM = 10;
private AStar pathfinder;
private Random random;
private int mapWidth, mapHeight, floors;
public Generator(int mapWidth, int mapHeight, int floors) {
pathfinder = new AStar();
random = new Random(System.currentTimeMillis());
this.mapWidth = mapWidth;
this.mapHeight = mapHeight;
this.floors = floors;
}
public List<Map> generateMaps() {
List<Map> mapList = new ArrayList<>();
for(int i = 0; i < floors; i++) {
Map map = new Map(i + 1, mapWidth, mapHeight, generateRooms(mapWidth, mapHeight), null);
generateDoors(map, map.getRooms());
debugFindPath(map);
mapList.add(map);
}
return mapList;
}
private List<Room> generateRooms(int mapWidth, int mapHeight) {
List<Room> roomList = new ArrayList<>();
int nRooms = random.nextInt(5) + 1;
for(int i = 0; i < nRooms; i++) {
Room room = null;
do {
int w = 0, h = 0, x = 0, y = 0;
w = getRandomDim();
h = getRandomDim();
x = random.nextInt(mapWidth - w);
y = random.nextInt(mapHeight - h);
room = new Room(x, y, w, h);
} while(roomsOverlap(room, roomList));
roomList.add(room);
}
return roomList;
}
private boolean roomsOverlap(Room room, List<Room> rooms) {
for(Room listRoom : rooms) {
if(room.overlapsWithRoom(listRoom))
return true;
}
return false;
}
private int getRandomDim() {
return random.nextInt(MAX_DIM - MIN_DIM + 1) + MIN_DIM;
}
private void generateDoors(Map map, List<Room> roomList) {
for(int i = 0; i < roomList.size(); i++) {
Door door = new Door(roomList.get(i));
do {
door.setSide(getRandomCardinal());
door.setDistNW(getRandomDistNW(roomList.get(i), door.getSide()));
} while(!validateDoor(map, door));
roomList.get(i).setDoors(Arrays.asList(new Door[] { door }));
map.getDoors().add(door);
}
}
private Cardinal getRandomCardinal() {
int cardinalInt = random.nextInt(4);
Cardinal cardinal;
switch(cardinalInt) {
case 1:
cardinal = Cardinal.EAST;
break;
case 2:
cardinal = Cardinal.SOUTH;
break;
case 3:
cardinal = Cardinal.WEST;
case 0:
default:
cardinal = Cardinal.NORTH;
break;
}
return cardinal;
}
private int getRandomDistNW(Room room, Cardinal cardinal) {
int distNW = 0;
if(cardinal == Cardinal.NORTH || cardinal == Cardinal.SOUTH)
distNW = random.nextInt(room.getW() - 2) + 1; // exclude corners
else if(cardinal == Cardinal.EAST || cardinal == Cardinal.WEST)
distNW = random.nextInt(room.getH() - 2) + 1; // exclude corners
return distNW;
}
private boolean validateDoor(Map map, Door door) {
Coords doorCoordsOnMap = door.getCoordsOnMap();
if(door.getSide() == Cardinal.NORTH
&& (door.getParent().getTop() == 0
// check if adjacent to another room
|| map.isWall(new Coords(doorCoordsOnMap.getX(), doorCoordsOnMap.getY() - 1))))
return false;
else if(door.getSide() == Cardinal.EAST
&& (door.getParent().getRight() == mapWidth - 1
// check if adjacent to another room
|| map.isWall(new Coords(doorCoordsOnMap.getX() + 1, doorCoordsOnMap.getY()))))
return false;
else if(door.getSide() == Cardinal.SOUTH
&& (door.getParent().getBottom() == mapHeight - 1
// check if adjacent to another room
|| map.isWall(new Coords(doorCoordsOnMap.getX(), doorCoordsOnMap.getY() + 1))))
return false;
else if(door.getSide() == Cardinal.WEST
&& (door.getParent().getLeft() == 0
// check if adjacent to another room
|| map.isWall(new Coords(doorCoordsOnMap.getX() - 1, doorCoordsOnMap.getY()))))
return false;
return true;
}
private void debugFindPath(Map map) {
if(map.getRooms().size() == 1)
return;
map.setDebugCorridor(pathfinder.findPath(
map.getRooms().get(0).getDoors().get(0).getCoordsOnMap(),
map.getRooms().get(1).getDoors().get(0).getCoordsOnMap(),
map
));
}
}
Room.java
public class Room {
private Coords topLeft;
private int w, h;
private List<Door> doors;
public Room(int topLeftX, int topLeftY, int w, int h) {
topLeft = new Coords(topLeftX, topLeftY);
this.w = w;
this.h = h;
doors = new ArrayList<>();
}
public boolean overlapsWithRoom(Room otherRoom) {
return !(otherRoom.getLeft() > this.getRight()
|| otherRoom.getRight() < this.getLeft()
|| otherRoom.getTop() > this.getBottom()
|| otherRoom.getBottom() < this.getTop());
}
#Override
public String toString() {
return "Room ~ top: " + getTop() + " right: " + getRight()
+ " bottom: " + getBottom() + " left: " + getLeft()
+ " width: " + w + " height: " + h;
}
public boolean isWall(Coords coords) { /*** TESTAA!!! ***/
if(
// x is either left or right, y is between top and bottom
((coords.getX() == topLeft.getX() || coords.getX() == topLeft.getX() + w)
&& coords.getY() >= topLeft.getY() && coords.getY() < topLeft.getY() + h + 1)
||
// y is either top or bottom, x is between left and right
((coords.getY() == topLeft.getY() || coords.getY() == topLeft.getY() + h)
&& coords.getX() >= topLeft.getX() && coords.getX() < topLeft.getX() + w)
)
return true;
return false;
}
}
Door.java
(Cardinal is a simple enum containing NORTH, EAST, SOUTH and WEST)
public class Door {
private Room parent;
private Cardinal side;
private int distNW = 0;
public Door(Room parent) {
this.parent = parent;
this.side = null;
}
public Door(Room parent, Cardinal side) {
this.parent = parent;
this.side = side;
}
public Coords getCoordsOnMap() {
Coords coords = null;
if(side == Cardinal.NORTH)
coords = new Coords(parent.getLeft() + distNW, parent.getTop());
else if(side == Cardinal.EAST)
coords = new Coords(parent.getRight(), parent.getTop() + distNW);
else if(side == Cardinal.SOUTH)
coords = new Coords(parent.getLeft() + distNW, parent.getBottom());
else if(side == Cardinal.WEST)
coords = new Coords(parent.getLeft(), parent.getTop() + distNW);
return coords;
}
}
In AStar, your A* pathfinding algorithm adds to its opened and closed lists before returning the chosen path
When pathfinding with different start/end destinations or a different map, those lists will need to be reset
The issue is that you're reusing the AStar object for each path you're trying to find, causing conflicts with old searches
To fix it, use a new AStar object for every path you search, or add a method to clear the old data
I removed this line from the Generator constructor:
public Generator(int mapWidth, int mapHeight, int floors) {
// pathfinder = new AStar(); // REMOVED THIS LINE
...
}
And I added the following line to the Generator.generateMaps method:
public List<Map> generateMaps() {
List<Map> mapList = new ArrayList<>();
for(int i = 0; i < floors; i++) {
pathfinder = new AStar(); // ADDED THIS LINE
Map map = new Map(i + 1, mapWidth, mapHeight, generateRooms(mapWidth, mapHeight), null);
generateDoors(map, map.getRooms());
debugFindPath(map);
mapList.add(map);
}
return mapList;
}
And now everything seems to work.
Another option is to add the following lines to AStar.findPath():
public List<Coords> findPath(Coords start, Coords end, Map map) {
openList = new ArrayList<>();
closedList = new ArrayList<>();
...
}
I'm trying to implement a rush hour solving algorithm using breadth first search to traverse a graph of the different paths, stopping when a solution has been found.
After numerous tests, I've come across a problem I have no idea how to solve. When there is a solution, the algorithm works very well, but when none can be found, it just endlessly cycles because it recreates "nodes" that contain the same information as older ones, instead of returning towards the older ones if the information is the same. How would I be able to make it stop when it figures out there are no more possibilities?
Here are the classes I am using for the solver:
This is the main class RushHour, the one I run.
package rush.hour;
import java.io.*;
import java.util.List;
import java.util.ArrayList;
public class RushHour
{
public static void main(String[] args)
{
String fileName = "Parking.txt";
String line;
int lineNumber = 0;
int numOfCars;
List<Car> carsList = new ArrayList<>();
try
{
FileReader fileReader = new FileReader(fileName);
BufferedReader bufferedReader = new BufferedReader(fileReader);
while((line = bufferedReader.readLine()) != null)
{
lineNumber += 1;
// System.out.println(lineNumber);
System.out.println(line);
if (lineNumber == 15)
numOfCars = Integer.parseInt(
line.substring(line.length() - 1)) + 1;
if (lineNumber >= 17)
{
String[] splitLine = line.split(":");
char[] coordinates = splitLine[1].toCharArray();
int a = coordinates[3] - 48;
int b = coordinates[5] - 48;
int c = coordinates[10] - 48;
int d = coordinates[12] - 48;
Car car = new Car(lineNumber - 17, a, b, c, d);
carsList.add(car);
}
}
Parking parking = new Parking(carsList);
Graph graph = new Graph(parking);
List<Parking> sol = graph.bfs();
System.out.println("\n------------- Solution -------------");
for (Parking sols: sol)
{ // L = Left, D = Down, R = Right, U = Up
System.out.println(sols);
}
}
catch(FileNotFoundException ex)
{ex.printStackTrace();}
catch(IOException ex)
{ex.printStackTrace();}
}
}
And this is the "graph" that uses the adapted BFS
package rush.hour;
import java.util.LinkedList;
import java.util.List;
import java.util.ArrayList;
import java.util.Queue;
public class Graph
{
Parking posDepart;
public Graph(Parking parking)
{
posDepart = parking;
}
public List<Parking> bfs()
{
List<Parking> res = new ArrayList<>();
Queue<Parking> queue = new LinkedList<>();
queue.add(posDepart);
while (!queue.isEmpty())
{
Parking next = queue.remove();
if (next.isFinal())
{
res.add(next);
return next.getPath();
}
next.setChildren();
for (Parking child : next.children) {
queue.add(child);
}
}
return res;
}
}
This is the Graph's "node", it represents the disposition of the cars on the board
package rush.hour;
import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;
public class Parking
{
Parking father = null;
List<Car> carsList = new ArrayList<>();
boolean visited = false;
List<Parking> children = new ArrayList<>();
List<Character> move = new ArrayList<>();
public Parking(List<Car> list)
{
for (Car car: list)
carsList.add(car.clone());
}
public Parking(Parking vader,List<Character> mov)
{
for (Car car: vader.carsList)
carsList.add(car.clone());
father = vader;
move = mov;
}
public void getNextParkings(List<List<Character>> moves)
{
children.clear();
for (int i = 0; i < moves.size(); ++i)
{
List<Character> info = moves.get(i);
Parking nextParking = new Parking(this, info);
nextParking.carsList.get(info.get(0) - 48).move(info.get(1));
children.add(nextParking);
}
}
#Override
public String toString()
{
String res = "";
if (!move.isEmpty())
{
res += "Move made to reach this parking:";
res += move;
res += "\n";
}
for (Car car : carsList)
res += car.toString();
return res;
}
public boolean isFinal()
{
return carsList.get(0).isFinal();
}
public void setChildren()
{
if (children.isEmpty())
{
List<List<Character>> moves = getPossibleMoves();
getNextParkings(moves);
}
}
public List<List<Character>> getPossibleMoves()
{
List<List<Character>> res = new ArrayList<>();
List<List<Character>> moves;
for (Car car : carsList)
{
moves = car.getPossibleMoves();
for (int i = 0; i < moves.size(); ++i)
{
List<Character> move = moves.get(i);
if (!hasNeighbour(move.get(0),
move.get(1) - 48, move.get(2) - 48))
{
List<Character> foo = new ArrayList<>(2);
foo.add(car.getNumber());
foo.add(move.get(0));
res.add(foo);
}
}
}
return res;
}
public boolean hasNeighbour(char direction, int x, int y)
{
for (Car car : carsList)
{
if (direction == 'U')
if ((car.getX1() == x - 1 && car.getY1() == y) ||
(car.getX2() == x - 1 && car.getY2() == y))
return true;
if (direction == 'D')
if ((car.getX1() == x + 1 && car.getY1() == y) ||
(car.getX2() == x + 1 && car.getY2() == y))
return true;
if (direction == 'L')
if ((car.getX1() == x && car.getY1() == y - 1) ||
(car.getX2() == x && car.getY2() == y - 1))
return true;
if (direction == 'R')
if ((car.getX1() == x && car.getY1() == y + 1) ||
(car.getX2() == x && car.getY2() == y + 1))
return true;
}
return false;
}
public List<Parking> getPath()
{
Parking temp_father;
List<Parking> path = new LinkedList<>();
path.add(this);
temp_father = this.father;
while (temp_father != null)
{
path.add(0, temp_father);
temp_father = temp_father.father;
}
return path;
}
}
And finally the Car class, which probably isn't very important to the problem but still
package rush.hour;
import java.util.List;
import java.util.ArrayList;
public class Car
{
char orientation;
int carNumber;
int x1;
int x2;
int y1;
int y2;
#Override
public String toString()
{
return "Car n° : " + (carNumber) + " : [(" + (x1) + ", " + y1 + ')' +
" (" + (x2) + ", " + y2 + ")]" + "\n";
}
public Car clone()
{
Car c = new Car(carNumber, x1, y1, x2, y2);
return c;
}
public Car(int n, int a, int b, int c, int d)
{
carNumber = n;
if (a <= c)
{
x1 = a;
x2 = c;
}
else
{
x1 = c;
x2 = a;
}
if (b <= d)
{
y1 = b;
y2 = d;
}
else
{
y1 = d;
y2 = b;
}
if (x1 == x2)
orientation = 'H';
else
orientation = 'V';
}
public char getNumber()
{
return Integer.toString(carNumber).charAt(0);
}
public int getX1()
{
return x1;
}
public int getX2()
{
return x2;
}
public int getY1()
{
return y1;
}
public int getY2()
{
return y2;
}
public void move(char direction)
{
if (direction == 'U')
{
--x1;
--x2;
}
if (direction == 'D')
{
++x1;
++x2;
}
if (direction == 'L')
{
--y1;
--y2;
}
if (direction == 'R')
{
++y1;
++y2;
}
}
public boolean isFinal()
{
return y2 == 4 && carNumber == 0;
}
public List<List<Character>> getPossibleMoves()
{
List<List<Character>> res = new ArrayList<>();
if (orientation == 'H')
{
if (y1 != 0)
{
List<Character> foo = new ArrayList<>(2);
foo.add('L');
foo.add(Integer.toString(x1).charAt(0));
foo.add(Integer.toString(y1).charAt(0));
res.add(foo);
}
if (y2 != 4)
{
List<Character> foo = new ArrayList<>(2);
foo.add('R');
foo.add(Integer.toString(x2).charAt(0));
foo.add(Integer.toString(y2).charAt(0));
res.add(foo);
}
}
else
{
if (x1 != 0)
{
List<Character> foo = new ArrayList<>(2);
foo.add('U');
foo.add(Integer.toString(x1).charAt(0));
foo.add(Integer.toString(y1).charAt(0));
res.add(foo);
}
if (x2 != 4)
{
List<Character> foo = new ArrayList<>(2);
foo.add('D');
foo.add(Integer.toString(x2).charAt(0));
foo.add(Integer.toString(y2).charAt(0));
res.add(foo);
}
}
return res;
}
}
Excuse me if I've made some blatant styling/coding mistakes, I am more used to coding in Python/C++. Would there be a way to make this work?
You need to use some type of data structure to keep track of paths already traversed. A hash table would be ideal and would provide a worst case of O(n) depending upon implementation. Not very familiar with Java but in C++ I would use std::unordered_map with the first field being a state of your board and the second being the number of moves at that point. I have implemented this algorithm before, and that's the approach I took. Incredibly fast.
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.
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.