.NET辞書のキーが重複していますか?


256

.NET基本クラスライブラリに、重複するキーの使用を許可する辞書クラスはありますか?私が見つけた唯一の解決策は、たとえば、次のようなクラスを作成することです。

Dictionary<string, List<object>>

しかし、これは実際に使用するのは非常にイライラします。Javaでは、MultiMapがこれを達成すると思いますが、.NETでアナログを見つけることができません。


3
この重複キーはどうですか、重複値(リスト)ですよね?
Shamim Hafiz、2010

1
@ShamimHafiz、いいえ、値は重複している必要はありません。重複{ a, 1 }したものをキーとなる{ a, 2 }ハッシュテーブルに格納する必要がある場合a、1つの代替方法はを使用すること{ a, [1, 2] }です。
nawfal 14年

1
実際、ここで本当に必要なのは、各キーが1つ以上の値にマッピングできるコレクションだと思います。「重複キー」という表現は、これを実際には伝えていないと思います。
DavidRR 2014

今後の参考のために、同じキーを何度も追加するのではなく、値を追加するだけで1つのキーを保持することを検討してください。
Ya Wang

キーと値の両方が文字列の場合、NameValueCollectionがあります(これにより、各文字列キーに複数の文字列値を関連付けることができます)。
AnorZaken

回答:


229

.NET 3.5を使用している場合は、Lookupクラスを使用します。

編集:通常、Lookupを使用して作成しEnumerable.ToLookupます。これは後で変更する必要がないことを前提としていますが、通常はこれで十分です。

