【问题标题】:Whats the best collection to use for uniquely identifying nodes?用于唯一标识节点的最佳集合是什么?
【发布时间】:2011-12-19 17:11:22
【问题描述】:

目前我使用Dictionary<int,node> 来存储大约 10,000 个节点。密钥用作稍后查找的 ID 号,“节点”是包含一些数据的类。程序中的其他类使用 ID 号作为指向节点的指针。 (这听起来可能效率低下。但是,解释我为此使用字典的原因超出了我的问题范围。)

但是,20% 的节点是重复的。 我想要做的是当我添加一个节点时检查它是否已经准备就绪。如果确实如此,则使用该 ID 号。如果不创建一个新的。

这是我目前对该问题的解决方案:

public class nodeDictionary 
{

    Dictionary<int, node> dict = new Dictionary<int, node>( );
    public int addNewNode( latLng ll )
    {
        node n = new node( ll );
        if ( dict.ContainsValue( n ) )
        {
            foreach ( KeyValuePair<int, node> kv in dict )
            {
                if ( kv.Value == n )
                {
                    return kv.Key;
                }
            }
        }
        else
        {
            if ( dict.Count != 0 )
            {
                dict.Add( dict.Last( ).Key + 1, n );
                return dict.Last( ).Key + 1;
            }
            else
            {
                dict.Add( 0, n );
                return 0;
            }
        }
        throw new Exception( );
    }//end add new node
}

问题在于,当尝试将新节点添加到 100,000 个节点的列表中时,添加节点需要 78 毫秒。这是不可接受的,因为我可以在任何给定时间添加额外的 1,000 个节点。

那么,有没有更好的方法来做到这一点?我不是在找人为我编写代码,我只是在寻找指导。

