0

I have a node class which is contained in a SortedDictionary:

SortedDictionary<Node, bool> openList = new SortedDictionary<Node, bool>();

I need to write the CompareTo method on the node, so that the nodes are sorted from lowest to highest based on an int value(F). I also need to be able to check if a node already exists in the dictionary based on their position (this position is what makes the node Unique). The CompareTo looks like this:

public int CompareTo(Node other)
{
    if (GridPosition != other.GridPosition)
    {
        if (F > other.F)
        {
            return 1;
        }
        if (F < other.F)
        {
            return -1;
        }
        return -1;
    }

    return 0;
}

The problem is, that it doesn't always return the correct result. For example, the following line of code will return false even though the node is in the dictionary. However it sorts all the nodes that I add to the dictionary exactly as I want it.

Node node = new Node(); //It has a grid position and a f value
openList.add(node, false);

if(openList.Keys.Contains(node)) //this returns false
{

} 

To fix this I created an EqualityComparer and used it while comparing the values. The equality comparer looks like this:

class NodeEqualityComparer : IEqualityComparer<Node>
{
    public bool Equals(Node x, Node y)
    {
        return x.GridPosition == y.GridPosition;
    }

    public int GetHashCode(Node obj)
    {
        return obj.GridPosition.GetHashCode();
    }
}

I'm using this equality comparer as a parameter in the Contains method on the SortedDictionary, this works fine and it returns the correct result based on the content in the Dictionary:

if (openList.Keys.Contains(currentNode,new NodeEqualityComparer())) //This will return true if the node is in the dictionary
{

}

The problem occours when I need to remove a node from the Dictionary. The remove function is using the CompareTo method on the Node to find the node to remove, and as I stated earlier, this function appreantly doesn't return the correct result when comparing the objects. This means that the code below will not remove the node from the dictionary 100% of the time. You can't pass an equalityComparer to the remove function, so that can't fix my problem.

   Node node = new Node(); //It has a grid position and a f value
    openList.add(node, false);
    openList.Remove(node);

Maybe I'm handeling this in a wrong way, so if anyone have some suggestions for solving this problem I'll be very happy to hear them. Maybe I can write a CompareTo function that solves the problem?

Karstan
  • 11
  • 4
  • You should return 0 at the end of the if-statements instead of -1. You should always make sure that `A.CompareTo(B)` is the opposite of `B.CompareTo(A)`. – Lasse V. Karlsen Dec 27 '15 at 20:44
  • If I return 0 after the if statement it will not allow me to have two nodes with the same F value in the dictionary. – Karstan Dec 27 '15 at 21:07
  • Then you need to expand your comparison. You should always follow the rule I wrote in my first comment, the sorting algorithms depend on it, if you don't do that, don't be surprised that the sorting algorithms do odd things. – Lasse V. Karlsen Dec 27 '15 at 21:08
  • The problem is that you've answered this question: "Should A or B come first" with "Yes". And that's not a good answer for that question. – Lasse V. Karlsen Dec 27 '15 at 21:09
  • Yea I understand I'll see if I can wrap my head around it :) – Karstan Dec 27 '15 at 21:16

1 Answers1

0

This problem is probably a result of you comparing GridPosition in the comparer. C# distinguishes between value types and reference types. If you compare two int variables the comparer will base equality on the value. For instantiated classes however the comparisson is based upon the reference, meaning you are comparing whether an object is the object in memory.

This:

if (GridPosition != other.GridPosition)
{

will probably almost always return true, thus resulting in your sort order being correct, because you are providing the order int he if statment nested inside:

if (F > other.F)
{
    return 1;
}
if (F < other.F)
{
    return -1;
}

If however (F == other.F) you assign -1, so the ContainsKey will not recognize these objects as equal.

Depending on what type GridPosition is, you should provide an appropriate comparer, for instance something like

if (GridPosition.X != other.GridPosition.X && GridPosition.Y != other.GridPosition.Y)

And depending on what F is, you are violating the mathematical principal of symmetry, as an object with different GridPosition and equal F will alway be sorted before any other object fulfilling this. Thus for two objects you could get A == B ==> -1 and B == A ==> -1 which isnt useful in any case.

So you would have to do this in the nested if:

if (GridPosition != other.GridPosition)
{
    if (F > other.F)
    {
        return 1;
    }
    if (F < other.F)
    {
        return -1;
    }
    /////////////////////////////////
    return 0;
    /////////////////////////////////
}

or expand the comparission to a third condition beyond GridPosition and F

EDIT: Based on the information you provided in the comments, you don't actually want a dictionary in this case, just a list with proper sorting. For instance:

        List<Node> myList = new List<Node>();
        myList.Sort((node1, node2) => node1.F > node2.F);//Sort the list based on their node value

As for your bool; I don't know what this represents, but I'm under the strong impression that you want a regular Dictionary<Node, bool> for this. Then use the list myList to handle whatever node you need as next waypoint and lookup the bool corresponding to a node in the dictionary whenever you need it.

srandppl
  • 571
  • 4
  • 14
  • Thx for the answer, I didn't think about reference types vs value types. I changed the grid position from a class to a struct, but I'm still having the same problem. Also if I return 0 instead of -1 the dictionary will not allow me to have multiple nodes with the same F value. So maybe I'll have to figure out how to implement a third condition as you suggested. – Karstan Dec 27 '15 at 21:31
  • It would help to know what your F actually is. It seems weird that you want the comparer to behave in this way. You may need to split this up as well, as Node is probably not what you really want as a dictionary key in this case. – srandppl Dec 27 '15 at 21:34
  • Its just a node in an astar algorithm. F is the total value of the node, which is stored as an int. The algorithm needs to pick the node with the lowest F value thats why I need to sort it based on this value. The dictionary can hold more nodes with the same F value, thats why I cant use it to check if two nodes are the equal. – Karstan Dec 27 '15 at 21:44
  • I wanted to use the dictionary over a normal list because of it's big O notation, since I'll be adding, removing and looking for nodes in the collection all the time. – Karstan Dec 27 '15 at 21:57
  • In a regular a star algorithm you do the sorting not very often. My suggestion is not to abandon the Dictionary, but to out-source the sorting. Also you might want a Dictionary where your value is the distance to a certain position. – srandppl Dec 27 '15 at 22:02
  • This is the first time I'm using a sorted dictionaryh to do this, my though was, that it is cheaper to keep a sorted dictionary, then iterating through a whole list og x nodes to find the one with the lowest value. Usually I just sort my list so that I can get the one with the lowest value, and that happen very often. – Karstan Dec 28 '15 at 04:20
  • In a list that is sorted, the item with the lowest values is MyList[0] - that is computationally very cheap. If you need to keep the complete list sorted all the time (which you usually don't with an A star algorithm) you can use the SortedList type. I really don't see a reason why you would need a dictionary here. – srandppl Dec 28 '15 at 04:27
  • A list that is sorted, needs to be sorted first, before you can take the first element and use it as the one with the lowest value. You would have to keep the list sorted every time you pick and examine a new node, so its fairly often. Besides this its way cheaper to find a specific node in a dictionary than in a normal list. In general a SortedDictionary has faster insertion and removal than a SortedList, since its a binary search tree. Anyway I think I'm going to use a hashset instead to get around my problem. – Karstan Dec 28 '15 at 09:21