.NET sözlüklerinde yinelenen anahtarlar?


256

.NET temel sınıf kitaplığında yinelenen anahtarların kullanılmasına izin veren sözlük sınıfları var mı? Bulduğum tek çözüm, örneğin, aşağıdaki gibi bir sınıf oluşturmaktır:

Dictionary<string, List<object>>

Ancak bu aslında kullanmak için oldukça rahatsız edici. Java'da, bir MultiMap'in bunu başardığına inanıyorum, ancak .NET'te bir analog bulamıyorum.


3
Bu yinelenen anahtar nasıl, yinelenen değerler (Liste), değil mi?
Shamim Hafiz

1
@ShamimHafiz, hayır, değerlerin yinelenmesi gerekmez. Yinelenenleri { a, 1 }ve anahtar olmanın { a, 2 }bir karma tablosunda saklamanız gerekiyorsa a, bir alternatif olması gerekir { a, [1, 2] }.
nawfal

1
Aslında, burada gerçekten istenen şeyin, her bir anahtarın bir veya daha fazla değerle eşleşebileceği bir koleksiyon olduğuna inanıyorum. "Yinelenen anahtarlar" ifadesinin bunu gerçekten aktarmadığını düşünüyorum.
DavidRR

İleride başvurmak için, aynı anahtarları tekrar tekrar eklemek yerine 1 anahtarını sadece değerleri eklemeyi düşünmelisiniz.
Ya Wang

Hem anahtar hem de değer dizeyse , NameValueCollection vardır (her dize anahtarıyla birden çok dize değerini ilişkilendirebilir).
AnorZaken

Yanıtlar:


229

.NET 3.5 kullanıyorsanız, Lookupsınıfı kullanın .

EDIT: Genellikle bir Lookupkullanarak oluşturun Enumerable.ToLookup. Bu, daha sonra değiştirmenize gerek olmadığını varsayar - ancak genellikle bunun yeterince iyi olduğunu düşünüyorum.

Bu sizin için işe yaramazsa , çerçevede yardımcı olacak bir şey olduğunu düşünmüyorum - ve sözlüğü kullanmak olabildiğince iyi :(


Lookup'taki head-up için teşekkürler. Standart sıralı ölçüt olmayan bir linq sorgusundan sonuçları bölümlere ayırmanın (gruplandırmanın) harika bir yolunu sunar.
Robert Paulson

3
@Josh: Bir tane oluşturmak için Enumerable.ToLookup kullanıyorsunuz.
Jon Skeet

29
Dikkat kelimesi : Lookupserileştirilemez
SliverNinja - MSFT

1
bu Aramaya nasıl öğe eklemeliyiz?
moldovanu

171

List sınıfı aslında koleksiyon üzerinde yineleme yapmak istediğiniz kopyaları içeren anahtar / değer koleksiyonları için oldukça iyi çalışır. Misal:

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
Bu çözüm işlevsel olarak çalışır, ancak List'in uygulanması anahtar veya değer hakkında hiçbir bilgiye sahip değildir ve anahtar aramayı optimize edemez
Spencer Rose

41

List <KeyValuePair <string, string>> ile bunu yapmanın bir yolu

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

Çıkışlar k1 = v1, k1 = v2, k1 = v3


Senaryomda harika çalışıyor ve kullanımı kolay.
user6121177

21

Dizeleri hem anahtar hem de değer olarak kullanıyorsanız , GetValues ​​(dize anahtarı) yöntemiyle bir dize değerleri dizisi döndürecek System.Collections.Specialized.NameValueCollection öğesini kullanabilirsiniz .


6
NameValueCollection birden çok anahtara izin vermiyor.
Jenny O'Reilly

@Jenny O'Reilly: Elbette, yinelenen anahtarlar ekleyebilirsiniz.
isHuman

Kesinlikle söylemek gerekirse @ JennyO'Reilly doğrudur, çünkü bağlantılı MSDN sayfasındaki açıklamalar açıkça belirtmektedir: bu sınıf birden fazla dize değerini tek bir anahtar altında saklar .
Ronald

İzin verir ama birden çok değer döndürür, ben dizin ve anahtar kullanmayı denedim.
user6121177

18

Diğer şeylerin yanı sıra MultiDictionary adlı bir sınıf içeren PowerCollections kütüphanesine yeni geldim . Bu, bu tür işlevleri düzgün bir şekilde sarar.


14

Arama kullanımı ile ilgili çok önemli not:

Lookup(TKey, TElement)Uygulayan ToLookupbir nesneyi çağırarak bir örneği oluşturabilirsiniz.IEnumerable(T)

A'nın yeni bir örneğini oluşturacak genel bir kurucu yoktur Lookup(TKey, TElement). Ayrıca, Lookup(TKey, TElement)nesneler değiştirilemez, yani bir Lookup(TKey, TElement)nesne oluşturulduktan sonra öğe veya anahtar ekleyemez veya kaldıramazsınız .

(MSDN'den)

Bunun çoğu kullanım için bir şov tıpası olacağını düşünüyorum.


6
Bunun bir gösteri tıpası olacağı yerlerde çok az kullanım düşünebilirim. Ama sonra, değişmez nesnelerin harika olduğunu düşünüyorum.
Joel Mueller

1
@JoelMueller Bunun bir gösteri tıpası olduğu birçok durumu düşünebilirim. Bir öğe eklemek için sözlüğü yeniden oluşturmak zorunda kalmak özellikle etkili bir çözüm değildir ...
reirab

12

Bence List<KeyValuePair<object, object>>İşi yapacak bir şey .


Bu listede bir şeyi anahtarıyla nasıl görüyorsunuz?
Wayne Bloss

Bunu yinelemek zorundasınız: ama .NET 3.5 LookUp-Class'ın farkında değildim: belki de içeriğinde arama yapmak için daha yararlıdır.
MADMap

2
@wizlib: Tek yol liste içinde dolaşmaktır, bu da hash kadar etkili değildir. -1
petr k.

@petrk. Bu gerçekten verilerinizin ne olduğuna bağlıdır. Bu uygulamayı kullandım çünkü çok az sayıda benzersiz anahtarım vardı ve karma çarpışmaların yükünü kaldırmak istemiyordum. +1
Evan M

9

> = .NET 4 kullanıyorsanız TupleClass'ı kullanabilirsiniz :

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

Bu List<KeyValuePair<key, value>>, yukarıdaki gibi bir çözüme benziyor. Yanlış mıyım?
Nathan Goings

6

"Yinelenen anahtar" girişlerine izin veren bir sözlüğün "kendinize ait" sürümünü kullanmak yeterince kolaydır. İşte kaba basit bir uygulama. Temel olarak çoğu için (hepsi değilse de) destek eklemeyi düşünebilirsiniz 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];
        }
    }
}

