Obtenir plusieurs clés de la valeur spécifiée d'un dictionnaire générique?

122

Il est facile d'obtenir la valeur d'une clé à partir d'un dictionnaire générique .NET:

Dictionary<int, string> greek = new Dictionary<int, string>();
greek.Add(1, "Alpha");
greek.Add(2, "Beta");
string secondGreek = greek[2];  // Beta

Mais essayer d'obtenir les clés avec une valeur n'est pas aussi simple car il pourrait y avoir plusieurs clés:

int[] betaKeys = greek.WhatDoIPutHere("Beta");  // expecting single 2
Dour High Arch
la source
1
Pourquoi le type de retour est-il int[]lorsque vous attendez une valeur unique?
anar khalilov le
3
@Anar, lis ma réponse à Domenic; «Les valeurs en double sont peu probables mais pas impossibles».
Dour High Arch
la clé d'une valeur? Je pense que vous voulez dire les clés
Max Hodges

Réponses:

144

D'accord, voici la version bidirectionnelle multiple:

using System;
using System.Collections.Generic;
using System.Text;

class BiDictionary<TFirst, TSecond>
{
    IDictionary<TFirst, IList<TSecond>> firstToSecond = new Dictionary<TFirst, IList<TSecond>>();
    IDictionary<TSecond, IList<TFirst>> secondToFirst = new Dictionary<TSecond, IList<TFirst>>();

    private static IList<TFirst> EmptyFirstList = new TFirst[0];
    private static IList<TSecond> EmptySecondList = new TSecond[0];

    public void Add(TFirst first, TSecond second)
    {
        IList<TFirst> firsts;
        IList<TSecond> seconds;
        if (!firstToSecond.TryGetValue(first, out seconds))
        {
            seconds = new List<TSecond>();
            firstToSecond[first] = seconds;
        }
        if (!secondToFirst.TryGetValue(second, out firsts))
        {
            firsts = new List<TFirst>();
            secondToFirst[second] = firsts;
        }
        seconds.Add(second);
        firsts.Add(first);
    }

    // Note potential ambiguity using indexers (e.g. mapping from int to int)
    // Hence the methods as well...
    public IList<TSecond> this[TFirst first]
    {
        get { return GetByFirst(first); }
    }

    public IList<TFirst> this[TSecond second]
    {
        get { return GetBySecond(second); }
    }

    public IList<TSecond> GetByFirst(TFirst first)
    {
        IList<TSecond> list;
        if (!firstToSecond.TryGetValue(first, out list))
        {
            return EmptySecondList;
        }
        return new List<TSecond>(list); // Create a copy for sanity
    }

    public IList<TFirst> GetBySecond(TSecond second)
    {
        IList<TFirst> list;
        if (!secondToFirst.TryGetValue(second, out list))
        {
            return EmptyFirstList;
        }
        return new List<TFirst>(list); // Create a copy for sanity
    }
}

class Test
{
    static void Main()
    {
        BiDictionary<int, string> greek = new BiDictionary<int, string>();
        greek.Add(1, "Alpha");
        greek.Add(2, "Beta");
        greek.Add(5, "Beta");
        ShowEntries(greek, "Alpha");
        ShowEntries(greek, "Beta");
        ShowEntries(greek, "Gamma");
    }

    static void ShowEntries(BiDictionary<int, string> dict, string key)
    {
        IList<int> values = dict[key];
        StringBuilder builder = new StringBuilder();
        foreach (int value in values)
        {
            if (builder.Length != 0)
            {
                builder.Append(", ");
            }
            builder.Append(value);
        }
        Console.WriteLine("{0}: [{1}]", key, builder);
    }
}
Jon Skeet
la source
2
D'après ce que j'ai lu dans msdn, cela ne devrait-il pas être un BiLookup au lieu d'un BiDictionary? Non pas que ce soit important ou quoi que ce soit, juste curieux de savoir si je comprends bien les choses ici ...
Svish
De plus, j'ai utilisé GetByFirst et récupéré la EmptySecondList, y ai ajouté quelques éléments, puis appelé à nouveau GetByFirst, est-ce que je n'obtiendrais pas une liste avec certaines choses et pas une liste vide alors?
Svish
@Svish: Non, car lorsque vous essayez d'ajouter à la liste, cela lèverait une exception (vous ne pouvez pas ajouter à un tableau). Et oui, BiLookup serait probablement un meilleur nom.
Jon Skeet
Bien que je vois que cela répond à la question d'OP, n'est-ce pas une implémentation quelque peu naïve? Une implémentation plus réaliste ne serait-elle pas Dictionary <> List <> Dictionary afin que vous puissiez réellement rechercher des objets riches par 2 clés différentes?
Chris Marisic
@ChrisMarisic: Je ne sais pas ce que vous voulez dire - mais quelque chose comme ça, c'est ce que j'ai beaucoup utilisé et je n'ai pas eu besoin de plus.
Jon Skeet
74

