Kunci duplikat dalam kamus .NET?


256

Apakah ada kelas kamus di perpustakaan .NET base class yang memungkinkan duplikat kunci untuk digunakan? Satu-satunya solusi yang saya temukan adalah membuat, misalnya, kelas seperti:

Dictionary<string, List<object>>

Tapi ini cukup menjengkelkan untuk digunakan. Di Jawa, saya percaya MultiMap menyelesaikan ini, tetapi tidak dapat menemukan analog di .NET.


3
Bagaimana kunci duplikat ini, itu nilai duplikat (Daftar), kan?
Shamim Hafiz

1
@ShamimHafiz, tidak, nilainya tidak perlu duplikat. Jika Anda harus menyimpan duplikat { a, 1 }dan { a, 2 }dalam tabel hash di mana amenjadi kuncinya, salah satu alternatif adalah memiliki { a, [1, 2] }.
nawfal

1
Sebenarnya, saya percaya apa yang sebenarnya diinginkan di sini adalah kumpulan di mana setiap tombol dapat memetakan ke satu atau lebih nilai. Saya pikir ungkapan "duplikat kunci" tidak benar-benar menyampaikan hal ini.
DavidRR

Untuk referensi di masa mendatang, Anda harus mempertimbangkan menyimpan 1 kunci hanya dengan menambahkan nilai ke dalamnya alih-alih menambahkan kunci yang sama berulang kali.
Ya Wang

Jika kedua kunci dan nilai adalah string maka ada NameValueCollection (yang dapat mengaitkan beberapa nilai string dengan setiap kunci string).
AnorZaken

Jawaban:


229

Jika Anda menggunakan .NET 3.5, gunakan Lookupkelas.

EDIT: Anda biasanya membuat Lookupmenggunakan Enumerable.ToLookup. Ini mengasumsikan bahwa Anda tidak perlu mengubahnya setelah itu - tetapi saya biasanya menemukan itu cukup baik.

Jika itu tidak berhasil untuk Anda, saya tidak berpikir ada sesuatu dalam kerangka kerja yang akan membantu - dan menggunakan kamus sama baiknya dengan mendapatkan :(


Terima kasih untuk informasi tentang Pencarian. Ia menawarkan cara yang bagus untuk mempartisi (grup) hasil dari kueri linq yang bukan kriteria orbital standar.
Robert Paulson

3
@Josh: Anda menggunakan Enumerable.ToLookup untuk membuatnya.
Jon Skeet

29
Peringatan : Lookuptidak bisa serial
SliverNinja - MSFT

1
bagaimana kita harus menambahkan item ke Pencarian itu?
moldovanu

171

Kelas Daftar sebenarnya berfungsi cukup baik untuk koleksi kunci / nilai yang berisi duplikat di mana Anda ingin beralih pada koleksi. Contoh:

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
Solusi ini berfungsi secara fungsional, tetapi penerapan Daftar tidak memiliki pengetahuan tentang kunci atau nilai dan tidak dapat mengoptimalkan pencarian kunci sama sekali
Spencer Rose

41

Berikut adalah salah satu cara untuk melakukan ini dengan List <KeyValuePair <string, string>>

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

Output k1 = v1, k1 = v2, k1 = v3


Bekerja sangat baik dalam skenario saya dan juga mudah digunakan.
user6121177

21

Jika Anda menggunakan string baik sebagai kunci maupun nilainya, Anda dapat menggunakan System.Collections.Specialized.NameValueCollection , yang akan mengembalikan array nilai string melalui metode GetValues ​​(kunci kunci).


6
NameValueCollection tidak mengizinkan beberapa kunci.
Jenny O'Reilly

@ Jenny O'Reilly: Tentu, Anda dapat menambahkan kunci duplikat.
isHuman

Tegasnya @ JennyO'Reilly benar, karena pernyataan pada halaman MSDN tertaut dengan jelas menyatakan: kelas ini menyimpan beberapa nilai string di bawah satu kunci .
Ronald

Ini akan memungkinkan tetapi akan mengembalikan beberapa nilai, saya mencoba menggunakan indeks dan kunci.
user6121177

18

Saya baru saja menemukan pustaka PowerCollections yang mencakup, antara lain, kelas yang disebut MultiDictionary. Ini dengan rapi membungkus jenis fungsi ini.


14

Catatan yang sangat penting tentang penggunaan Pencarian:

Anda dapat membuat instance dari Lookup(TKey, TElement)dengan memanggil ToLookupobjek yang mengimplementasikanIEnumerable(T)

Tidak ada konstruktor publik untuk membuat instance baru a Lookup(TKey, TElement). Selain itu, Lookup(TKey, TElement)objek tidak dapat diubah, yaitu, Anda tidak dapat menambah atau menghapus elemen atau kunci dari Lookup(TKey, TElement)objek setelah itu dibuat.

(dari MSDN)

Saya pikir ini akan menjadi show stopper untuk sebagian besar kegunaan.


6
Saya bisa memikirkan sangat sedikit kegunaan di mana ini akan menjadi show stopper. Tapi kemudian, saya pikir benda-benda abadi itu hebat.
Joel Mueller

1
@ JoelMueller Saya bisa memikirkan banyak kasus di mana ini adalah show stopper. Harus membuat ulang kamus untuk menambahkan item bukanlah solusi yang sangat efisien ...
reirab

12

Saya pikir sesuatu seperti List<KeyValuePair<object, object>>akan melakukan pekerjaan itu.


Bagaimana Anda melihat sesuatu di daftar itu dengan kuncinya?
Wayne Bloss

Anda harus mengulanginya: tetapi saya tidak mengetahui Kelas lookup. NET 3.5: mungkin ini lebih berguna untuk mencari kontennya.
MADMap

2
@ wizlib: Satu-satunya cara adalah mengulang daftar, yang hampir tidak seefisien hashing. -1
petr k.

@petrk. Itu benar-benar tergantung pada apa data Anda. Saya menggunakan implementasi ini karena saya memiliki kunci unik yang sangat sedikit dan tidak ingin menimbulkan benturan hash. +1
Evan M

9

Jika Anda menggunakan> = .NET 4 maka Anda dapat menggunakan TupleKelas:

// 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());
}