Nasıl kullanılacağına dair hızlı bir örnek:

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 bir anahtar altında birden fazla dize değerini destekler (bu da bir dizedir), ancak farkında olduğum tek örnektir.

Bu tür işlevselliğe ihtiyaç duyduğum durumlarla karşılaştığımda, örneğinizdekine benzer yapılar oluşturma eğilimindeyim.


3

Kullanırken List<KeyValuePair<string, object>>seçeneği, sen arama yapmak için LINQ kullanabilirsiniz:

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
Evet, ama bu daha hızlı yapmıyor (hala hashing yok)
Haukman

3

Yeni C # (7.0'dan olduğuna inanıyorum) beri, böyle bir şey de yapabilirsiniz:

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

ve bunu standart Liste olarak kullanıyorsunuz, ancak istediğiniz her iki adda

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

2

Gerçek bir kopya değil, uyumlu mu demek istiyorsun? Aksi takdirde bir karma bir işe yaramazdı.

Uyumlu, iki ayrı anahtarın eşdeğer değere hash edebileceği, ancak anahtarların eşit olmadığı anlamına gelir.

Örneğin: hashtable öğenizin hash işlevinin sadece hashval = anahtar mod 3 olduğunu varsayalım. Hem 1 hem de 4, 1 ile eşleşir, ancak farklı değerlerdir. Burada liste fikriniz devreye giriyor.

1'e bakmanız gerektiğinde, bu değer 1'e hash edilir, Anahtar = 1 bulunana kadar liste gezilir.

Yinelenen anahtarların eklenmesine izin verdiyseniz, hangi anahtarların hangi değerlerle eşleştiğini ayırt edemezdiniz.


2
Bir karma tablosu zaten aynı değere hash olan anahtarları işler (buna çarpışma denir). Birden çok değeri aynı anahtarla eşlemek istediğiniz bir duruma başvuruyorum.

2

Benim kullanma şeklim sadece

Dictionary<string, List<string>>