Comme tout le monde l'a dit, il n'y a pas de mappage dans un dictionnaire d'une valeur à une clé.

Je viens de remarquer que vous vouliez mapper de valeur à plusieurs clés - je laisse cette solution ici pour la version à valeur unique, mais j'ajouterai ensuite une autre réponse pour une carte bidirectionnelle à entrées multiples.

L'approche normale à adopter ici est d'avoir deux dictionnaires - un mappage dans un sens et l'autre. Encapsulez-les dans une classe séparée et déterminez ce que vous voulez faire lorsque vous avez une clé ou une valeur en double (par exemple, lever une exception, écraser l'entrée existante ou ignorer la nouvelle entrée). Personnellement, j'irais probablement pour lancer une exception - cela rend le comportement de réussite plus facile à définir. Quelque chose comme ça:

using System;
using System.Collections.Generic;

class BiDictionary<TFirst, TSecond>
{
    IDictionary<TFirst, TSecond> firstToSecond = new Dictionary<TFirst, TSecond>();
    IDictionary<TSecond, TFirst> secondToFirst = new Dictionary<TSecond, TFirst>();

    public void Add(TFirst first, TSecond second)
    {
        if (firstToSecond.ContainsKey(first) ||
            secondToFirst.ContainsKey(second))
        {
            throw new ArgumentException("Duplicate first or second");
        }
        firstToSecond.Add(first, second);
        secondToFirst.Add(second, first);
    }

    public bool TryGetByFirst(TFirst first, out TSecond second)
    {
        return firstToSecond.TryGetValue(first, out second);
    }

    public bool TryGetBySecond(TSecond second, out TFirst first)
    {
        return secondToFirst.TryGetValue(second, out first);
    }
}

class Test
{
    static void Main()
    {
        BiDictionary<int, string> greek = new BiDictionary<int, string>();
        greek.Add(1, "Alpha");
        greek.Add(2, "Beta");
        int x;
        greek.TryGetBySecond("Beta", out x);
        Console.WriteLine(x);
    }
}
Jon Skeet
la source
1
Je ne pense pas qu'il y ait de raison de le faire dériver d'une classe concrète - je n'aime pas l'héritage sans une réflexion très approfondie - mais cela pourrait certainement implémenter IEnumerable etc. En fait, il pourrait implémenter IDictionary <TFirst, TSecond> et IDictionary <TSecond, TFirst>.
Jon Skeet
1
(Bien que ce serait assez étrange si TFirst et TSecond étaient les mêmes ...)
Jon Skeet
6
En fait, vous ne pouvez pas implémenter à la fois IDictionary <TFirst, TSecond> et IDictionary <TSecond, TFirst> en même temps, .NET 4.0 ne le permettra pas
Sebastian
2
@nawfal: L'un des Addappels au dictionnaire échouera - mais si c'est le second, nous avons alors mis le système dans un état confus. Ma façon, vous avez toujours une collection cohérente après l'exception.
Jon Skeet
1
@nawfal: Eh bien, je ne sais pas si c'est pourquoi je l'ai fait lorsque j'ai écrit la réponse pour la première fois ... je suppose;)
Jon Skeet
26

Les dictionnaires ne sont pas vraiment censés fonctionner comme ça, car si l'unicité des clés est garantie, l'unicité des valeurs ne l'est pas. Donc par exemple si vous aviez

