web-dev-qa-db-ja.com

辞書が「注文されていない」のはなぜですか?

私はここで多くの質問に答えてこれを読みました。しかし、それは正確にはどういう意味ですか?

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");

上記のコードは期待どおりに機能しているようです。では、辞書はどのように順序付けされていないと見なされますか?上記のコードはどのような状況で失敗する可能性がありますか?

45

まあ、一つには、これが挿入順序なのかキー順序なのかは明確ではありません。たとえば、次のように書いた場合、結果はどうなると思いますか。

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

「3」または「ゼロ」を期待しますか?

たまたま、私はthink何も削除しない限り、現在の実装は挿入順序を保持します-しかし、あなたはこれに依存してはいけません。これは実装の詳細であり、将来変更される可能性があります。

削除もこれに影響します。たとえば、このプログラムの結果はどうなると思いますか?

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の新しいエントリは、以前に2で使用されていた空のエントリを使用しています。ただし、これも保証されません。

再ハッシュ(辞書の基礎となるストレージを拡張する必要がある場合)は、物事に影響を与える可能性があります...あらゆる種類の物事が影響します。

順序付けられたコレクションとして扱わないでください。そのために設計されていません。現在機能している場合でも、クラスの目的に反する文書化されていない動作に依存しています。

73
Jon Skeet

A Dictionary<TKey, TValue>ハッシュテーブル を表し、ハッシュテーブルには順序の概念はありません。

ドキュメント はそれをかなりよく説明しています:

列挙の目的で、ディクショナリ内の各アイテムは、値とそのキーを表すKeyValuePair構造として扱われます。アイテムが返される順序は未定義です。

24
Darin Dimitrov

ここには良いアイデアがたくさんありますが、散らばっているので、問題が解決されたとしても、それをよりよくレイアウトする答えを作成しようと思います。

まず、辞書には保証された順序がないため、キーをすばやく検索して対応する値を見つけるためにのみ使用するか、順序を気にせずにすべてのキーと値のペアを列挙します。

注文が必要な場合はOrderedDictionaryを使用しますが、トレードオフとしてルックアップが遅くなるため、注文が必要ない場合は要求しないでください。

辞書(およびJavaのHashMap)はハッシュを使用します。つまり、テーブルのサイズに関係なく、O(1)時間です。順序付けられたディクショナリは通常、O(log2(n))であるある種のバランスの取れたツリーを使用します。 =データが大きくなると、アクセスが遅くなります。100万個の要素を比較するには、2 ^ 20のオーダーであるため、ツリーのルックアップは20のオーダーで行う必要がありますが、ハッシュの場合は1です。マップ。それはかなり速いです。

ハッシュは決定論的です。非決定論とは、最初にハッシュ(5)したとき、次にハッシュ(5)したときに、別の場所を取得することを意味します。それは完全に役に立たないでしょう。

人々が言うことは、辞書に物事を追加すると、順序が複雑になり、要素を追加(または削除する可能性がある)するたびに変更される可能性があるということです。たとえば、ハッシュテーブルに500kの要素があり、400kの値があるとします。もう1つ追加すると、効率を上げるために約20%の空きスペースが必要になるため、クリティカルしきい値に到達します。そのため、より大きなテーブル(たとえば、100万エントリ)が割り当てられ、すべての値が再ハッシュされます。今では、それらはすべて以前とは異なる場所にあります。

同じ辞書を2回作成すると(私のステートメントを注意深く読んでください、同じです)、同じ順序になります。しかし、ジョンが正しく言っているように、それを当てにしないでください。最初に割り当てられたサイズであっても、物が多すぎると同じではなくなる可能性があります。

これは優れた点をもたらします。ハッシュマップのサイズを変更する必要があるのは、本当に本当に費用がかかります。つまり、より大きなテーブルを割り当て、すべてのキーと値のペアを再挿入する必要があります。したがって、1回の拡張でも発生するのではなく、必要なメモリの10倍を割り当てる価値があります。ハッシュマップのサイズを把握し、可能な場合は十分に事前に割り当てておくと、パフォーマンスが大幅に向上します。また、サイズ変更されない不適切な実装がある場合、小さすぎるサイズを選択すると、問題が発生する可能性があります。

Jonが彼の回答のコメントで私と議論したのは、2つの異なる実行でオブジェクトを辞書に追加すると、2つの異なる順序が得られるということでした。本当ですが、それは辞書のせいではありません。

