web-dev-qa-db-fra.com

Des clés en double dans les dictionnaires .NET?

Existe-t-il des classes de dictionnaire dans la bibliothèque de classes de base .NET permettant l'utilisation de clés en double? La seule solution que j'ai trouvée consiste à créer, par exemple, une classe telle que:

Dictionary<string, List<object>>

Mais c'est assez irritant d'utiliser réellement. En Java, je pense qu’une carte multiple accomplit cette tâche, mais ne trouve pas d’analogue dans .NET.

232

Si vous utilisez .NET 3.5, utilisez la classe Lookup .

EDIT: Vous créez généralement un Lookup en utilisant Enumerable.ToLookup . Cela suppose que vous n'avez pas besoin de le changer après coup - mais je trouve généralement que c'est assez bon.

Si ne fonctionne pas pour vous, je ne pense pas que rien dans le cadre permette de l'aider - et utiliser le dictionnaire est aussi bon que cela obtient :(

217
Jon Skeet

En fait, la classe List fonctionne assez bien pour les collections de clés/valeurs contenant des doublons pour lesquelles vous souhaitez effectuer une itération sur la collection. Exemple:

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

Voici une façon de le faire avec 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);
}

Sorties k1 = v1, k1 = v2, k1 = v3

37
Hector Correa

Si vous utilisez des chaînes en tant que clés et valeurs, vous pouvez utiliser System.Collections.Specialized.NameValueCollection , qui renverra un tableau de valeurs de chaîne via la méthode GetValues ​​(string key).

21
Matt

Je viens de tomber sur la bibliothèque PowerCollections qui inclut, entre autres, une classe appelée MultiDictionary. Cela enveloppe parfaitement ce type de fonctionnalité.

Note très importante concernant l'utilisation de Lookup:

Vous pouvez créer une instance de Lookup(TKey, TElement) en appelant ToLookup sur un objet qui implémente IEnumerable(T).

Il n'y a pas de constructeur public pour créer une nouvelle instance de Lookup(TKey, TElement). De plus, les objets Lookup(TKey, TElement) sont immuables, ce qui signifie que vous ne pouvez pas ajouter ou supprimer des éléments ou des clés d'un objet Lookup(TKey, TElement) après sa création.

(à partir de MSDN)

Je penserais que ce serait un spectacle pour la plupart des utilisations.

14
TheSoftwareJedi

Je pense que quelque chose comme List<KeyValuePair<object, object>> ferait le travail.

10
MADMap

Si vous utilisez> = .NET 4, vous pouvez utiliser Tuple Class:

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

Jetez un coup d'œil à C5HashBag class.

6
Yuval

C'est assez facile de "rouler votre propre" version d'un dictionnaire qui permet les entrées "en double". Voici une mise en œuvre simple et approximative. Vous voudrez peut-être envisager de prendre en charge la plupart (sinon la totalité) sur 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];
        }
    }
}

Un exemple rapide sur la façon de l'utiliser:

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

En réponse à la question initiale. Quelque chose comme Dictionary<string, List<object>> est implémenté dans une classe appelée MultiMap dans The Code Project.

Vous trouverez plus d’informations sur le lien ci-dessous: http://www.codeproject.com/KB/cs/MultiKeyDictionary.aspx

4
Dan

NameValueCollection prend en charge plusieurs valeurs de chaîne sous une clé (qui est également une chaîne), mais c'est le seul exemple que je connaisse.

J'ai tendance à créer des constructions similaires à celles de votre exemple lorsque je rencontre des situations dans lesquelles j'ai besoin de ce type de fonctionnalité. 

3
ckramer

Lorsque vous utilisez l'option List<KeyValuePair<string, object>>, vous pouvez utiliser LINQ pour effectuer la recherche:

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 }
3
Greg

La façon dont j'utilise est juste un

Dictionary<string, List<string>>

De cette façon, vous avez une seule clé contenant une liste de chaînes.

Exemple:

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

Voulez-vous dire congruente et non un doublon réel? Sinon, une table de hachage ne pourrait pas fonctionner.

Congruent signifie que deux clés distinctes peuvent être hachées à la valeur équivalente, mais les clés ne sont pas égales.

Par exemple: supposons que la fonction de hachage de votre hashtable soit juste hashval = key mod 3. Les deux valeurs 1 et 4 correspondent à 1, mais sont différentes. C'est ici qu'intervient votre idée d'une liste.

Lorsque vous avez besoin de rechercher 1, cette valeur est hachée à 1, la liste est parcourue jusqu'à ce que la clé = 1 soit trouvée.

Si vous autorisiez l'insertion de clés dupliquées, vous ne seriez pas en mesure de différencier les clés en fonction des valeurs.

1
Nicholas Mancuso

J'ai remplacé la réponse de @Hector Correa par une extension avec des types génériques et y ai également ajouté une TryGetValue personnalisée.

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

Je suis tombé par hasard sur ce message à la recherche de la même réponse et je n’en ai trouvé aucun; j’ai donc construit un exemple simple de solution à l’aide d’une liste de dictionnaires. donnée clé (set), et retourne une liste de valeurs (get).
C'est moche et inefficace, il obtient/définit UNIQUEMENT par clé, et il retourne toujours une liste, mais cela fonctionne:

 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
Sintrinsic

C’est un dictionnaire simultané, je pense que cela vous aidera:

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

exemples:

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
Ali Yousefie

j'utilise cette classe simple:

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

usage:

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