【问题标题】:Dictionary element accessing字典元素访问
【发布时间】:2015-04-21 16:06:36
【问题描述】:

问题

必须循环遍历所有 C# Dictionary 元素只能通过 foreach 完成,如果是这样,为什么?

或者,我可以这样问:Dictionary 元素是否可以通过Dictionary 对象中的位置访问(即,first 元素,last 元素, 倒数第三个元素等)?

这个问题的背景

我正在尝试了解更多关于 Dictionary 对象如何工作的信息,所以如果能帮助我解决这个问题,我将不胜感激。我正在了解这一点,所以我有几个想法都与这个问题有关。我会尝试以适合 SO 格式的方式呈现。

研究

在 C# 数组中,元素按位置引用。在Dictionary 中,值由键引用。

翻看documentation on MSDN,有说法

"出于枚举的目的,字典中的每个项目都被视为 表示值及其键的 KeyValuePair 结构。这 返回项目的顺序未定义。”

因此,似乎由于返回的订单项是未定义的,因此无法按位置访问元素。我也读过:

"使用key取值非常快,接近O(1), 因为 Dictionary 类是作为哈希表实现的。”

查看HashTable .NET 4.5 class 的文档,参考了使用foreach 语句循环并返回元素。但是没有提及使用for 语句,或者就此而言while 或任何其他循环语句。

另外,我注意到Dictionary 元素使用IEnumerable 接口,它似乎使用foreach 作为循环函数的唯一语句类型。

想法

那么,这是否意味着Dictionary 元素不能像数组或列表那样通过“位置”访问?

如果是这样,为什么有一个 .Count 属性返回键/值对的数量,但没有任何东西可以让我通过接近总数来引用这些?比如.Count是5,为什么我不能请求键值对.Count减1?

foreach 如何能够遍历每个元素,但我无法以相同的方式访问单个元素?

如果不使用foreach,是否无法确定Dictionary 对象中元素(键或值)的位置?在没有将元素映射到集合的情况下,我不能判断一个键是Dictionary 中的第一个键还是最后一个键?

This SO question 和出色的答案都涉及到这一点,但我特别想看看我是否必须将元素复制到数组或其他可枚举类型,以按位置访问特定元素。

这是一个例子。请注意,我不是在寻找专门解决此示例的方法 - 这仅用于说明我的问题。假设我想将 Dictionary<string, string> 对象中的所有它们的键添加到逗号分隔的列表中,最后没有逗号。使用数组我可以做到:

string[] arrayStr = new string[2] { "Test1", "Test2" };
string outStr = "";
for (int i = 0; i < arrayStr.Length; i++)
{
    outStr += arrayStr[i];
    if (i < arrayStr.Length - 1)
    {
        outStr += ", ";
    }
}

使用Dictionary&lt;string, string&gt;,如何使用上述方法将每个密钥复制到outStr?看来我必须使用foreach。但是存在哪些Dictionary 方法或属性可以让我在字典中识别元素的位置?

如果您仍在阅读本文,我还想指出,我并不是想说Dictionary 有什么错误...我只是想了解这是怎么回事.NET 框架中的工具的工作原理,以及如何最好地自己使用它。

【问题讨论】:

  • 如果顺序很重要,请使用字典以外的其他集合类型,例如列表、堆栈、队列、数组或whathaveyou。当需要 O(1) 查找键=> 值时使用字典。例如,我不明白您为什么会对 "Count-1"'th 元素感兴趣;如果您是字典,则不是您要使用的集合类型。
  • 顺序未定义,因此在特定索引处的值也未定义。我可以看到想要获取特定索引的唯一原因是随机访问元素,并且可能有更好的方法来做到这一点。
  • With Dictionary&lt;string, string&gt;, how would I copy each key to outStr using the above method? Presonally 我不会使用那种方法。 var list = dictionary.Select(k =&gt; k.Key).ToList(); 更简单。
  • 如果你有一个 strings 的集合并且想要索引访问和散列查找,你可以使用NameValueCollection。但是没有通用版本(我一直想知道为什么)。
  • @Ulric,已经有一个Keys 属性; dictionary.Keys.ToList() 更简单。

标签: c# .net


【解决方案1】:

假设您有四辆不同颜色的汽车。并且您希望能够通过颜色快速找到汽车的钥匙。因此,您制作了 4 个标有“红色”、“蓝色”、“黑色”和“白色”的信封,并将每辆车的钥匙放入正确的信封中。哪辆是“第一辆”汽车?哪个是“第三”?您不关心信封的顺序;您担心能否通过颜色快速获取密钥。

那么,这是否意味着字典元素不能像数组或列表那样通过“位置”访问?