var greek = new Dictionary<int, string> { { 1, "Alpha" }, { 2, "Alpha" } };

À quoi vous attendriez-vous greek.WhatDoIPutHere("Alpha")?

Par conséquent, vous ne pouvez pas vous attendre à ce que quelque chose comme ça soit intégré dans le framework. Vous auriez besoin de votre propre méthode pour vos propres utilisations uniques --- voulez-vous retourner un tableau (ou IEnumerable<T>)? Voulez-vous lever une exception s'il existe plusieurs clés avec la valeur donnée? Et s'il n'y en a pas?

Personnellement, je choisirais un énumérable, comme ceci:

IEnumerable<TKey> KeysFromValue<TKey, TValue>(this Dictionary<TKey, TValue> dict, TValue val)
{
    if (dict == null)
    {
        throw new ArgumentNullException("dict");
    }
    return dict.Keys.Where(k => dict[k] == val);
}

var keys = greek.KeysFromValue("Beta");
int exceptionIfNotExactlyOne = greek.KeysFromValue("Beta").Single();
Domenic
la source
Une solution élégante, mais qui doit fonctionner en 2.0. Les valeurs en double sont peu probables mais pas impossibles, renvoyer une collection serait mieux.
Dour High Arch
23

Peut-être que la façon la plus simple de le faire, sans Linq, peut être de boucler sur les paires:

int betaKey; 
foreach (KeyValuePair<int, string> pair in lookup)
{
    if (pair.Value == value)
    {
        betaKey = pair.Key; // Found
        break;
    }
}
betaKey = -1; // Not found

Si vous aviez Linq, cela aurait pu faire facilement de cette façon:

int betaKey = greek.SingleOrDefault(x => x.Value == "Beta").Key;
CMS
la source
dour, mais vous avez un type var ci-dessus?! vous êtes sûrement en 3.0? voir ma mise à jour ci-dessous aussi.
colombe
Toutes mes excuses, j'ai utilisé "var" simplement pour réduire la frappe. Je préférerais ne pas faire une recherche linéaire, le dictionnaire pourrait être volumineux.
Dour High Arch
2
varest une fonctionnalité de langage, pas une fonctionnalité de cadre. Vous pouvez utiliser la fusion nulle à partir de C # -6.0 et toujours cibler CF-2.0 si vous le souhaitez vraiment.
binki
3

Un dictionnaire ne conserve pas un hachage des valeurs, seulement les clés, donc toute recherche dessus en utilisant une valeur va prendre au moins un temps linéaire. Votre meilleur pari est simplement de parcourir les éléments du dictionnaire et de garder une trace des clés correspondantes ou de passer à une structure de données différente, peut-être de conserver deux mappages de dictionnaire clé-> valeur et valeur-> List_of_keys. Si vous faites ce dernier, vous échangerez du stockage contre de la vitesse de recherche. Il ne faudrait pas grand-chose pour transformer l'exemple @Cybis en une telle structure de données.

Tvanfosson
la source
3

