i'm stuck in a task which I could use a little help with. Basically, my job is to design a factory for wrapping presents. Our stocks are boxes with a square base (not necessarily cubes). For each box, we know the dimensions of the base of the box (I called it side) and the height (height). When the factory receives a request to wrap a present, the customer knows the side and height values of the smallest box that fits the present, but we will provide the box with the minimal volume that we have at the moment.
The idea is to plan a data structure to manage the boxes. The data structure has to support the following methods:
INSERTBOX(side, height) - adding a box to the data structure with the given dimensions
REMOVEBOX(side, height) - removing a box from the data structure with the given dimensions
GETBOX(side, height) - returning the dimensions of a box with minimal volume that fits for wrapping the present (of course, its dimension should be equal or larger than the given side and height...)
CHECKBOX(side,height) - checking whether there is a box in the storage that fits the present.
The parameters of the entire problem are the number of side values, m, and the number of height values, n.
I was thinking of a red-black tree that uses the side as the key, every node also holds a red-black tree in which all sides are equal and the height is now the key.
What I got stuck with is implenting the GetBox method, I just can't figure out a better way than checking every single node for the smallest volume in which the side and height are equal or larger than those who were entered by the user.
MAIN PROGRAM
import java.util.Scanner;
class ProgramBody
{
public static BoxesRedBlackTree sideTree = new BoxesRedBlackTree();
public static void Main()
{
final int INSERTBOX = 1;
final int REMOVEBOX = 2;
final int GETBOX = 3;
final int CHECKBOX = 4;
final int EXIT = 5;
System.out.println("Welcome to the factory! What would you like to do?");
System.out.println("Insert a box - " + INSERTBOX);
System.out.println("Remove a box - " + REMOVEBOX);
System.out.println("Get a box - " + GETBOX);
System.out.println("Check if a box exists - " + CHECKBOX);
System.out.println("Exit - " + EXIT);
System.out.println("Please enter your choice: ");
Scanner in = new Scanner(System.in);
int Choise = in.nextInt();
// While the user doesn't exit the program
while (Choise != EXIT)
{
switch (Choise)
{
case (INSERTBOX):
{
System.out.println("Enter the size of the side");
int side = in.nextInt();
System.out.println("Enter the size of the height");
int height = in.nextInt();
InsertBox(side, height);
break;
}
case (REMOVEBOX):
{
System.out.println("Enter the size of the side");
int side = in.nextInt();
System.out.println("Enter the size of the height");
int height = in.nextInt();
RemoveBox(side, height);
break;
}
case (GETBOX):
{
System.out.println("Enter the size of the side");
int side = in.nextInt();
System.out.println("Enter the size of the height");
int height = in.nextInt();
GetBox(side, height);
break;
}
case (CHECKBOX):
{
System.out.println("Enter the size of the side");
int side = in.nextInt();
System.out.println("Enter the size of the height");
int height = in.nextInt();
boolean boxExists;
if (CheckBox(side, height))
{ System.out.println("The box exists."); }
else
{ System.out.println("The box doesn't exist."); }
break;
}
default:
{ System.out.println("You have entered an unfamiliar choice. Please try again."); }
}
System.out.println("\nWhat would you like to do now?");
System.out.println("Insert a box - " + INSERTBOX);
System.out.println("Remove a box - " + REMOVEBOX);
System.out.println("Get a box - " + GETBOX);
System.out.println("Check if a box exists - " + CHECKBOX);
System.out.println("Exit - " + EXIT);
System.out.println("Please enter your choice: ");
Choise = in.nextInt();
}
}
private static void InsertBox(int side, int height) {
if (sideTree.sideExist(side))
{
//Check if There is same box
if(sideTree.searchBySide(side).getTree().heightExist(height))
{ sideTree.searchBySide(side).getTree().searchByHeight(height).setCounter(sideTree.searchBySide(side).getTree().searchByHeight(height).getCounter()+1); }
else
{
BoxNode temp = new BoxNode(side, height, 1);
sideTree.searchBySide(side).getTree().insert(temp);
}
}
else
{
BoxNode heightTempNode = new BoxNode(side, height, 1);
BoxesRedBlackTree tempTree = new BoxesRedBlackTree();
tempTree.insert(heightTempNode);
BoxNode sideTempNode = new BoxNode(side, height, tempTree);
sideTree.insert(sideTempNode);
}
System.out.println("The box with side " + side + " and height " + height + " has been added.");
}
private static void RemoveBox(int side, int height) {
if(sideTree.sideExist(side) && sideTree.searchBySide(side).getTree().heightExist(height)) {
if (sideTree.searchBySide(side).getTree().searchByHeight(height).getCounter() > 1)
{
sideTree.searchBySide(side).getTree().searchByHeight(height).setCounter(sideTree.searchBySide(side).getTree().searchByHeight(height).getCounter()-1);
System.out.println("The box with side " + side + " and height " + height + " has been removed.");
}
else
{
sideTree.searchBySide(side).getTree().delete(sideTree.searchBySide(side).getTree().searchByHeight(height));
System.out.println("The box with side " + side + " and height " + height + " has been removed.");
}
if (!sideTree.searchBySide(side).getTree().sideExist(side))
{
sideTree.delete(sideTree.searchBySide(side));
} }
else
{ System.out.println("There isn't a box with the requested size!"); }
}
private static BoxNode GetBox(int side, int height) {
BoxNode currentNode = sideTree.getMin(sideTree.getRoot());
BoxNode minimalBox = sideTree.getRoot();
// Check all The Tree
while (currentNode != null)
{ if(currentNode.getSide() >= side && currentNode.getHeight() >= height && currentNode.getVolume() < minimalBox.getVolume()) { minimalBox = currentNode; }}
currentNode = sideTree.getSuccessor(currentNode);
if (minimalBox != null)
{ System.out.println("The box with side " + minimalBox.getSide() + " and height " + minimalBox.getSide() + " has returned."); }
else
{ System.out.println("No box was found."); }
return (minimalBox);
}
private static boolean CheckBox(int side, int height) {
if ((GetBox(side,height)) != null)
{ return true; }
return false; }
}
RED BLACK TREE
public class BoxesRedBlackTree
{
private BoxNode _root;
/**
* Constructor
*/
public BoxesRedBlackTree ()
{
_root = null;
}
/**
* Reset root
*/
public BoxesRedBlackTree(BoxNode box)
{
_root = new BoxNode(box);
}
/**
* get -root method
*/
public BoxNode getRoot()
{
return _root;
}
/**
* LEFT-ROTATE algorithm
*/
public void leftRotate(BoxNode x)
{
BoxNode y = x.getRightSon();
x.setRightSon(y.getLeftSon());
if(y.getLeftSon() != null)
{
y.getLeftSon().setParent(x);
}
y.setParent(x.getParent());
if(x.getParent() == null)
{
_root = y;
}
else if( x == x.getParent().getLeftSon())
{
x.getParent().setLeftSon(y);
}
else
{
x.getParent().setRightSon(y);
}
y.setLeftSon(x);
x.setParent(y);
}
/**
* RIGHT-ROTATE algorithm
* Assumes right son of x is not null.
*/
public void rightRotate(BoxNode x)
{ //assumes left son is not null
BoxNode y = x.getLeftSon();
x.setLeftSon(y.getRightSon());
if(y.getRightSon() != null)
{
y.getRightSon().setParent(x);
}
y.setParent(x.getParent());
if(x.getParent() == null)
{
_root = y;
}
else if( x == x.getParent().getRightSon())
{
x.getParent().setRightSon(y);
}
else
{
x.getParent().setLeftSon(y);
}
y.setRightSon(x);
x.setParent(y);
}
/**
* RB-INSERT algorithm
*/
public void insert(BoxNode toInsert)
{
BoxNode x = _root;
BoxNode y = null;
while(x != null){
y = x;
if(toInsert.isBigger(x))
{
x = x.getLeftSon();
}
else
{
x = x.getRightSon();
}
}
toInsert.setParent(y);
if(y == null)
{
_root = toInsert;
}
else if(toInsert.isBigger(y))
{
y.setLeftSon(toInsert);
}
else
{
y.setRightSon(toInsert);
}
toInsert.setLeftSon(null);
toInsert.setRightSon(null);
toInsert.setColor(1); // red
insertFixUp(toInsert);
}
/**
* RB-INSERT-FIXUP algorithm
*/
private void insertFixUp(BoxNode z)
{
BoxNode y = null;
while (z != _root && z.getParent().getColor() == 1)
{
if(z.getParent() == z.getParent().getParent().getLeftSon())
{
y = z.getParent().getParent().getRightSon();
if(y != null && y.getColor()== 1)
{
z.getParent().setColor(0);
y.setColor(0);
z = z.getParent().getParent();
}
else
{
if (z == z.getParent().getRightSon())
{
z = z.getParent();
this.leftRotate(z);
}
z.getParent().setColor(0);
z.getParent().getParent().setColor(1);
rightRotate(z.getParent().getParent());
}
}
else
{
y = z.getParent().getParent().getLeftSon();
if(y != null && y.getColor() == 1)
{
z.getParent().setColor(0);
y.setColor(0);
z = z.getParent().getParent();
}
else
{
if (z == z.getParent().getLeftSon()){
z = z.getParent();
this.rightRotate(z);
}
z.getParent().setColor(0);
z.getParent().getParent().setColor(1);
this.leftRotate(z.getParent().getParent());
}
}
}
_root.setColor(0);
}
/**
* RB-DELETE algorithm
*/
public void delete(BoxNode z)
{
BoxNode y = null;
BoxNode x = null;
if(z.getLeftSon() == null || z.getRightSon() == null)
{
y = z;
}
else
{
y = getSuccessor(z);
}
if(y.getLeftSon() != null)
{
x = y.getLeftSon();
}
else
{
x=y.getRightSon();
}
if (x != null && y != null)
{
x.setParent(y.getParent());
}
if(y.getParent() == null)
{
_root = x;
}
else if(y == y.getParent().getLeftSon())
{
y.getParent().setLeftSon(x);
}
else
{
y.getParent().setRightSon(x);
}
if(y != z)
{
z.setSide(y.getSide());
}
if(y.isBlack())
{
deleteFixUp(x);
}
}
/**
* RB-DELETE-FIXUP algorithm
*/
private void deleteFixUp(BoxNode x)
{
BoxNode temp = null;
while (x != null && x != _root && x.isBlack())
{
if (x == x.getParent().getLeftSon())
{
temp = x.getParent().getRightSon();
if (!temp.isBlack())
{
temp.setColor(0);
x.getParent().setColor(1);
leftRotate(x.getParent());
temp = x.getParent().getRightSon();
}
if (temp.getLeftSon().isBlack() && temp.getRightSon().isBlack() )
{
temp.setColor(1);
x = x.getParent();
}
else
{
if (temp.getRightSon().isBlack())
{
temp.getLeftSon().setColor(0);
temp.setColor(1);
rightRotate (temp);
temp = x.getParent().getRightSon();
}
temp.setColor(x.getParent().getColor());
x.getParent().setColor(0);
temp.getRightSon().setColor(0);
leftRotate (x.getParent());
x = _root;
}
}
else
{
temp = x.getParent().getLeftSon();
if (!temp.isBlack())
{
temp.setColor(0);
x.getParent().setColor(1);
rightRotate(x.getParent());
temp = x.getParent().getLeftSon();
}
if (temp.getRightSon().isBlack() && temp.getLeftSon().isBlack())
{
temp.setColor(1);
x = x.getParent();
}
else
{
if (temp.getLeftSon().isBlack())
{
temp.getRightSon().setColor(0);
temp.setColor(1);
leftRotate (temp);
temp = x.getParent().getLeftSon();
}
temp.setColor(x.getParent().getColor());
x.getParent().setColor(0);
temp.getLeftSon().setColor(0);
rightRotate (x.getParent());
x = _root;
}
}
x.setColor(0);
}
}
/**
* returns the successor BoxNode of a certain BoxNodeFrom.
*/
public BoxNode getSuccessor(BoxNode BoxNodeFrom)
{
if(BoxNodeFrom.getRightSon() != null)
{
return getMin(BoxNodeFrom.getRightSon());
}
BoxNode temp = BoxNodeFrom.getParent();
while ((temp != null) && (BoxNodeFrom == temp.getRightSon()))
{
BoxNodeFrom = temp;
temp = temp.getParent();
}
return temp;
}
/**
* Get the minimum Valued BoxNode from a given subtree
*/
public BoxNode getMin(BoxNode BoxNodeFrom)
{
while (BoxNodeFrom.getLeftSon() != null)
{
BoxNodeFrom = BoxNodeFrom.getLeftSon();
}
return BoxNodeFrom;
}
/**
* Get the maximum Valued BoxNode from a given subtree
*/
public BoxNode getMax(BoxNode BoxNodeFrom)
{
while (BoxNodeFrom.getRightSon() != null)
{
BoxNodeFrom = BoxNodeFrom.getRightSon();
}
return BoxNodeFrom;
}
/**
* search height in the Tree
* return the BoxNode Node if it exict, and NULL otherwise
*/
public BoxNode searchByHeight(int boxCode)
{
BoxNode temp = _root;
while (temp != null)
{
if (temp.getHeight() > boxCode)
{
temp = temp.getRightSon();
}
else if (temp.getHeight() < boxCode)
{
temp = temp.getLeftSon();
}
else
{
return temp;
}
}
return null;
}
/**
* check if there is a height
*/
public boolean heightExist(int height)
{
BoxNode temp = searchByHeight(height);
if(temp == null)
{ return false; }
return true;
}
/**
* search side in the Tree
* return the BoxNode Node if it exict, and NULL otherwise
*/
public BoxNode searchBySide(int boxCode)
{
BoxNode temp = _root;
while (temp != null)
{
if (temp.getSide() > boxCode)
{
temp = temp.getRightSon();
}
else if (temp.getSide() < boxCode)
{
temp = temp.getLeftSon();
}
else
{
return temp;
}
}
return null;
}
/**
* check if there is a side
*/
public boolean sideExist(int side)
{
BoxNode temp = searchBySide(side);
if(temp == null)
{ return false; }
return true;
}
}
NODE
public class BoxNode
{
private int color=0; // black is 0 red is 1.
private BoxNode rightSon, leftSon, parent;
private int _side;
private int _height;
private int _counter;
private BoxesRedBlackTree _pointer;
/**
* Constructor
*/
public BoxNode(int side,int height)
{
_side = side;
_height = height;
}
public BoxNode(int side,int height,int counter)
{
_side = side;
_height = height;
_counter = counter;
}
public BoxNode(int side,int height, BoxesRedBlackTree pointer)
{
_side = side;
_height = height;
_pointer = pointer;
}
/**
* Copy Constructor
*/
public BoxNode(BoxNode box)
{
_side = box._side;
_height = box._height;
}
//get Method
/**
* get the height of the node
*/
public int getHeight()
{
return _height;
}
/**
* get the side of the box
*/
public int getSide()
{
return _side;
}
public int getCounter()
{
return _counter;
}
public BoxesRedBlackTree getTree()
{
return _pointer;
}
/**
* get the volume of the box
*/
public int getVolume()
{
return (_side)*(_side)*(_height);
}
public BoxNode getRightSon()
{
return rightSon;
}
public BoxNode getLeftSon()
{
return leftSon;
}
public BoxNode getParent()
{
return parent;
}
public int getColor()
{
return color;
}
//set Method
/**
* set the side
*/
public void setSide(int side)
{
_side = side;
}
/**
* set the height
*/
public void setHeight(int height)
{
_height = height;
}
public void setCounter(int counter)
{
_counter = counter;
}
public void setTree(BoxesRedBlackTree pointer)
{
_pointer = pointer;
}
public void setRightSon(BoxNode toSet)
{
rightSon = toSet;
}
public void setLeftSon(BoxNode toSet)
{
leftSon = toSet;
}
public void setParent(BoxNode toSet )
{
parent = toSet;
}
public void setColor(int toSet)
{
if(toSet == 1 || toSet == 0)
{
color = toSet;
}
}
public boolean isBigger(BoxNode other)
{
if(_side > other._side)
{
return true;
}
return false;
}
public boolean isBlack()
{ return true; }
}
Have you considered using the volume of the box as the key? In your getBox method, you can get the box with the smallest volume that is greater than or equal to side^2*height. Then, check if it can fit the given side and height. If not, try the next bigger one. This can perform decently, depending on the sizes of your boxes and presents.
If you're really bent on nested red-black trees, you can do th following:
1) Find the sideNode with the minimum side that's greater than or equal to the given side.
2) Find the heightNode with the minimum height that's greater than or equal to the given height. Hold it in a variable named minVolumeBox.
3) Get the next sideNode. Keep doing this until
a) You check all the available sideNodes or
b) The side of the sideNode is bigger than (volume of minVolumeBox / given height).
Case 3b will save you some time over checking every single node.
Related
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.
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 have been working on this problem for several hours now and I just cannot figure out what I am doing wrong here. Could anyone help point me in the right direction?
I was asked to write an Autocomplete program and I've completed everything except for this one method I cannot get working. Each term has: 1. String query and 2. long weight.
Here is the method:
public static Comparator<Term> byReverseWeightOrder() {
return new Comparator<Term>() { // LINE CAUSING PROBLEM
public int compare(Term t1, Term t2) {
if (t1.weight > t2.weight) { // LINE CAUSING PROBLEM
return -1;
} else if (t1.weight == t2.weight) {
return 0;
} else {
return 1;
}
}
};
}
My problem is that no matter how I mess with the method I always result in a NullPointerException(). Which, it points to this method (byReverseWeightOrder) as well as these two statements.
Arrays.sort(matches, Term.byReverseWeightOrder());
Term[] results = autocomplete.allMatches(prefix);
Here is the rest of the code if it can be found helpful:
Term
import java.util.Comparator;
public class Term implements Comparable<Term> {
public String query;
public long weight;
public Term(String query, long weight) {
if (query == null) {
throw new java.lang.NullPointerException("Query cannot be null");
}
if (weight < 0) {
throw new java.lang.IllegalArgumentException("Weight cannot be negative");
}
this.query = query;
this.weight = weight;
}
public static Comparator<Term> byReverseWeightOrder() {
return new Comparator<Term>() {
public int compare(Term t1, Term t2) {
if (t1.weight > t2.weight) {
return -1;
} else if (t1.weight == t2.weight) {
return 0;
} else {
return 1;
}
}
};
}
public static Comparator<Term> byPrefixOrder(int r) {
if (r < 0) {
throw new java.lang.IllegalArgumentException("Cannot order with negative number of characters");
}
final int ref = r;
return
new Comparator<Term>() {
public int compare(Term t1, Term t2) {
String q1 = t1.query;
String q2 = t2.query;
int min;
if (q1.length() < q2.length()) {
min = q1.length();
}
else {
min = q2.length();
}
if (min >= ref) {
return q1.substring(0, ref).compareTo(q2.substring(0, ref));
}
else if (q1.substring(0, min).compareTo(q2.substring(0, min)) == 0) {
if (q1.length() == min) {
return -1;
}
else {
return 1;
}
}
else {
return q1.substring(0, min).compareTo(q2.substring(0, min));
}
}
};
}
public int compareTo(Term that) {
String q1 = this.query;
String q2 = that.query;
return q1.compareTo(q2);
}
public long getWeight() {
return this.weight;
}
public String toString() {
return this.weight + "\t" + this.query;
}
}
BinarySearchDeluxe
import java.lang.*;
import java.util.*;
import java.util.Comparator;
public class BinarySearchDeluxe {
public static <Key> int firstIndexOf(Key[] a, Key key, Comparator<Key> comparator) {
if (a == null || key == null || comparator == null) {
throw new java.lang.NullPointerException();
}
if (a.length == 0) {
return -1;
}
int left = 0;
int right = a.length - 1;
while (left + 1 < right) {
int middle = left + (right - left)/2;
if (comparator.compare(key, a[middle]) <= 0) {
right = middle;
} else {
left = middle;
}
}
if (comparator.compare(key, a[left]) == 0) {
return left;
}
if (comparator.compare(key, a[right]) == 0) {
return right;
}
return -1;
}
public static <Key> int lastIndexOf(Key[] a, Key key, Comparator<Key> comparator) {
if (a == null || key == null || comparator == null) {
throw new java.lang.NullPointerException();
}
if (a == null || a.length == 0) {
return -1;
}
int left = 0;
int right = a.length - 1;
while (left + 1 < right) {
int middle = left + (right - left)/2;
if (comparator.compare(key, a[middle]) < 0) {
right = middle;
} else {
left = middle;
}
}
if (comparator.compare(key, a[right]) == 0) {
return right;
}
if (comparator.compare(key, a[left]) == 0) {
return left;
}
return -1;
}
}
AutoComplete
import java.util.Arrays;
import java.util.Scanner;
import java.io.File;
import java.io.IOException;
import java.util.Comparator;
public class Autocomplete {
public Term[] terms;
public Autocomplete(Term[] terms) {
if (terms == null) {
throw new java.lang.NullPointerException();
}
this.terms = terms.clone();
Arrays.sort(this.terms);
}
public Term[] allMatches(String prefix) {
if (prefix == null) {
throw new java.lang.NullPointerException();
}
Term theTerm = new Term(prefix, 0);
int start = BinarySearchDeluxe.firstIndexOf(terms, theTerm, Term.byPrefixOrder(prefix.length()));
int end = BinarySearchDeluxe.lastIndexOf(terms, theTerm, Term.byPrefixOrder(prefix.length()));
int count = start;
System.out.println("Start: " + start + " End: " + end);
if (start == -1 || end == -1) {
// System.out.println("PREFIX: " + prefix);
throw new java.lang.NullPointerException();
} // Needed?
Term[] matches = new Term[end - start + 1];
//matches = Arrays.copyOfRange(terms, start, end);
for (int i = 0; i < end - start; i++) {
matches[i] = this.terms[count];
count++;
}
Arrays.sort(matches, Term.byReverseWeightOrder());
System.out.println("Finished allmatches");
return matches;
}
public int numberOfMatches(String prefix) {
if (prefix == null) {
throw new java.lang.NullPointerException();
}
Term theTerm = new Term(prefix, 0);
int start = BinarySearchDeluxe.firstIndexOf(terms, theTerm, Term.byPrefixOrder(prefix.length()));
int end = BinarySearchDeluxe.lastIndexOf(terms, theTerm, Term.byPrefixOrder(prefix.length()));
System.out.println("Finished numberMatches");
return end - start + 1; // +1 needed?
}
public static void main(String[] args) throws IOException {
// Read the terms from the file
Scanner in = new Scanner(new File("wiktionary.txt"));
int N = in.nextInt(); // Number of terms in file
Term[] terms = new Term[N];
for (int i = 0; i < N; i++) {
long weight = in.nextLong(); // read the next weight
String query = in.nextLine(); // read the next query
terms[i] = new Term(query.replaceFirst("\t",""), weight); // construct the term
}
Scanner ip = new Scanner(System.in);
// TO DO: Data Validation Here
int k;
do {
System.out.println("Enter how many matching terms do you want to see:");
k = ip.nextInt();
} while (k < 1 || k > N);
Autocomplete autocomplete = new Autocomplete(terms);
// TO DO: Keep asking the user to enter the prefix and show results till user quits
boolean cont = true;
do {
// Read in queries from standard input and print out the top k matching terms
System.out.println("Enter the term you are searching for. Enter * to exit");
String prefix = ip.next();
if (prefix.equals("*")) {
cont = false;
break;
}
Term[] results = autocomplete.allMatches(prefix);
System.out.println(results.length);
for(int i = 0; i < Math.min(k,results.length); i++)
System.out.println(results[i].toString());
} while(cont);
System.out.println("Done!");
}
}
I apologize for the sloppy code, I have been pulling my hair out for awhile now and keep forgetting to clean it up.
Two examples:
Example 1:
int k = 2;
String prefix = "auto";
Enter how many matching terms do you want to see:
2
Enter the term you are searching for. Enter * to exit
auto
619695 automobile
424997 automatic
Example 2:
int k = 5;
String prefix = "the";
Enter how many matching terms do you want to see:
5
Enter the term you are searching for. Enter * to exit
the
5627187200 the
334039800 they
282026500 their
250991700 them
196120000 there
Hi I am implementing an A* search algorithm based on WikiLink but I am not getting an answer from my search at all. any hints and/or help on what I may be doing wrong
I get a map that contains dirt and obstacles, I need to travel to all dirt particles and hoover them up and return to my starting point
Search Function
private void search_Astar(State start){ // we have confirmed a valid start state
// needed vars
Map<State, Integer> g_score = new HashMap<>();
Map<State, Integer> f_score = new HashMap<>();
Queue<State> openQueue = new PriorityQueue<State>(((State o1, State o2) -> f_score.get(o1) - f_score.get(o2)));
HashSet<State> closedSet = new HashSet<>();
// inits
g_score.put(start, 0);
f_score.put(start, start.heuristic());
openQueue.add(start);
// search
while (!openQueue.isEmpty()){
State current = openQueue.poll();
if (current.isGoal()){
// create path
solutionStack.push(Actions.Turn_Off);
while (current.takenAction != null){
solutionStack.push(current.takenAction);
current = current.parent;
}
return;
}
closedSet.add(current);
for (String action:current.legalActions()){
State child = current.CreateState(action);
if (closedSet.contains(child)) continue;
int tentativeG = g_score.get(current) + 1; // the cost to take an action
if (!openQueue.contains(child)) openQueue.add(child);
else if (tentativeG >= g_score.getOrDefault(child, Integer.MAX_VALUE)) continue;
g_score.put(child, tentativeG);
f_score.put(child, tentativeG + child.heuristic());
}
}
}
and State class (sorry for huge-ness)
import java.util.ArrayList;
import java.util.Collection;
public class State {
// keep track of position and orientation
public Orientation orientation;
public Position position;
public boolean turned_on;
// track world state
private Collection<Position> dirt;
private Collection<Position> obstacles;
private Position size;
// variables for detirmining goal state and heuristic
private Position home;
// for calculating G
public State parent;
public String takenAction;
public State(Position position, Orientation orientation, boolean turned_on, Collection dirt, Collection obs, Position home,
Position size, State parent, String action) {
this.position = position;
this.orientation = orientation;
this.turned_on = turned_on;
this.dirt = dirt;
this.obstacles = obs;
this.home = home;
this.size = size;
this.parent = parent;
this.takenAction = action;
}
public int G(){
State curr = parent;
int total = 0;
while (curr != null){
total += 1;
curr = parent.parent;
}
return total;
}
public String toString() {
return "State{position: " + position + ", orientation: " + orientation + ", on:" + turned_on + "}";
}
#Override
public int hashCode(){
int hashVal = 23;
hashVal = ((hashVal + position.x) << 5) - (hashVal + position.x);
hashVal = ((hashVal + position.y) << 5) - (hashVal + position.y);
if(turned_on){
hashVal += 1243;
}
if (orientation == Orientation.NORTH){
hashVal += 12;
}else if (orientation == Orientation.EAST){
hashVal += 2234;
}else if (orientation == Orientation.WEST){
hashVal += 32345;
}
return hashVal + dirt.size();
}
#Override
public boolean equals(Object o){
if (o instanceof State){
State other = (State)o;
if(!other.position.equals(this.position)){
return false;
}else if( other.turned_on != this.turned_on){
return false;
}else if( other.orientation != this.orientation){
return false;
}else if (other.dirt.size() != this.dirt.size()){
return false;
} else{
return true;
}
}else {
return false;
}
}
/**
* check to see if current node is a valid goal node
*
* #return true if valid, false otherwise
*/
public Boolean isGoal(){
return dirt.isEmpty() && position.equals(home);
}
/**
* Calculate all legal moves from current state
*
* #return Collection of legal actions (as string)
*/
public Collection<String> legalActions(){
Collection<String> actions = new ArrayList<String>();
if (!turned_on){
actions.add(Actions.Turn_On);
}else{
// no reason we couldn't turn
actions.add(Actions.Turn_Left);
actions.add(Actions.Turn_Right);
if (dirt.contains(position)){
actions.add(Actions.Suck);
}
// check if we can move forward
// ATTENTION this must be at bottom of function
if (orientation == Orientation.NORTH){
for (Position o: obstacles) {
if (o.y == position.y + 1 && o.x == position.x || position.y == size.y){
return actions;
}
}
// we have checked every obstacle and we would not collide
actions.add(Actions.Go);
}else if (orientation == Orientation.SOUTH){
for (Position o: obstacles) {
if (o.y == position.y - 1 && o.x == position.x || position.y == 1){
return actions;
}
}
actions.add(Actions.Go);
}else if (orientation == Orientation.WEST){
for (Position o: obstacles) {
if (o.x == position.x - 1 && o.y == position.y|| position.x == 1){
return actions;
}
}
actions.add(Actions.Go);
}else {
// we are pointing east
for (Position o: obstacles) {
if (o.x == position.x + 1 && o.y == position.y|| position.x == size.x){
return actions;
}
}
actions.add(Actions.Go);
}
}
return actions;
}
/**
* return a value to determine how optimal this state is
*
* evaluation methood: number of dirt left times a constant
* which is the added to the distance to the closest dirt
* if there are no dirts left it is the mannhatan distance to home
*
* note: should add state depth?
* #return int
*/
public int heuristic(){
int h = 0;
for (Position p:obstacles){
h += mannhatandist(p);
}
h += mannhatandist(home);
return h + dirt.size();
}
private int mannhatandist(Position p){
return Math.abs(p.x - position.x) + Math.abs(p.y - position.y);
}
private int distToClosest(){
int min = Integer.MAX_VALUE;
if (dirt.isEmpty()){
int dist = Math.abs(home.x - position.x) + Math.abs(home.y - position.y);
return dist;
}
for (Position p: dirt) {
int dist = Math.abs(p.x - position.x) + Math.abs(p.y - position.y);
min = Math.min(min, dist);
}
if (!turned_on) min++;
return min;
}
public State CreateState(String action) {
//Copy the "old" values from the current state.
//Before any action done, the "new" state has the same values.
Position new_position = new Position(position.x, position.y);
Orientation new_orientation = orientation;
boolean new_turned_on = turned_on;
Collection new_dirt = new ArrayList(dirt);
State new_parent = this;
//If the action is to turn the robot on, the turned_on variable for the new state takes the value "true".
if(action == "TURN_ON") {
new_turned_on = true;
}
//If the action is to turn the robot on, the turned_on variable for the new state takes the value "true".
else if(action == "TURN_OFF") {
new_turned_on = false;
}
//If the action is to suck, the current position needs to be taken out of the new state's dirt collection.
else if(action == "SUCK") {
new_dirt.remove(position);
}
//If the action is to go it depends on the orientation of the robot how the position of the new state will change.
else if(action == "GO") {
//If it's facing north, the y position increases by one, considering the old state and so on.
if(orientation == Orientation.NORTH) {
new_position.y++;
}
else if(orientation == Orientation.EAST) {
new_position.x++;
}
else if(orientation == Orientation.SOUTH) {
new_position.y--;
}
else {
new_position.x--;
}
}
//If the action is to turn left it depends on the orientation of the robot in the current state what the new orientation will be.
else if(action == "TURN_LEFT") {
if(orientation == Orientation.NORTH) {
new_orientation = Orientation.WEST;
}
else if(orientation == Orientation.EAST) {
new_orientation = Orientation.NORTH;
}
else if(orientation == Orientation.SOUTH) {
new_orientation = Orientation.EAST;
}
else {
new_orientation = Orientation.SOUTH;
}
}
//If the action is to turn right it depends on the orientation of the robot in the current state what the new orientation will be.
else if(action == "TURN_RIGHT") {
if(orientation == Orientation.NORTH) {
new_orientation = Orientation.EAST;
}
else if(orientation == Orientation.EAST) {
new_orientation = Orientation.SOUTH;
}
else if(orientation == Orientation.SOUTH) {
new_orientation = Orientation.WEST;
}
else {
new_orientation = Orientation.NORTH;
}
}
//Make a new state from the new variables that have been changed according to the action done.
State new_state = new State(new_position, new_orientation, new_turned_on, new_dirt, obstacles, home, size, new_parent, action);
return new_state;
}
}
I have Fixed it now. the problem was that my heuristic was not consistent. I have modified it and it is performing correctly now
I will not share my answer since this was for a school project and since the automatic anti-cheating system at school will probably detect this I will add my school username to this comment (dagur13)
p.s. I will post my updated code when I have recieved a grade for the project