Ini terlihat seperti List<KeyValuePair<key, value>>solusi seperti di atas. Apakah aku salah?
Nathan Goings

6

Cukup mudah untuk "memutar versi Anda sendiri" dari kamus yang memungkinkan entri "kunci duplikat". Berikut ini adalah implementasi sederhana yang kasar. Anda mungkin ingin mempertimbangkan untuk menambahkan dukungan untuk sebagian besar (jika tidak semua) pada dasarnya 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];
        }
    }
}

Contoh cepat tentang cara menggunakannya:

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 mendukung beberapa nilai string di bawah satu kunci (yang juga merupakan string), tetapi itu adalah satu-satunya contoh yang saya ketahui.

Saya cenderung membuat konstruksi yang mirip dengan yang ada dalam contoh Anda ketika saya mengalami situasi di mana saya memerlukan fungsionalitas semacam itu.


3

Saat menggunakan List<KeyValuePair<string, object>>opsi, Anda bisa menggunakan LINQ untuk melakukan pencarian:

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
Ya, tapi itu tidak membuatnya lebih cepat (masih tanpa hashing)
Haukman

3

Karena C # baru (saya yakin ini dari 7.0), Anda juga dapat melakukan sesuatu seperti ini:

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

dan Anda menggunakannya sebagai Daftar standar, tetapi dengan dua nilai bernama apa pun yang Anda inginkan

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

2

Apakah maksud Anda kongruen dan bukan duplikat yang sebenarnya? Kalau tidak, hashtable tidak akan bisa berfungsi.

Congruent berarti bahwa dua kunci terpisah dapat hash dengan nilai yang setara, tetapi kunci tidak sama.

Sebagai contoh: katakanlah fungsi hash hash Anda hanya hashval = key mod 3. Baik 1 dan 4 peta ke 1, tetapi merupakan nilai yang berbeda. Di sinilah gagasan Anda tentang daftar berperan.

Ketika Anda perlu mencari 1, nilai itu di-hash ke 1, daftar dilewati hingga Kunci = 1 ditemukan.

Jika Anda mengizinkan kunci duplikat untuk dimasukkan, Anda tidak akan dapat membedakan kunci mana yang dipetakan ke nilai mana.


2
Tabel hash sudah menangani kunci yang kebetulan hash dengan nilai yang sama (ini disebut collision). Saya merujuk pada situasi di mana Anda ingin memetakan banyak nilai ke kunci yang sama persis.

2

Cara saya menggunakan hanya a

Dictionary<string, List<string>>

Dengan cara ini Anda memiliki kunci tunggal yang memegang daftar string.