Comme je voulais un dictionnaire bi-directionnel à part entière (et pas seulement une carte), j'ai ajouté les fonctions manquantes pour en faire une classe compatible IDictionary. Ceci est basé sur la version avec des paires clé-valeur uniques. Voici le fichier si vous le souhaitez (la plupart du travail était le XMLDoc):

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Common
{
    /// <summary>Represents a bidirectional collection of keys and values.</summary>
    /// <typeparam name="TFirst">The type of the keys in the dictionary</typeparam>
    /// <typeparam name="TSecond">The type of the values in the dictionary</typeparam>
    [System.Runtime.InteropServices.ComVisible(false)]
    [System.Diagnostics.DebuggerDisplay("Count = {Count}")]
    //[System.Diagnostics.DebuggerTypeProxy(typeof(System.Collections.Generic.Mscorlib_DictionaryDebugView<,>))]
    //[System.Reflection.DefaultMember("Item")]
    public class BiDictionary<TFirst, TSecond> : Dictionary<TFirst, TSecond>
    {
        IDictionary<TSecond, TFirst> _ValueKey = new Dictionary<TSecond, TFirst>();
        /// <summary> PropertyAccessor for Iterator over KeyValue-Relation </summary>
        public IDictionary<TFirst, TSecond> KeyValue => this;
        /// <summary> PropertyAccessor for Iterator over ValueKey-Relation </summary>
        public IDictionary<TSecond, TFirst> ValueKey => _ValueKey;

        #region Implemented members

        /// <Summary>Gets or sets the value associated with the specified key.</Summary>
        /// <param name="key">The key of the value to get or set.</param>
        /// <Returns>The value associated with the specified key. If the specified key is not found,
        ///      a get operation throws a <see cref="KeyNotFoundException"/>, and
        ///      a set operation creates a new element with the specified key.</Returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is null.</exception>
        /// <exception cref="T:System.Collections.Generic.KeyNotFoundException">
        /// The property is retrieved and <paramref name="key"/> does not exist in the collection.</exception>
        /// <exception cref="T:System.ArgumentException"> An element with the same key already
        /// exists in the <see cref="ValueKey"/> <see cref="Dictionary&lt;TFirst,TSecond&gt;"/>.</exception>
        public new TSecond this[TFirst key]
        {
            get { return base[key]; }
            set { _ValueKey.Remove(base[key]); base[key] = value; _ValueKey.Add(value, key); }
        }

        /// <Summary>Gets or sets the key associated with the specified value.</Summary>
        /// <param name="val">The value of the key to get or set.</param>
        /// <Returns>The key associated with the specified value. If the specified value is not found,
        ///      a get operation throws a <see cref="KeyNotFoundException"/>, and
        ///      a set operation creates a new element with the specified value.</Returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="val"/> is null.</exception>
        /// <exception cref="T:System.Collections.Generic.KeyNotFoundException">
        /// The property is retrieved and <paramref name="val"/> does not exist in the collection.</exception>
        /// <exception cref="T:System.ArgumentException"> An element with the same value already
        /// exists in the <see cref="KeyValue"/> <see cref="Dictionary&lt;TFirst,TSecond&gt;"/>.</exception>
        public TFirst this[TSecond val]
        {
            get { return _ValueKey[val]; }
            set { base.Remove(_ValueKey[val]); _ValueKey[val] = value; base.Add(value, val); }
        }

        /// <Summary>Adds the specified key and value to the dictionary.</Summary>
        /// <param name="key">The key of the element to add.</param>
        /// <param name="value">The value of the element to add.</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> or <paramref name="value"/> is null.</exception>
        /// <exception cref="T:System.ArgumentException">An element with the same key or value already exists in the <see cref="Dictionary&lt;TFirst,TSecond&gt;"/>.</exception>
        public new void Add(TFirst key, TSecond value) {
            base.Add(key, value);
            _ValueKey.Add(value, key);
        }

        /// <Summary>Removes all keys and values from the <see cref="Dictionary&lt;TFirst,TSecond&gt;"/>.</Summary>
        public new void Clear() { base.Clear(); _ValueKey.Clear(); }

        /// <Summary>Determines whether the <see cref="Dictionary&lt;TFirst,TSecond&gt;"/> contains the specified
        ///      KeyValuePair.</Summary>
        /// <param name="item">The KeyValuePair to locate in the <see cref="Dictionary&lt;TFirst,TSecond&gt;"/>.</param>
        /// <Returns>true if the <see cref="Dictionary&lt;TFirst,TSecond&gt;"/> contains an element with
        ///      the specified key which links to the specified value; otherwise, false.</Returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="item"/> is null.</exception>
        public bool Contains(KeyValuePair<TFirst, TSecond> item) => base.ContainsKey(item.Key) & _ValueKey.ContainsKey(item.Value);

        /// <Summary>Removes the specified KeyValuePair from the <see cref="Dictionary&lt;TFirst,TSecond&gt;"/>.</Summary>
        /// <param name="item">The KeyValuePair to remove.</param>
        /// <Returns>true if the KeyValuePair is successfully found and removed; otherwise, false. This
        ///      method returns false if <paramref name="item"/> is not found in the <see cref="Dictionary&lt;TFirst,TSecond&gt;"/>.</Returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="item"/> is null.</exception>
        public bool Remove(KeyValuePair<TFirst, TSecond> item) => base.Remove(item.Key) & _ValueKey.Remove(item.Value);

        /// <Summary>Removes the value with the specified key from the <see cref="Dictionary&lt;TFirst,TSecond&gt;"/>.</Summary>
        /// <param name="key">The key of the element to remove.</param>
        /// <Returns>true if the element is successfully found and removed; otherwise, false. This
        ///      method returns false if <paramref name="key"/> is not found in the <see cref="Dictionary&lt;TFirst,TSecond&gt;"/>.</Returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is null.</exception>
        public new bool Remove(TFirst key) => _ValueKey.Remove(base[key]) & base.Remove(key);

        /// <Summary>Gets the key associated with the specified value.</Summary>
        /// <param name="value">The value of the key to get.</param>
        /// <param name="key">When this method returns, contains the key associated with the specified value,
        ///      if the value is found; otherwise, the default value for the type of the key parameter.
        ///      This parameter is passed uninitialized.</param>
        /// <Returns>true if <see cref="ValueKey"/> contains an element with the specified value; 
        ///      otherwise, false.</Returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="value"/> is null.</exception>
        public bool TryGetValue(TSecond value, out TFirst key) => _ValueKey.TryGetValue(value, out key);
        #endregion
    }
}
DW.com
la source
2

