【问题标题】:Can't remove object from SortedDictionary C#无法从 SortedDictionary C# 中删除对象
【发布时间】:2016-04-01 18:40:11
【问题描述】:

我有一个包含在 SortedDictionary 中的节点类:

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

我需要在节点上编写 CompareTo 方法,以便节点根据一个 int 值(F)从低到高排序。我还需要能够根据节点的位置检查字典中是否已经存在节点(这个位置是使节点唯一的原因)。 CompareTo 看起来像这样:

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;
}

问题是,它并不总是返回正确的结果。例如,即使节点在字典中,以下代码行也会返回 false。但是,它会完全按照我的需要对我添加到字典中的所有节点进行排序。

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
{

} 

为了解决这个问题,我创建了一个 EqualityComparer 并在比较值时使用它。相等比较器如下所示:

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();
    }
}

我在 SortedDictionary 的 Contains 方法中使用这个相等比较器作为参数,这很好用,它会根据 Dictionary 中的内容返回正确的结果:

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

}

当我需要从字典中删除一个节点时,就会出现问题。 remove 函数使用 Node 上的 CompareTo 方法来查找要删除的节点,正如我之前所说,这个函数在比较对象时显然不会返回正确的结果。这意味着下面的代码不会 100% 地从字典中删除节点。您不能将equalComparer 传递给remove 函数,因此无法解决我的问题。

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

也许我以错误的方式处理这个问题,所以如果有人对解决这个问题有一些建议,我会很高兴听到他们的意见。或许我可以写一个CompareTo函数来解决这个问题?

【问题讨论】:

  • 您应该在 if 语句的末尾返回 0 而不是 -1。您应该始终确保 A.CompareTo(B) 与 B.CompareTo(A) 相反。
  • 如果我在 if 语句之后返回 0,它将不允许我在字典中有两个具有相同 F 值的节点。
  • 那你需要扩大你的比较。你应该始终遵循我在第一条评论中写的规则,排序算法依赖于它,如果你不这样做,不要对排序算法做奇怪的事情感到惊讶。
  • 问题是你已经用“是”回答了这个问题:“A 还是 B 应该先来”。这不是这个问题的好答案。
  • 是的,我知道我会看看我能不能把头绕过去 :)

标签: c# dictionary collections iequalitycomparer icomparer


【解决方案1】:

这个问题可能是您在比较器中比较GridPosition 的结果。 C# 区分值类型和引用类型。如果您比较两个 int 变量,比较器将基于值相等。然而,对于实例化的类,比较是基于引用的,这意味着您正在比较一个对象是否是内存中的对象。

这个:

if (GridPosition != other.GridPosition)
{

可能几乎总是返回 true,从而导致您的排序顺序是正确的,因为您提供的顺序是嵌套在里面的 if 语句:

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

但是,如果 (F == other.F) 分配了 -1,那么 ContainsKey 将无法将这些对象识别为相等。

根据 GridPosition 的类型,您应该提供适当的比较器,例如

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

根据F 是什么,您违反了对称性的数学原理,因为具有不同 GridPosition 和相等 F 的对象将始终在任何其他满足此要求的对象之前排序。因此,对于两个对象,您可以获得A == B ==&gt; -1 和B == A ==&gt; -1,这在任何情况下都没有用。

所以你必须在嵌套的 if 中这样做:

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

或将比较扩展到GridPosition 和F 之外的第三个条件

编辑:根据您在 cmets 中提供的信息,在这种情况下您实际上并不需要字典,只需要一个正确排序的列表。例如:

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

至于你的布尔值;我不知道这代表什么,但我的强烈印象是您需要一个常规的Dictionary&lt;Node, bool&gt;。然后使用列表myList 处理您需要的任何节点作为下一个航路点,并在需要时在字典中查找与节点对应的布尔值。

【讨论】:

  • 感谢答案,我没有考虑引用类型与值类型。我将网格位置从一个类更改为一个结构,但我仍然遇到同样的问题。此外,如果我返回 0 而不是 -1,字典将不允许我拥有多个具有相同 F 值的节点。所以也许我必须按照你的建议弄清楚如何实现第三个条件。
  • 了解你的 F 实际上是什么会有所帮助。您希望比较器以这种方式运行似乎很奇怪。您可能还需要将其拆分,因为在这种情况下,Node 可能不是您真正想要的字典键。
  • 它只是astar算法中的一个节点。 F是节点的总值,存储为int。该算法需要选择具有最低 F 值的节点,这就是为什么我需要根据该值对其进行排序的原因。字典可以容纳更多具有相同 F 值的节点,这就是为什么我不能用它来检查两个节点是否相等。
  • 我想在普通列表上使用字典,因为它是大 O 表示法,因为我将一直添加、删除和查找集合中的节点。
  • 在常规的星型算法中,您不会经常进行排序。我的建议不是放弃字典,而是外包排序。此外,您可能需要 Dictionary ,其中您的值是到某个位置的距离。
猜你喜欢
  • 1970-01-01
  • 2012-11-25
  • 1970-01-01
  • 1970-01-01
  • 2018-03-11
  • 2021-11-23
  • 2021-12-03
  • 2018-11-13
  • 2017-08-21
相关资源
最近更新 更多