为什么词典“没有订购”?

时间:2011-06-17 10:54:33

标签: c# .net dictionary base-class-library operator-precedence

我已经在这里回答了很多问题。但究竟是什么意思呢?

var test = new Dictionary<int, string>();
test.Add(0, "zero");
test.Add(1, "one");
test.Add(2, "two");
test.Add(3, "three");

Assert(test.ElementAt(2).Value == "two");

上面的代码似乎按预期工作。那么字典被认为是无序的?在什么情况下上面的代码会失败?

7 个答案:

答案 0 :(得分:72)

嗯,首先不清楚您是否希望这是插入订单键盘订单。例如,如果你写的话,你会期望结果是什么:

var test = new Dictionary<int, string>();
test.Add(3, "three");
test.Add(2, "two");
test.Add(1, "one");
test.Add(0, "zero");

Console.WriteLine(test.ElementAt(0).Value);

你会期待“三”还是“零”?

碰巧,我认为当前的实现保留了插入顺序,只要你永远不会删除任何东西 - 但你不能依赖于这个。这是一个实现细节,将来可能会发生变化。

删除也会影响这一点。例如,您期望该程序的结果是什么?

using System;
using System.Collections.Generic;

class Test
{ 
    static void Main() 
    {
        var test = new Dictionary<int, string>();
        test.Add(3, "three");
        test.Add(2, "two");
        test.Add(1, "one");
        test.Add(0, "zero");

        test.Remove(2);
        test.Add(5, "five");

        foreach (var pair in test)
        {
            Console.WriteLine(pair.Key);
        }
    }     
}

它实际上(在我的盒子上)3,5,1,0。5的新条目使用了之前使用的空出条目。但这不会得到保证。

Rehashing(当字典的底层存储需要扩展时)可能会影响事物......所有事情都会发生。

请勿将其视为有序集合。它不是为此而设计的。即使它现在正好工作,你依赖的是无证的行为,这违背了班级的目的。

答案 1 :(得分:24)

Dictionary<TKey, TValue>表示Hash Table,在散列表中没有订单概念。

documentation解释得非常好:

  

为了枚举,每个项目   在字典中被视为一个   KeyValuePair结构   代表一个值及其关键。该   退货的订单   未定义。

答案 2 :(得分:7)

这里有很多好主意,但是分散了,所以即使问题已经得到解答,我也会尝试创建一个更好的答案。

首先,词典没有保证顺序,因此您只能使用它来快速查找键并找到相应的值,或者您可以通过所有键值对进行枚举,而无需关心订单是什么。

如果你想要订单,你使用OrderedDictionary,但权衡是查找速度较慢,所以如果你不需要订单,不要求它。

字典(以及Java中的HashMap)使用散列。无论你的桌子大小如何,这都是O(1)时间。有序字典通常使用某种平衡树,即O(log2(n)),因此随着数据的增长,访问速度变慢。为了比较,对于100万个元素,大约为2 ^ 20,所以你必须做20个查找树的顺序,但是1个用于哈希映射。这快了很多。

哈希是确定性的。非确定性意味着当您第一次散列(5)并且下次散列(5)时,您会得到一个不同的位置。那将是完全没用的。

人们想要说的是,如果您将字词添加到字典中,则订单很复杂,并且在您添加(或可能删除)元素时可能会发生变化。例如,假设哈希表中有500k个元素,并且您有400k值。当你再添加一个时,你就达到了临界阈值,因为它需要大约20%的空闲空间才能有效,所以它分配了一个更大的表(比如100万个条目)并重新散列所有的值。现在他们都处在不同的位置。

如果你两次构建相同的词典(仔细阅读我的陈述,相同),你将获得相同的顺序。但正如乔恩所说,不要指望它。太多东西可能会使它变得不一样,甚至是最初分配的大小。

这提出了一个很好的观点。必须调整散列映射的确非常非常昂贵。这意味着你必须分配一个更大的表,并重新插入每个键值对。所以非常值得分配10倍所需的内存,而不是只有一个增长必须发生。知道你的hashmap的大小,并且如果可能的话,预先分配足够的,这是一个巨大的性能胜利。如果你有一个不能调整大小的糟糕实现,如果你选择的规模太小,那就太麻烦了。