révisé: d'accord pour avoir une sorte de recherche, vous auriez besoin d'autre chose que d'un dictionnaire, car si vous y pensez, les dictionnaires sont des clés à sens unique. autrement dit, les valeurs peuvent ne pas être uniques

Cela dit, il semble que vous utilisez c # 3.0, vous n'aurez donc peut-être pas à recourir à la boucle et vous pourriez utiliser quelque chose comme:

var key = (from k in yourDictionary where string.Compare(k.Value, "yourValue", true)  == 0 select k.Key).FirstOrDefault();
Colombe
la source
Le dictionnaire n'a pas .FindByValue. Je préfère passer à une structure de données différente que de parcourir les valeurs.
Dour High Arch
2

La classe Dictionary n'est pas optimisée pour ce cas, mais si vous vouliez vraiment le faire (en C # 2.0), vous pouvez faire:

public List<TKey> GetKeysFromValue<TKey, TVal>(Dictionary<TKey, TVal> dict, TVal val)
{
   List<TKey> ks = new List<TKey>();
   foreach(TKey k in dict.Keys)
   {
      if (dict[k] == val) { ks.Add(k); }
   }
   return ks;
}

Je préfère la solution LINQ pour l'élégance, mais c'est la voie 2.0.

dbkk
la source
1

Ne pouvez-vous pas créer une sous-classe de Dictionary qui a cette fonctionnalité?


    public class MyDict < TKey, TValue > : Dictionary < TKey, TValue >
    {
        private Dictionary < TValue, TKey > _keys;

        public TValue this[TKey key]
        {
            get
            {
                return base[key];
            }
            set 
            { 
                base[key] = value;
                _keys[value] = key;
            }
        }

        public MyDict()
        {
            _keys = new Dictionary < TValue, TKey >();
        }

        public TKey GetKeyFromValue(TValue value)
        {
            return _keys[value];
        }
    }

EDIT: Désolé, je n'ai pas reçu le code du premier coup.

Cybis
la source
Cela changera simplement ce que j'utilise pour une clé et ne retournera que la valeur int de la clé de chaîne, je dois aller dans les deux sens. Et, comme le souligne Domenic, je peux avoir des valeurs de chaîne en double.
Dour High Arch
Si vous pouvez avoir des valeurs de chaîne en double pour vos clés int, qu'espérez-vous obtenir lorsque vous recherchez par chaîne? Un objet de liste des int correspondants?
Cybis
1