Bu şekilde dizelerin listesini tutan tek bir anahtarınız olur.

Misal:

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

Güzel ve temiz.
Jerry Nixon

Kullanım durumumu ele almak için harika bir çözüm.
dub stylee

1

Aynı cevabı bulmak için bu gönderi boyunca tökezledim ve hiçbiri bulamadım, bu yüzden bir sözlük listesi kullanarak çıplak kemikli bir örnek çözüm hazırladım, [] operatörünü diğerlerine sahip olduğunda listeye yeni bir sözlük eklemek için geçersiz kıldım verilen anahtarı (set) girin ve bir değer listesi döndürün (get).
Çirkin ve verimsiz, SADECE anahtarla alır / ayarlar ve her zaman bir liste döndürür, ancak çalışır:

 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'nın cevabını genel türlerle bir uzantıya dönüştürdüm ve buna özel bir TryGetValue ekledim.

  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

Bu bir çekme yolu Eşzamanlı sözlük Sanırım bu size yardımcı olacaktır:

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

örnekler:

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

Bu basit sınıfı kullanın:

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

kullanımı:

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

0

Bunun gibi bir şey olan kendi sözlük paketinizi, anahtar olarak boş değeri desteklediği bir bonus olarak oluşturabilirsiniz:

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

Kullanım örneği:

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 }

Kodunuzun ne yaptığı, soruyu nasıl yanıtladığı ve bazı örnek kullanımları hakkında biraz açıklama yapabilir misiniz?
Enigmativite

@ Enigmativity, tam olarak sorulan şeyi yapar, soru yinelenenleri destekleyen bazı sözlükler önermekti, bu yüzden .net sözlüğü etrafında bu işlevselliği destekleyecek bir örnek oluşturmayı teklif ettim ve bir örnek olarak, bir anahtar olarak null işlemek (kötü bir uygulama olsa bile, kesin) Kullanımı oldukça basit: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

Cevabınıza detay ekleyebilir misiniz?
Enigmativite

@ Enigmativity, eğer orijinal cevabı kastediyorsanız, o zaman emin olun
Alexander Tolstikov

-1

U, sözlük kullanmak istediğiniz her yerde bir Bileşik dize anahtarı oluşturmak için bir yöntem tanımlayabilir, örneğin anahtarınızı oluşturmak için bu yöntemi kullanmanız gerekir:

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

kullanmak için:

myDict.ContainsKey(keyBuilder(key1, key2))

-3

Yinelenen anahtarlar, sözlüğün tüm sözleşmesini bozar. Sözlükte her anahtar benzersizdir ve tek bir değerle eşleştirilir. Bir nesneyi gelişigüzel sayıda ek nesneye bağlamak istiyorsanız, en iyi bahis bir DataSet'e benzeyen bir şey olabilir (genel olarak bir tablo). Anahtarlarınızı bir sütuna, değerlerinizi diğer sütuna koyun. Bu, bir sözlükten önemli ölçüde daha yavaştır, ancak bu, anahtar nesneleri hash etme yeteneğini kaybettiğiniz için ödünç vermenizdir.


5
Performans kazancı için Sözlük kullanmanın asıl amacı bu değil midir? Bir DataSet kullanmak List <KeyValuePair <T, U >> listesinden daha iyi görünmüyor.
Doug

-4

Ayrıca bu mümkündür:

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

Bu şekilde benzersiz anahtarlara sahip olabiliriz. Umarım bu işe yarar.


OP yinelenen tuşlara izin veren bir sözlük istedi.
Skaparate

-10

Farklı anahtarlar ile aynı anahtarları ekleyebilirsiniz:

key1
Anahtar1
KEY1
KeY1
kEy1
keY1

Kukla cevap olduğunu biliyorum, ama benim için çalıştı.


4
Hayır, işe yaramadı. Sözlükler çok hızlı aramaya izin verir - ayrıca O (1) olarak da sınıflandırılır - farklı kasalı çoklu anahtarlarınızı eklediğinizde kaybedersiniz, çünkü bunları nasıl alırsınız? Her büyük / küçük harf kombinasyonunu denemek ister misiniz? Nasıl yaparsanız yapın, performans normal, tek sözlük araması olmayacaktır. Bu, anahtardaki yükseltilebilir karakter sayısına bağlı olarak anahtar başına değer sınırlaması gibi diğer daha belirgin eksikliklere ek olarak.
Eugene Beresovsky
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.