现在,Jon在我的回答评论中与我争论的是,如果你在两个不同的运行中向一个字典添加对象,你将得到两个不同的排序。没错,但那不是字典的错。

当你说:

new Foo();

您正在内存中的新位置创建新对象。

如果使用值Foo作为字典中的键,没有其他信息,他们唯一能做的就是使用对象的地址作为键。

这意味着

var f1 = new Foo(1);
var f2 = new Foo(1);

f1和f2不是同一个对象,即使它们具有相同的值。

所以,如果你把它们放入词典:

var test = new Dictionary<Foo, string>();
test.Add(f1, "zero");

不要指望它与:

相同
var test = new Dictionary<Foo, string>();
test.Add(f2, "zero");

即使f1和f2都具有相同的值。这与词典的确定性行为无关。

Hashing是计算机科学中一个很棒的主题,我最喜欢用数据结构教学。

查看Cormen和Leiserson有关红黑树与散列的高端书籍 这个名叫鲍勃的人有一个关于哈希和最佳哈希的优秀网站:http://burtleburtle.net/bob

答案 3 :(得分:5)

订单是不确定的。

来自here

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

可能符合您的需求OrderedDictionary是必需的。

答案 4 :(得分:0)

我不知道C#或任何.NET,但是Dictionary的一般概念是它是键值对的集合。
您不像在迭代列表或数组时那样按顺序访问字典 您可以通过键来访问,然后查找字典中是否存在该键的值以及它是什么 在您的示例中,您发布了一个带有数字键的字典,这些数字键恰好是顺序的,没有间隙,也是按插入的升序排列的 但无论您为哪个顺序插入键'2'的值,查询键'2'时总是会得到相同的值。
我不知道C#是否允许使用除数字以外的键类型,但在这种情况下,它是相同的,键上没有明确的顺序。
与现实生活字典的类比可能会令人困惑,因为作为单词的键是按字母顺序排列的,因此我们可以更快地找到它们,但如果它们不是,那么字典无论如何都会起作用,因为“Aardvark”这个词的定义“具有相同的意义,即使它出现在”斑马“之后。想想一部小说,另一方面,改变页面的顺序是没有任何意义的,因为它们本质上是一个有序的集合。

答案 5 :(得分:0)

Dictionary<TKey,TValue>是使用数组支持的索引链接列表实现的。如果没有删除任何项目,后备存储将按顺序保留项目。但是,当删除某个项目时,在展开数组之前,将标记该空间以供重用。因此,如果是将十个项目添加到新字典中,删除第四个项目,添加新项目,并枚举字典,新项目可能显示为第四个而不是第十个,但不能保证不同版本的{{1将以同样的方式处理事情。

恕我直言,对于Microsoft来说,记录一个没有删除的项目的字典将按原始顺序枚举项目,但删除任何项目后,任何未来的更改都会有所帮助字典可以任意置换其中的项目。只要不删除任何项目,坚持这样的保证对于大多数合理的字典实现来说相对便宜;在删除项目后继续坚持保证将会更加昂贵。

或者,对于单个编写器与任意数量的读取器同时具有Dictionary可能是有线程安全的,并且保证按顺序保留项目(请注意,如果仅添加项目) - 无论何时删除或以其他方式修改 - 只需注意当前包含的项目数量,即可拍摄快照&#34;快照。使通用字典线程安全是昂贵的,但添加上述级别的线程安全将是便宜的。请注意,高效的多写入器多读取器使用不需要使用读写器锁,但可以通过让编写器锁定并让读者不费心来处理。

微软当然没有如上所述实现AddOnlyDictionary,但有趣的是,线程安全AddOnlyDictionary具有只添加语义,可能是因为 - 如上所述 - 将并发添加到仅添加集合比允许删除的集合要容易得多。

答案 6 :(得分:0)

词典&LT; string,Obj&gt;,而不是SortedDictionary&lt;字符串,Obj&gt;,默认按插入顺序排序。很奇怪,你需要专门声明一个SortedDictionary,以便有一个按键字符串顺序排序的字典:

public SortedDictionary<string, Row> forecastMTX = new SortedDictionary<string, Row>();