La solution de dictionnaire bidirectionnel «simple» proposée ici est complexe et peut être difficile à comprendre, à maintenir ou à étendre. Aussi la question originale demandait "la clé pour une valeur", mais clairement il pourrait y avoir plusieurs clés (j'ai depuis édité la question). L'approche dans son ensemble est plutôt suspecte.

Modifications du logiciel. L'écriture de code facile à maintenir doit avoir la priorité sur d'autres solutions de contournement complexes «intelligentes». La façon de récupérer des clés à partir des valeurs d'un dictionnaire est de faire une boucle. Un dictionnaire n'est pas conçu pour être bidirectionnel.

Max Hodges
la source
Ou peut-être un deuxième dictionnaire qui mappe chaque valeur à sa (ses) clé (s).
DavidRR
@DavidRR seules les clés doivent être uniques, donc la deuxième approche par dictionnaire ne fonctionnerait pas vraiment. Mais vous pouvez simplement parcourir le dictionnaire pour obtenir les clés d'une valeur.
Max Hodges
Si les appels de problème pour un dictionnaire pour prendre en charge plusieurs intvaleurs par stringclé, le dictionnaire peut être défini comme suit: Dictionary<string, List<int>>.
DavidRR
maintenant comment rendre cela bidirectionnel sans itération?
Max Hodges
En ce qui concerne la question du PO, une norme Dictionaryn'offre pas de capacité bidirectionnelle. Donc, si tout ce que vous avez est un standard Dictionaryet que vous souhaitez trouver la ou les clés associées à une valeur spécifique, vous devez en effet itérer! Cependant, pour les dictionnaires «volumineux», l'itération peut entraîner de mauvaises performances. Notez que la réponse que j'ai moi-même proposée est basée sur l'itération (via LINQ). Si votre initiale Dictionaryn'est pas sujette à d'autres modifications, vous pouvez créer une inverse Dictionaryune fois pour accélérer les recherches inversées.
DavidRR
1

Utilisez LINQ pour effectuer une Dictionary<K, V>recherche inversée . Mais gardez à l'esprit que les valeurs de vos Dictionary<K, V>valeurs peuvent ne pas être distinctes.

Manifestation:

using System;
using System.Collections.Generic;
using System.Linq;

class ReverseDictionaryLookupDemo
{
    static void Main()
    {
        var dict = new Dictionary<int, string>();
        dict.Add(4, "Four");
        dict.Add(5, "Five");
        dict.Add(1, "One");
        dict.Add(11, "One"); // duplicate!
        dict.Add(3, "Three");
        dict.Add(2, "Two");
        dict.Add(44, "Four"); // duplicate!

        Console.WriteLine("\n== Enumerating Distinct Values ==");
        foreach (string value in dict.Values.Distinct())
        {
            string valueString =
                String.Join(", ", GetKeysFromValue(dict, value));

            Console.WriteLine("{0} => [{1}]", value, valueString);
        }
    }

    static List<int> GetKeysFromValue(Dictionary<int, string> dict, string value)
    {
        // Use LINQ to do a reverse dictionary lookup.
        // Returns a 'List<T>' to account for the possibility
        // of duplicate values.
        return
            (from item in dict
             where item.Value.Equals(value)
             select item.Key).ToList();
    }
}

Production attendue:

== Enumerating Distinct Values ==
Four => [4, 44]
Five => [5]
One => [1, 11]
Three => [3]
Two => [2]
DavidRR
la source
1
Le problème que je vois avec ceci est que vous vérifiez chaque élément du dictionnaire afin d'obtenir la direction inverse. Un temps de recherche O (n) va à l'encontre de l'objectif de l'utilisation d'un dictionnaire; ce devrait être O (1).
stephen
@stephen - D'accord. Comme d'autres l'ont souligné, si la performance est primordiale, un dictionnaire distinct pour les valeurs ou un dictionnaire bidirectionnel serait approprié. Cependant, si la nécessité de faire une recherche de valeur est rare et que les performances sont acceptables, l'approche que je décris ici mérite d'être prise en considération. Cela dit, l'utilisation de LINQ dans ma réponse n'est pas compatible avec la volonté de l'OP d'une solution adaptée à une utilisation avec .NET 2.0. (Bien qu'une contrainte .NET 2.0 soit sans doute moins probable en 2014.)
DavidRR
1
Dictionary<string, string> dic = new Dictionary<string, string>();
dic["A"] = "Ahmed";
dic["B"] = "Boys";