不是直接,不。您可以使用SkipTake,但它们所做的只是迭代直到您到达“第n 个”项目。

如果是这样,为什么有一个 .Count 属性返回键/值对的数量,但没有任何东西可以让我通过接近总数来引用它们?比如.Count是5,为什么我不能请求键值对.Count减1?

即使没有订单,您仍然可以测量商品的数量。在我的示例中,您知道有 4 个信封,但没有“第三个”信封的概念。

foreach 如何能够遍历每个元素,而我却无法以相同的方式访问单个元素?

因为foreach使用IEnumerable,它每次只要求“下一个”元素——底层集合决定了元素返回的顺序。你可以一个一个地拿起信封,但顺序是无关紧要。

如果不使用 foreach,是否无法确定 Dictionary 对象中元素(键或值)的位置?

您可以推断使用foreach 并计算在达到所需元素之前您有多少元素,但是一旦添加或删除项目,该位置可能会改变。如果我买一辆绿色汽车并加上信封,它会在“订单”中的哪个位置?

我特别想看看是否必须将元素复制到数组或其他可枚举类型,才能按位置访问特定元素。

嗯,不,您可以使用SkipTake,但无法预测该位置的物品。您可以拿起两个信封,忽略它们,再拿起一个并称其为“第三个”信封,但那又如何?

【讨论】:

    【解决方案2】:

    这里有几个正确的答案,但我想你可能会喜欢一个简短的版本:)

    在底层,Dictionary 类有一个名为 buckets 的私有字段。它只是一个普通数组,将整数位置映射到您添加到 Dictionary 的对象。

    当您向Dictionary 添加键/值对时,它会为您的键计算哈希值。哈希值用作buckets 数组的索引。 Dictionary 使用尽可能多的哈希位,以确保buckets 数组的索引不会与现有条目冲突。 buckets 数组会因冲突而按需扩展。

    是的,可以通过反射(允许您提取私有数据字段)获取buckets 数组的第三、第四或第 N 个成员。但是数组可以随时调整大小,你甚至不能保证Dictionary 的实现细节不会改变。

    【讨论】:

      【解决方案3】:

      除了 D Stanley 的回答之外,我想补充一点,您应该查看 SortedDictionary&lt;TKey, TValue&gt;。它将键/值对存储在确实保持键有序的数据结构中。

      var d = new SortedDictionary<int, string>();
      d.Add(4, "banana");
      d.Add(2, "apple");
      d.Add(7, "pineapple");
      Console.WriteLine(d.ElementAt(1).Value); // banana
      

      【讨论】:

      • 这应该是对 D Stanley 的回答的评论;它没有回答问题。
      【解决方案4】:

      不需要使用foreach来循环遍历字典,但是'first'和'last'这两个词在字典中是没有意义的,因为顺序不能保证并且与订单项没有任何关系已添加到您的字典中。

      这样想。你有一个用来存放积木的袋子,每个积木上都有一个独特的标签。放入一个带有“Foo”、“Bar”和“Baz”标签的块。现在,如果你问我包的数量是多少,我可以说我里面有 3 个积木,如果你要我要标有“酒吧”的积木,我可以给你。但是,如果你问我第一个块,我不知道你的意思。这些积木只是我包里的杂物。如果您说“foreach”块,而我想给它拍照,我会一个接一个地把每个块递给您。同样,不能保证顺序。我只是把手伸进我的包里,把每一个积木都拿出来,直到我得到每一个。

      您还可以请求字典中所有键的集合,然后使用每个键来访问字典中的项目。然而,再次强调,密钥的顺序并不能保证,理论上,每次访问它时都会改变(实际上,.NET 的密钥顺序通常非常稳定)。

      这样存储字典的原因有很多,但关键是字典必须具有未指定的顺序才能同时进行 O(1) 插入和 O(1) 访问。具有指定顺序的数组具有 O(1) 访问权限(您可以一步获得第 n 个项目),但插入是 O(n)。

      【讨论】:

        【解决方案5】:

        .Net 框架中有大量可用的集合。您必须分析您的需求并决定使用哪个集合:

        • 您需要键/值对还是只需要项?
        • 对项目进行排序重要吗?
        • 您需要快速插入还是只在收集开始/结束时添加?
        • 您需要快速检索:O(1) 还是 O(log n)?
        • 您是否需要索引,即按整数位置访问项目?

        对于这些要求的大多数组合,都存在一个专门的集合。

        在您的情况下:键/值对和通过索引访问:SortedList

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-04-06
          • 2011-07-21
          • 2018-07-05
          • 2021-07-27
          • 1970-01-01
          相关资源
          最近更新 更多