それがうまくいかない場合、フレームワークに役立つものはないと思います-そして、辞書を使用することは、それと同じくらい良いです:(


Lookupのヘッドアップをありがとう。これは、標準のorderby基準ではないlinqクエリからの結果(グループ)を分割する優れた方法を提供します。
ロバートポールソン

3
@Josh:Enumerable.ToLookupを使用して作成します。
ジョンスキート、

29
注意Lookupシリアライズ可能ではありません
SliverNinja-MSFT

1
そのルックアップにアイテムをどのように追加すればよいですか?
モルドバヌ

171

Listクラスは、実際には、コレクションを反復処理したい重複を含むキー/値コレクションに対して非常にうまく機能します。例:

List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>();

// add some values to the collection here

for (int i = 0;  i < list.Count;  i++)
{
    Print(list[i].Key, list[i].Value);
}

31
このソリューションは、機能的に動作しますが、リストの実装では、すべてのキーのキーや値とカント最適化検索の知識がありません
スペンサー・ローズ

41

List <KeyValuePair <string、string>>でこれを行う1つの方法を次に示します。

public class ListWithDuplicates : List<KeyValuePair<string, string>>
{
    public void Add(string key, string value)
    {
        var element = new KeyValuePair<string, string>(key, value);
        this.Add(element);
    }
}

var list = new ListWithDuplicates();
list.Add("k1", "v1");
list.Add("k1", "v2");
list.Add("k1", "v3");

foreach(var item in list)
{
    string x = string.format("{0}={1}, ", item.Key, item.Value);
}

出力k1 = v1、k1 = v2、k1 = v3


私のシナリオでうまく機能し、使いやすいです。
user6121177 2017年

21

キーと値の両方として文字列を使用している場合は、System.Collections.Specialized.NameValueCollectionを使用できます。これは、GetValues(string key)メソッドを介して文字列値の配列を返します。


6
NameValueCollectionは複数のキーを許可しません。
Jenny O'Reilly、

@Jenny O'Reilly:もちろん、重複したキーを追加できます。
isHuman 2016年

リンクされているMSDNページの備考に明記されているように、厳密に言えば@ JennyO'Reillyは正しいです。このクラスは単一のキーの下に複数の文字列値を格納します
ロナルド

それは可能ですが複数の値を返します、私はインデックスとキーを使用してみました。
user6121177 2017年

18

私はちょうど出くわしたPowerCollectionsの、とりわけ、MultiDictionaryと呼ばれるクラスを含んでいるライブラリ。これにより、このタイプの機能が適切にラップされます。


14

Lookupの使用に関する非常に重要な注意:

を実装するオブジェクトをLookup(TKey, TElement)呼び出すことにより、のインスタンスを作成できますToLookupIEnumerable(T)

の新しいインスタンスを作成するパブリックコンストラクタはありませんLookup(TKey, TElement)。さらに、Lookup(TKey, TElement)オブジェクトは不変です。つまり、Lookup(TKey, TElement)オブジェクトが作成された後、オブジェクトから要素またはキーを追加または削除することはできません。

(MSDNから)

これは、ほとんどの用途でショーストッパーになると思います。


6
これがショーストッパーになるような用途はほとんど考えられません。しかし、それでは、不変オブジェクトは素晴らしいと思います。
Joel Mueller、

1
@JoelMueller私はこれがショーストッパーである多くのケースを考えることができます。アイテムを追加するために辞書を再作成する必要があることは、特に効率的な解決策ではありません...
reirab

12

私はそのようなList<KeyValuePair<object, object>>仕事をすると思います。


そのリストをキーとしてどのように検索しますか?
ウェインブロス

繰り返し処理する必要があります。ただし、.NET 3.5のLookUp-Classについては認識していませんでした。これは、そのコンテンツを検索するのに便利です。
MADMap 2008

2
@wizlib:唯一の方法はリストをループすることです。これはハッシュほど効率的ではありません。-1
ペトルk。

@petrk。それは本当にあなたのデータが何であるかに依存します。この実装を使用したのは、一意のキーがほとんどなく、ハッシュ衝突のオーバーヘッドが発生したくないためです。+1
エヴァンM

9

> = .NET 4を使用している場合は、Tupleクラスを使用できます。

// declaration
var list = new List<Tuple<string, List<object>>>();

// to add an item to the list
var item = Tuple<string, List<object>>("key", new List<object>);
list.Add(item);

// to iterate
foreach(var i in list)
{
    Console.WriteLine(i.Item1.ToString());
}

これはList<KeyValuePair<key, value>>上記のような解決策のように見えます。私が間違っている?
Nathan Goings

6

「重複キー」エントリを許可する辞書の「独自のバージョン」をロールするのは簡単です。大まかな単純な実装は次のとおりです。の基本的にほとんど(すべてではないにしても)のサポートを追加することを検討してくださいIDictionary<T>

public class MultiMap<TKey,TValue>
{
    private readonly Dictionary<TKey,IList<TValue>> storage;

    public MultiMap()
    {
        storage = new Dictionary<TKey,IList<TValue>>();
    }

    public void Add(TKey key, TValue value)
    {
        if (!storage.ContainsKey(key)) storage.Add(key, new List<TValue>());
        storage[key].Add(value);
    }

    public IEnumerable<TKey> Keys
    {
        get { return storage.Keys; }
    }

    public bool ContainsKey(TKey key)
    {
        return storage.ContainsKey(key);
    }

    public IList<TValue> this[TKey key]
    {
        get
        {
            if (!storage.ContainsKey(key))
                throw new KeyNotFoundException(
                    string.Format(
                        "The given key {0} was not found in the collection.", key));
            return storage[key];
        }
    }
}

それを使用する方法の簡単な例:

const string key = "supported_encodings";
var map = new MultiMap<string,Encoding>();
map.Add(key, Encoding.ASCII);
map.Add(key, Encoding.UTF8);
map.Add(key, Encoding.Unicode);

foreach (var existingKey in map.Keys)
{
    var values = map[existingKey];
    Console.WriteLine(string.Join(",", values));
}


3

NameValueCollectionは1つのキー(文字列でもあります)の下で複数の文字列値をサポートしますが、私が知っている唯一の例です。

そのような機能が必要な状況に遭遇した場合、私はあなたの例のような構成を作成する傾向があります。


3

このList<KeyValuePair<string, object>>オプションを使用する場合、LINQを使用して検索を実行できます。

List<KeyValuePair<string, object>> myList = new List<KeyValuePair<string, object>>();
//fill it here
var q = from a in myList Where a.Key.Equals("somevalue") Select a.Value
if(q.Count() > 0){ //you've got your value }

2
はい、しかしそれはそれを速くはしません(まだハッシュなし)
Haukman '16

3

新しいC#(7.0からだと思います)なので、次のようなこともできます。

var duplicatedDictionaryExample = new List<(string Key, string Value)> { ("", "") ... }

そしてあなたはそれを標準のリストとして使用していますが、あなたが好きな名前の2つの値があります

foreach(var entry in duplicatedDictionaryExample)
{ 
    // do something with the values
    entry.Key;
    entry.Value;
}

2

実際の複製ではなく合同の意味ですか?そうでなければ、ハッシュテーブルは機能しません。

Congruentは、2つの別々のキーが同等の値にハッシュできるが、キーが等しくないことを意味します。

たとえば、ハッシュテーブルのハッシュ関数がhashval = key mod 3だったとします。1と4はどちらも1にマッピングされますが、値は異なります。ここで、リストのアイデアが役立ちます。

ルックアップ1が必要な場合、その値は1にハッシュされ、Key = 1が見つかるまでリストが走査されます。

重複するキーの挿入を許可した場合、どのキーがどの値にマップされているかを区別できなくなります。


2
ハッシュテーブルは既に同じ値にハッシュされるキーを処理します(これは衝突と呼ばれます)。複数の値を同じ正確なキーにマップしたい状況について言及しています。

2

私の使い方はただの

Dictionary<string, List<string>>

このようにして、文字列のリストを保持する単一のキーがあります。

例:

List<string> value = new List<string>();
if (dictionary.Contains(key)) {
     value = dictionary[key];
}
value.Add(newValue);

それは素晴らしくてきれいです。
ジェリーニクソン

これは、私のユースケースを処理するための優れたソリューションです。
ダブ・スタイレ

1

私は同じ答えを求めてこの投稿を偶然見つけましたが、何も見つかりませんでした。そのため、辞書のリストを使用して必要最小限のソリューションを作り、[]演算子をオーバーライドして、他のすべてのリストに新しい辞書がある場合にリストに新しい辞書を追加しました。与えられたkey(set)と値のリストを返します(get)。
醜く非効率的で、キーによってのみ取得/設定し、常にリストを返しますが、機能します。

 class DKD {
        List<Dictionary<string, string>> dictionaries;
        public DKD(){
            dictionaries = new List<Dictionary<string, string>>();}
        public object this[string key]{
             get{
                string temp;
                List<string> valueList = new List<string>();
                for (int i = 0; i < dictionaries.Count; i++){
                    dictionaries[i].TryGetValue(key, out temp);
                    if (temp == key){
                        valueList.Add(temp);}}
                return valueList;}
            set{
                for (int i = 0; i < dictionaries.Count; i++){
                    if (dictionaries[i].ContainsKey(key)){
                        continue;}
                    else{
                        dictionaries[i].Add(key,(string) value);
                        return;}}
                dictionaries.Add(new Dictionary<string, string>());
                dictionaries.Last()[key] =(string)value;
            }
        }
    }

1

@Hector Correaの回答をジェネリック型の拡張に変更し、カスタムのTryGetValueを追加しました。

  public static class ListWithDuplicateExtensions
  {
    public static void Add<TKey, TValue>(this List<KeyValuePair<TKey, TValue>> collection, TKey key, TValue value)
    {
      var element = new KeyValuePair<TKey, TValue>(key, value);
      collection.Add(element);
    }

    public static int TryGetValue<TKey, TValue>(this List<KeyValuePair<TKey, TValue>> collection, TKey key, out IEnumerable<TValue> values)
    {
      values = collection.Where(pair => pair.Key.Equals(key)).Select(pair => pair.Value);
      return values.Count();
    }
  }

0

これは、並行辞書の2つの方法です。

public class HashMapDictionary<T1, T2> : System.Collections.IEnumerable
{
    private System.Collections.Concurrent.ConcurrentDictionary<T1, List<T2>> _keyValue = new System.Collections.Concurrent.ConcurrentDictionary<T1, List<T2>>();
    private System.Collections.Concurrent.ConcurrentDictionary<T2, List<T1>> _valueKey = new System.Collections.Concurrent.ConcurrentDictionary<T2, List<T1>>();

    public ICollection<T1> Keys
    {
        get
        {
            return _keyValue.Keys;
        }
    }

    public ICollection<T2> Values
    {
        get
        {
            return _valueKey.Keys;
        }
    }

    public int Count
    {
        get
        {
            return _keyValue.Count;
        }
    }

    public bool IsReadOnly
    {
        get
        {
            return false;
        }
    }

    public List<T2> this[T1 index]
    {
        get { return _keyValue[index]; }
        set { _keyValue[index] = value; }
    }

    public List<T1> this[T2 index]
    {
        get { return _valueKey[index]; }
        set { _valueKey[index] = value; }
    }

    public void Add(T1 key, T2 value)
    {
        lock (this)
        {
            if (!_keyValue.TryGetValue(key, out List<T2> result))
                _keyValue.TryAdd(key, new List<T2>() { value });
            else if (!result.Contains(value))
                result.Add(value);

            if (!_valueKey.TryGetValue(value, out List<T1> result2))
                _valueKey.TryAdd(value, new List<T1>() { key });
            else if (!result2.Contains(key))
                result2.Add(key);
        }
    }

    public bool TryGetValues(T1 key, out List<T2> value)
    {
        return _keyValue.TryGetValue(key, out value);
    }

    public bool TryGetKeys(T2 value, out List<T1> key)
    {
        return _valueKey.TryGetValue(value, out key);
    }

    public bool ContainsKey(T1 key)
    {
        return _keyValue.ContainsKey(key);
    }

    public bool ContainsValue(T2 value)
    {
        return _valueKey.ContainsKey(value);
    }

    public void Remove(T1 key)
    {
        lock (this)
        {
            if (_keyValue.TryRemove(key, out List<T2> values))
            {
                foreach (var item in values)
                {
                    var remove2 = _valueKey.TryRemove(item, out List<T1> keys);
                }
            }
        }
    }

    public void Remove(T2 value)
    {
        lock (this)
        {
            if (_valueKey.TryRemove(value, out List<T1> keys))
            {
                foreach (var item in keys)
                {
                    var remove2 = _keyValue.TryRemove(item, out List<T2> values);
                }
            }
        }
    }

    public void Clear()
    {
        _keyValue.Clear();
        _valueKey.Clear();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return _keyValue.GetEnumerator();
    }
}

例:

public class TestA
{
    public int MyProperty { get; set; }
}

public class TestB
{
    public int MyProperty { get; set; }
}

            HashMapDictionary<TestA, TestB> hashMapDictionary = new HashMapDictionary<TestA, TestB>();

            var a = new TestA() { MyProperty = 9999 };
            var b = new TestB() { MyProperty = 60 };
            var b2 = new TestB() { MyProperty = 5 };
            hashMapDictionary.Add(a, b);
            hashMapDictionary.Add(a, b2);
            hashMapDictionary.TryGetValues(a, out List<TestB> result);
            foreach (var item in result)
            {
                //do something
            }

0

私はこの単純なクラスを使用します:

public class ListMap<T,V> : List<KeyValuePair<T, V>>
{
    public void Add(T key, V value) {
        Add(new KeyValuePair<T, V>(key, value));
    }

    public List<V> Get(T key) {
        return FindAll(p => p.Key.Equals(key)).ConvertAll(p=> p.Value);
    }
}

使用法:

var fruits = new ListMap<int, string>();
fruits.Add(1, "apple");
fruits.Add(1, "orange");
var c = fruits.Get(1).Count; //c = 2;

0

次のような独自の辞書ラッパーを作成して、ボーナスとしてnull値をキーとしてサポートできます。

/// <summary>
/// Dictionary which supports duplicates and null entries
/// </summary>
/// <typeparam name="TKey">Type of key</typeparam>
/// <typeparam name="TValue">Type of items</typeparam>
public class OpenDictionary<TKey, TValue>
{
    private readonly Lazy<List<TValue>> _nullStorage = new Lazy<List<TValue>>(
        () => new List<TValue>());

    private readonly Dictionary<TKey, List<TValue>> _innerDictionary =
        new Dictionary<TKey, List<TValue>>();

    /// <summary>
    /// Get all entries
    /// </summary>
    public IEnumerable<TValue> Values =>
        _innerDictionary.Values
            .SelectMany(x => x)
            .Concat(_nullStorage.Value);

    /// <summary>
    /// Add an item
    /// </summary>
    public OpenDictionary<TKey, TValue> Add(TKey key, TValue item)
    {
        if (ReferenceEquals(key, null))
            _nullStorage.Value.Add(item);
        else
        {
            if (!_innerDictionary.ContainsKey(key))
                _innerDictionary.Add(key, new List<TValue>());

            _innerDictionary[key].Add(item);
        }

        return this;
    }

    /// <summary>
    /// Remove an entry by key
    /// </summary>
    public OpenDictionary<TKey, TValue> RemoveEntryByKey(TKey key, TValue entry)
    {
        if (ReferenceEquals(key, null))
        {
            int targetIdx = _nullStorage.Value.FindIndex(x => x.Equals(entry));
            if (targetIdx < 0)
                return this;

            _nullStorage.Value.RemoveAt(targetIdx);
        }
        else
        {
            if (!_innerDictionary.ContainsKey(key))
                return this;

            List<TValue> targetChain = _innerDictionary[key];
            if (targetChain.Count == 0)
                return this;

            int targetIdx = targetChain.FindIndex(x => x.Equals(entry));
            if (targetIdx < 0)
                return this;

            targetChain.RemoveAt(targetIdx);
        }

        return this;
    }

    /// <summary>
    /// Remove all entries by key
    /// </summary>
    public OpenDictionary<TKey, TValue> RemoveAllEntriesByKey(TKey key)
    {
        if (ReferenceEquals(key, null))
        {
            if (_nullStorage.IsValueCreated)
                _nullStorage.Value.Clear();
        }       
        else
        {
            if (_innerDictionary.ContainsKey(key))
                _innerDictionary[key].Clear();
        }

        return this;
    }

    /// <summary>
    /// Try get entries by key
    /// </summary>
    public bool TryGetEntries(TKey key, out IReadOnlyList<TValue> entries)
    {
        entries = null;

        if (ReferenceEquals(key, null))
        {
            if (_nullStorage.IsValueCreated)
            {
                entries = _nullStorage.Value;
                return true;
            }
            else return false;
        }
        else
        {
            if (_innerDictionary.ContainsKey(key))
            {
                entries = _innerDictionary[key];
                return true;
            }
            else return false;
        }
    }
}

使用例:

var dictionary = new OpenDictionary<string, int>();
dictionary.Add("1", 1); 
// The next line won't throw an exception; 
dictionary.Add("1", 2);

dictionary.TryGetEntries("1", out List<int> result); 
// result is { 1, 2 }

dictionary.Add(null, 42);
dictionary.Add(null, 24);
dictionary.TryGetEntries(null, out List<int> result); 
// result is { 42, 24 }

あなたのコードが何をするか、それがどのように質問に答えるか、そしていくつかの使用例についていくつかの説明を投げることができますか?
Enigmativity

@Enigmativity、それは正確に尋ねられたことを行います、質問は重複をサポートするいくつかの辞書を提案することでした、それで私はこの機能をサポートする.net辞書の周りにラッパーを作成することを提案し、例としてそのようなラッパーをボーナスとして作成しました:キー(それは確かに、悪い習慣だ場合でも)使用法は非常に簡単であるとしてハンドルヌル var dictionary = new OpenDictionary<string, int>(); dictionary.Add("1", 1); // The next line won't throw an exception; dictionary.Add("1", 2); dictionary.TryGetEntries("1", out List<int> result); // result is { 1, 2 }
アレクサンダーTolstikov

回答に詳細を追加できますか?
Enigmativity

@Enigmativity、もしあなたが元の答えを意味するなら、確かに
Alexander Tolstikov

-1

Uは、uが辞書を使用するすべての場所で複合文字列キーを作成する方法を定義できます。uは、この方法を使用してキーを作成する必要があります。次に例を示します。

private string keyBuilder(int key1, int key2)
{
    return string.Format("{0}/{1}", key1, key2);
}

使用するため:

myDict.ContainsKey(keyBuilder(key1, key2))

-3

重複するキーは、辞書の契約全体を破ります。辞書では、各キーは一意であり、単一の値にマッピングされます。オブジェクトを任意の数の追加オブジェクトにリンクする場合、最善の策は、DataSetに似たものになる可能性があります(一般的にはテーブル)。1つの列にキーを入れ、もう1つの列に値を入れます。これはディクショナリよりもかなり低速ですが、これはキーオブジェクトをハッシュする機能を失うことに対するトレードオフです。


5
パフォーマンス向上のためにディクショナリを使用することの要点ではありませんか?DataSetを使用することは、List <KeyValuePair <T、U >>に劣らないようです。
Doug

-4

これも可能です:

Dictionary<string, string[]> previousAnswers = null;

このようにして、一意のキーを持つことができます。これがうまくいくことを願っています。


OPは、重複キーを許可する辞書を要求しました。
2016年

-10

次のように、大文字と小文字を区別せずに同じキーを追加できます。

キー1
キー1
KEY1
KEY1
KEY1
KEY1

私はダミーの答えですが、私のために働きました。


4
いいえ、うまくいきませんでした。辞書を使用すると、キーによってO(1)としても分類される非常に高速な検索が可能になります。大文字と小文字が異なる複数のキーを追加すると、キーをどのように取得するのですか?大文字と小文字のすべての組み合わせを試しますか?どのように実行しても、パフォーマンスは通常の単一の辞書検索のパフォーマンスとは異なります。これは、キー内の大文字変換可能な文字の数に応じて、キーごとの値の制限など、他のより明らかな欠点に加えてです。
Eugene Beresovsky、2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.