foreach (string mk in dic.Keys)
{
    if(dic[mk] == "Ahmed")
    {
        Console.WriteLine("The key that contains \"Ahmed\" is " + mk);
    }
}
Loay
la source
1
Merci d'avoir publié une réponse! Bien qu'un extrait de code puisse répondre à la question, il est toujours bon d'ajouter des informations supplémentaires, comme expliquer, etc.
j0k
0

Comme une torsion de la réponse acceptée ( https://stackoverflow.com/a/255638/986160 ) en supposant que les clés seront associées à des valeurs de signle dans le dictionnaire. Similaire à ( https://stackoverflow.com/a/255630/986160 ) mais un peu plus élégant. La nouveauté réside dans le fait que la classe consommatrice peut être utilisée comme alternative d'énumération (mais aussi pour les chaînes) et que le dictionnaire implémente IEnumerable.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace MyApp.Dictionaries
{

    class BiDictionary<TFirst, TSecond> : IEnumerable
    {
        IDictionary<TFirst, TSecond> firstToSecond = new Dictionary<TFirst, TSecond>();
        IDictionary<TSecond, TFirst> secondToFirst = new Dictionary<TSecond, TFirst>();

        public void Add(TFirst first, TSecond second)
        {
            firstToSecond.Add(first, second);
            secondToFirst.Add(second, first);
        }

        public TSecond this[TFirst first]
        {
            get { return GetByFirst(first); }
        }

        public TFirst this[TSecond second]
        {
            get { return GetBySecond(second); }
        }

        public TSecond GetByFirst(TFirst first)
        {
            return firstToSecond[first];
        }

        public TFirst GetBySecond(TSecond second)
        {
            return secondToFirst[second];
        }

        public IEnumerator GetEnumerator()
        {
            return GetFirstEnumerator();
        }

        public IEnumerator GetFirstEnumerator()
        {
            return firstToSecond.GetEnumerator();
        }

        public IEnumerator GetSecondEnumerator()
        {
            return secondToFirst.GetEnumerator();
        }
    }
}

Et en tant que classe consommatrice, vous pourriez avoir

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MyApp.Dictionaries
{
    class Greek
    {

        public static readonly string Alpha = "Alpha";
        public static readonly string Beta = "Beta";
        public static readonly string Gamma = "Gamma";
        public static readonly string Delta = "Delta";


        private static readonly BiDictionary<int, string> Dictionary = new BiDictionary<int, string>();


        static Greek() {
            Dictionary.Add(1, Alpha);
            Dictionary.Add(2, Beta);
            Dictionary.Add(3, Gamma);
            Dictionary.Add(4, Delta);
        }

        public static string getById(int id){
            return Dictionary.GetByFirst(id);
        }

        public static int getByValue(string value)
        {
            return Dictionary.GetBySecond(value);
        }

    }
}
Michail Michailidis
la source
1
C'est fondamentalement la même chose qu'une réponse publiée il y a six ans et, comme indiqué alors, les clés ne sont pas associées à des valeurs uniques. Chaque clé peut avoir plusieurs valeurs.
Dour High Arch
Eh bien, je sais, mais ma version implémente IEnumerable et est plus élégante. De plus, l'exemple de classe de consommation met la classe BiDictionary à un niveau d'utilisation différent - il résout le problème des énumérations statiques de chaînes et d'identifiants qui ne sont pas fournis par le C #. J'y ai aussi fait référence si vous lisez ma réponse!
Michail Michailidis
0

Puis la solution du profane

Une fonction similaire à celle ci-dessous pourrait être écrite pour créer un tel dictionnaire:

    public Dictionary<TValue, TKey> Invert(Dictionary<TKey, TValue> dict) {
    Dictionary<TValue, TKey> ret = new Dictionary<TValue, TKey>();
    foreach (var kvp in dict) {ret[kvp.value] = kvp.key;} return ret; }
beppe9000
la source