Contoh:

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

Bagus dan bersih.
Jerry Nixon

Ini adalah solusi yang bagus untuk menangani kasus penggunaan saya.
dub stylee

1

Saya sengaja menemukan posting ini untuk mencari jawaban yang sama, dan tidak menemukannya, jadi saya mencocokkan solusi contoh sederhana menggunakan daftar kamus, menimpa operator [] untuk menambahkan kamus baru ke daftar ketika semua yang lain memiliki diberikan kunci (set), dan mengembalikan daftar nilai (dapatkan).
Itu jelek dan tidak efisien, itu HANYA mendapat / set dengan kunci, dan selalu mengembalikan daftar, tetapi berfungsi:

 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

Saya mengubah jawaban @Hector Correa menjadi ekstensi dengan tipe generik dan juga menambahkan TryGetValue khusus ke dalamnya.

  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

Ini adalah cara derek Kamus serentak saya pikir ini akan membantu Anda:

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

contoh:

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

saya menggunakan kelas sederhana ini:

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

pemakaian:

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

0

Anda dapat membuat bungkus kamus Anda sendiri, sesuatu seperti ini, sebagai bonus itu mendukung nilai nol sebagai kunci:

/// <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;
        }
    }
}

Sampel penggunaan:

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 }

Bisakah Anda memberikan penjelasan seputar apa yang kode Anda lakukan, bagaimana cara menjawab pertanyaan, dan beberapa contoh penggunaan?
Enigmativity

@ Enigmativity, tidak persis apa yang diminta, pertanyaannya adalah untuk menyarankan beberapa kamus yang mendukung duplikat, jadi saya menawarkan untuk membuat pembungkus di sekitar. Net kamus yang akan mendukung fungsi ini dan sebagai contoh membuat pembungkus tersebut, sebagai bonus itu bisa menangani null sebagai kunci (bahkan jika itu adalah praktik yang buruk, pasti) Penggunaannya cukup sederhana: 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 }
Alexander Tolstikov

Bisakah Anda menambahkan detail ke jawaban Anda?
Enigmativity

@Enigmativity, jika Anda bermaksud jawaban yang asli, maka pasti
Alexander Tolstikov

-1

Anda dapat mendefinisikan metode untuk membuat kunci string Senyawa di mana Anda ingin menggunakan kamus. Anda harus menggunakan metode ini untuk membuat kunci Anda, misalnya:

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

untuk menggunakan:

myDict.ContainsKey(keyBuilder(key1, key2))

-3

Kunci duplikat memutus seluruh kontrak Kamus. Dalam kamus, setiap tombol unik dan dipetakan ke satu nilai. Jika Anda ingin menautkan suatu objek ke sejumlah objek tambahan yang sewenang-wenang, taruhan terbaik mungkin adalah sesuatu yang mirip dengan DataSet (secara umum menggunakan tabel). Letakkan kunci Anda di satu kolom dan nilai Anda di kolom lainnya. Ini secara signifikan lebih lambat dari sebuah kamus, tetapi itu adalah tradeoff Anda karena kehilangan kemampuan untuk hash objek utama.


5
Bukankah seluruh titik menggunakan Kamus untuk mendapatkan kinerja? Menggunakan DataSet tampaknya tidak lebih baik daripada Daftar <KeyValuePair <T, U >>.
Doug

-4

Ini juga mungkin:

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

Dengan cara ini, kita dapat memiliki kunci unik. Semoga ini berhasil untuk Anda.


OP meminta kamus yang memungkinkan kunci duplikat.
Skaparate

-10

Anda dapat menambahkan kunci yang sama dengan case berbeda seperti:

key1
Key1
key1
key1
key1
key1

Saya tahu adalah jawaban bodoh, tetapi berhasil untuk saya.


4
Tidak, itu tidak berhasil untuk Anda. Kamus memungkinkan pencarian sangat cepat - juga digolongkan sebagai O (1) - dengan kunci, Anda kehilangan itu ketika Anda menambahkan beberapa kunci berbeda cased Anda, karena bagaimana Anda mengambilnya? Coba setiap kombinasi huruf besar / kecil? Tidak peduli bagaimana Anda melakukannya, kinerja tidak akan seperti yang normal, pencarian kamus tunggal. Itu di samping yang lain, kekurangan yang lebih jelas seperti batasan nilai per kunci, tergantung pada jumlah karakter yang bisa dikunci di kunci.
Eugene Beresovsky
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.