【问题讨论】:

    标签: c# .net data-structures collections


    【解决方案1】:

    听起来你很想

    • 确保 LatLng 覆盖 Equals/GetHashCode(最好实现 IEquatable&lt;LatLng&gt; 接口)
    • 将所有项目直接放入HashSet&lt;LatLng&gt;

    要实现 GetHashCode,请参见此处:Why is it important to override GetHashCode when Equals method is overridden?

    如果您需要以某种方式生成“人工”唯一 ID,我建议您再次使用字典方法,但“反过来”:

    // uses the same hash function for speedy lookup/insertion
    IDictionary<LatLng, int> idMap = new Dictionary<LatLng, int>(); 
    
    foreach (LatLng latLng in LatLngCoords)
    {
        if (!idMap.ContainsKey(latLng))
            idMap.Add(latLng, idMap.Count+1); // to start with 1
    }
    

    您可以让idMap 替换HashSet&lt;&gt;;实现(和性能特征)基本相同,但作为关联容器。

    这是一个从 LatLng 到 Id 的查找函数:

    int IdLookup(LatLng latLng)
    {
         int id;
         if (idMap.TryGetValue(latLng, id))
             return id;
         throw new InvalidArgumentException("Coordinate not in idMap");
    }
    

    您可以及时添加:

    int IdFor(LatLng latLng)
    {
         int id;
         if (idMap.TryGetValue(latLng, id))
             return id;
    
         id = idMap.Count+1;
         idMap.Add(latLng, id);
         return id;
    }
    

    【讨论】:

    • 所以我这样做了,填充过程现在快如闪电了(100,000 个节点大约需要 80 毫秒)。但我仍然需要一个身份证号码...
    • a possible 策略更新了我的答案以生成 int id。请注意,生成的 id 将取决于将坐标添加到字典中的顺序。您不能依赖此方案在程序运行中保持“稳定”(除非插入的顺序和值始终是完全确定的)。
    • 虽然这不是一个完美的策略,但我现在明白了如何解决这个问题。谢谢你的帮助!完成后我会尝试发布我的最终代码。
    【解决方案2】:

    这段代码的具体用途是什么?

    if ( dict.ContainsValue( n ) )
    {
        foreach ( KeyValuePair kv in dict )
        {
            if ( kv.Value == n )
            {
                return kv.Key;
            }
        }
    }
    

    ContainsValue 搜索(而不是键)并且效率非常低(O(n))。 foreach 同上。更不用说在只需要一个的时候两者都做(你可以通过重新排列你的ifs 来完全删除ContainsValue)!

    您可能应该维护与原始字典“相反”的附加字典(即旧字典中的值是新字典中的键,反之亦然),以“覆盖”您的搜索模式(类似于数据库如何维护多个索引 par table 以涵盖可以查询 table 的多种方式。

    【讨论】:

    • 代码中使用的是泛型版本(非泛型版本称为HashTable)。另外,我不清楚你是支持还是反对HashSet&lt;T&gt;)。
    • @svick 我看到您编辑了代码以使用泛型。为什么?我评论了原始代码。顺便说一句,我(可能) HashTable&lt;T&gt;(已编辑)。
    • 对不起,我没有注意到。代码在我编辑之前是通用的,但看起来不是那样,因为使用的格式没有显示&lt;&gt; 中的部分。否则代码没有意义,.Net 中没有非泛型 Dictionary
    • 显然我混淆了存在的非通用IDictionary 和不存在的非通用Dictionary。过失;)
    【解决方案3】:

    您的解决方案不仅慢,而且是错误的。 Dictionary 中的项目顺序未定义,因此 dict.Last() 不能保证返回最后添加的项目。 (虽然它可能经常看起来那样。)

    使用 id 来识别应用程序中的对象似乎也是错误的。您应该考虑直接使用对对象的引用。

    但是,如果您想使用您当前的设计并假设您根据它们的latLng 比较节点,您可以创建两个字典:一个是您已有的字典,另一个是Dictionary&lt;latLng, int&gt;,可用于有效地判断某个节点是否已经存在。如果有,它会给你它的 id。

    【讨论】:

    • 在大图上直接引用对象是不可能的。但这超出了问题的范围。
    【解决方案4】:

    我会为反向添加第二个字典。即Dictionary&lt;Node,int&gt;

    那你要么

    • 满足于引用相等并且什么都不做。
    • 创建一个IEqualityComparer&lt;Node&gt; 并将其提供给字典
    • Node 上覆盖EqualsGetHashCode

    在这两种情况下,良好的哈希码实现对于获得良好的性能都是必不可少的。

    【讨论】:

      【解决方案5】:

      您可能需要考虑将其重组为仅使用列表(其中“键”只是列表中的索引)而不是字典。几个优点:

      1. 现在通过整数键查找元素是 O(1)(而且是一个非常快的 O(1),因为它只是内部的数组解引用)。

      2. 当您插入一个新元素时,您将执行 O(n) 搜索以查看它是否已存在于列表中。如果没有,您也已经遍历了该列表,并且可以记录您是否遇到了具有空记录的条目。如果有,该索引就是新键。如果不是,则新键是当前列表 Count。您枚举集合一次而不是多次,并且枚举本身比枚举字典快得多。

      【讨论】:

      • 如果您使用两个哈希表(两个字典或字典和 HashSet),您可以在 O(1) 中对这两个操作执行相同的操作。
      • 这个问题是;如果我删除一个节点,所有的 ID 都必须转移。这会使事情变得过于复杂。虽然我可以添加一个虚拟节点来代替被删除的节点.....
      • @Chad,其目的是将 null 用于空节点。如果您有值类型,则可以使用可为空作为存储类型或使用哨兵(虚拟)值来指示为空。
      • @Chad,经过进一步思考,这种方法的一个缺点是,由于重复使用以前删除的 id,它会遇到过时的引用问题。使用旧引用可能会错误地给出“新”对象值,而不是导致错误。
      【解决方案6】:

      你可以试试HashSet&lt;T&gt;

      【讨论】:

        猜你喜欢
        • 2011-01-05
        • 2023-03-17
        • 2011-04-27
        • 1970-01-01
        • 2021-11-22
        • 2011-03-27
        • 1970-01-01
        • 2015-12-02
        • 2020-01-17
        相关资源
        最近更新 更多