あなたが言う時:

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の両方が同じ値であっても。これは、辞書の決定論的な動作とは何の関係もありません。

ハッシュはコンピュータサイエンスの素晴らしいトピックであり、データ構造で教えるのが私のお気に入りです。

赤黒木とハッシュに関するハイエンドの本については、CormenとLeisersonをチェックしてください。Bobという名前のこの男は、ハッシュと最適なハッシュに関するすばらしいサイトを持っています: http://burtleburtle.net/bob

7
Dov

順序は非決定論的です。

から ここ

列挙の目的で、ディクショナリの各項目は、値とそのキーを表すKeyValuePair構造体として扱われます。アイテムが返される順序は未定義です。

多分あなたのニーズのために OrderedDictionary が必要です。

5
V4Vendetta

SortedDictionary <string、Obj>ではなくDictionary <string、Obj>は、デフォルトで挿入順序によるシーケンスになります。奇妙なことに、キー文字列の順序でソートされた辞書を作成するには、SortedDictionaryを明確に宣言する必要があります。

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

クラス Dictionary<TKey,TValue>は、配列に裏打ちされたインデックスリンクリストを使用して実装されます。アイテムが削除されない場合、バッキングストアはアイテムを順番に保持します。ただし、アイテムが削除されると、配列が展開される前にスペースが再利用できるようにマークされます。結果として、例えば10個のアイテムが新しい辞書に追加され、4番目のアイテムが削除され、新しいアイテムが追加され、辞書が列挙されます。新しいアイテムは10番目ではなく4番目に表示される可能性がありますが、異なるバージョンのDictionaryは同じように処理します。

私見ですが、アイテムが削除されていない辞書everは元の順序でアイテムを列挙しますが、アイテムが削除されると、辞書に将来変更が加えられることをMicrosoftが文書化することは役に立ちました。その中のアイテムを恣意的に並べ替えることができます。アイテムが削除されない限りそのような保証を維持することは、最も合理的な辞書の実装にとって比較的安価です。アイテムが削除された後も保証を維持し続けると、はるかに費用がかかります。

あるいは、任意の数のリーダーと同時に単一のライターに対してスレッドセーフであり、アイテムを順番に保持することを保証するAddOnlyDictionaryがあると便利だったかもしれません(アイテムが追加されるだけの場合は-削除したり変更したりすることはありません。現在含まれているアイテムの数を記録するだけで「スナップショット」を撮ることができます)。汎用辞書をスレッドセーフにするのはコストがかかりますが、上記のレベルのスレッドセーフを追加するのは安価です。マルチライターマルチリーダーを効率的に使用するには、リーダーライターロックを使用する必要はありませんが、ライターをロックし、リーダーを気にしないことで簡単に処理できることに注意してください。

もちろん、Microsoftは上記のようにAddOnlyDictionaryを実装しませんでしたが、スレッドセーフなConditionalWeakTableには追加専用のセマンティクスがあることに注意してください。おそらく、前述のように、削除を許可するコレクションよりも、追加のみのコレクションに同時実行性を追加する方がはるかに簡単です。

0
supercat

C#や.NETのいずれかを知りませんが、ディクショナリの一般的な概念は、キーと値のペアのコレクションであるということです。
たとえば、リストや配列を反復処理する場合のように、辞書に順番にアクセスすることはありません。
キーを持ってアクセスし、辞書にそのキーの値があるかどうか、そしてそれは何であるかを見つけます。
あなたの例では、ギャップがなく、挿入の昇順で、たまたま連続している数値キーを含む辞書を投稿しました。
ただし、キー「2」の値を挿入する順序に関係なく、キー「2」を照会すると常に同じ値が取得されます。
C#で数字以外のキーの種類を許可するかどうかはわかりませんが、その場合も同じで、キーに明示的な順序はありません。
実際の辞書との類似性は混乱を招く可能性があります。単語であるキーはアルファベット順に並べられているため、すばやく見つけることができますが、そうでない場合でも、定義が原因で辞書は機能します。 「ツチブタ」という言葉は、「ゼブラ」の後に来たとしても、同じ意味を持ちます。一方、小説について考えてみてください。ページの順序を変更しても、本質的に順序付けられたコレクションであるため、意味がありません。

0
Petruza