Dictionnaire bidirectionnel / bidirectionnel en C #?

87

Je souhaite stocker des mots dans un dictionnaire de la manière suivante:

Je peux obtenir un code mot par mot: dict["SomeWord"]-> 123et obtenir un code mot par mot: dict[123]->"SomeWord"

Est-ce que c'est réel? Bien sûr, une façon de faire est de deux dictionnaires: Dictionary<string,int>et Dictionary<int,string>mais y a-t-il une autre façon?

Neir0
la source
2
Il n'y a pas de type de données standard (à partir de .NET 4) qui fournit un accès O (1) dans les deux sens ... AFAIK :)
Pas non plus qu'une carte bidirectionnelle (mot-clé?) Impose des restrictions supplémentaires, à moins qu'une carte multi-bidirectionnelle ...

Réponses:

109

J'ai écrit quelques cours rapides qui vous permettent de faire ce que vous voulez. Vous auriez probablement besoin de l'étendre avec plus de fonctionnalités, mais c'est un bon point de départ.

L'utilisation du code ressemble à ceci:

var map = new Map<int, string>();

map.Add(42, "Hello");

Console.WriteLine(map.Forward[42]);
// Outputs "Hello"

Console.WriteLine(map.Reverse["Hello"]);
//Outputs 42

Voici la définition:

public class Map<T1, T2>
{
    private Dictionary<T1, T2> _forward = new Dictionary<T1, T2>();
    private Dictionary<T2, T1> _reverse = new Dictionary<T2, T1>();

    public Map()
    {
        this.Forward = new Indexer<T1, T2>(_forward);
        this.Reverse = new Indexer<T2, T1>(_reverse);
    }

    public class Indexer<T3, T4>
    {
        private Dictionary<T3, T4> _dictionary;
        public Indexer(Dictionary<T3, T4> dictionary)
        {
            _dictionary = dictionary;
        }
        public T4 this[T3 index]
        {
            get { return _dictionary[index]; }
            set { _dictionary[index] = value; }
        }
    }

    public void Add(T1 t1, T2 t2)
    {
        _forward.Add(t1, t2);
        _reverse.Add(t2, t1);
    }

    public Indexer<T1, T2> Forward { get; private set; }
    public Indexer<T2, T1> Reverse { get; private set; }
}
Énigmativité
la source
2
@ Pedro77 - C'est le cas maintenant. ;-)
Enigmativity
2
@ Pedro77 - J'étais juste effronté en suggérant que ma classe était la nouvelle solution "carte".
Enigmativity
11
Cela ne maintient pas les invariants de classe sur les exceptions. Il est possible _forward.Addde réussir et _reverse.Addd'échouer, vous laissant avec une paire partiellement ajoutée.
5
@hvd - Comme je l'ai dit - c'est un cours rapidement mis en place.
Enigmativity
3
@AaA Il ne modifie pas la Forwardpropriété de dictionnaire lui-même (qui possède private set;), mais il modifie la valeur de ce dictionnaire via la propriété Indexer de la classe Indexer qui la transmet au dictionnaire. public T4 this[T3 index] { get { return _dictionary[index]; } set { _dictionary[index] = value; } }C'est donc interrompre la recherche avant / arrière.
Jeroen van Langen
27

Malheureusement, vous avez besoin de deux dictionnaires, un pour chaque direction. Cependant, vous pouvez facilement obtenir le dictionnaire inverse en utilisant LINQ:

Dictionary<T1, T2> dict = new Dictionary<T1, T2>();
Dictionary<T2, T1> dictInverse = dict.ToDictionary((i) => i.Value, (i) => i.Key);
Hasan Baidoun
la source
11

Développé sur le code Enigmativity en ajoutant la méthode initializes et Contains.

public class Map<T1, T2> : IEnumerable<KeyValuePair<T1, T2>>
{
    private readonly Dictionary<T1, T2> _forward = new Dictionary<T1, T2>();
    private readonly Dictionary<T2, T1> _reverse = new Dictionary<T2, T1>();

    public Map()
    {
        Forward = new Indexer<T1, T2>(_forward);
        Reverse = new Indexer<T2, T1>(_reverse);
    }

    public Indexer<T1, T2> Forward { get; private set; }
    public Indexer<T2, T1> Reverse { get; private set; }

    public void Add(T1 t1, T2 t2)
    {
        _forward.Add(t1, t2);
        _reverse.Add(t2, t1);
    }

    public void Remove(T1 t1)
    {
        T2 revKey = Forward[t1];
        _forward.Remove(t1);
        _reverse.Remove(revKey);
    }
    
    public void Remove(T2 t2)
    {
        T1 forwardKey = Reverse[t2];
        _reverse.Remove(t2);
        _forward.Remove(forwardKey);
    }

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

    public IEnumerator<KeyValuePair<T1, T2>> GetEnumerator()
    {
        return _forward.GetEnumerator();
    }

    public class Indexer<T3, T4>
    {
        private readonly Dictionary<T3, T4> _dictionary;

        public Indexer(Dictionary<T3, T4> dictionary)
        {
            _dictionary = dictionary;
        }

        public T4 this[T3 index]
        {
            get { return _dictionary[index]; }
            set { _dictionary[index] = value; }
        }

        public bool Contains(T3 key)
        {
            return _dictionary.ContainsKey(key);
        }
    }
}

Voici un cas d'utilisation, vérifiez les parenthèses valides

public static class ValidParenthesisExt
{
    private static readonly Map<char, char>
        _parenthesis = new Map<char, char>
        {
            {'(', ')'},
            {'{', '}'},
            {'[', ']'}
        };

    public static bool IsValidParenthesis(this string input)
    {
        var stack = new Stack<char>();
        foreach (var c in input)
        {
            if (_parenthesis.Forward.Contains(c))
                stack.Push(c);
            else
            {
                if (stack.Count == 0) return false;
                if (_parenthesis.Reverse[c] != stack.Pop())
                    return false;
            }
        }
        return stack.Count == 0;
    }
}
Xavier John
la source
7

Vous pouvez utiliser deux dictionnaires, comme d'autres l'ont dit, mais notez également que si les deux TKeyet TValuesont du même type (et que leurs domaines de valeur d'exécution sont connus pour être disjoints), vous pouvez simplement utiliser le même dictionnaire en créant deux entrées pour chaque clé / valeur appariement:

dict["SomeWord"]= "123" et dict["123"]="SomeWord"

De cette façon, un seul dictionnaire peut être utilisé pour l'un ou l'autre type de recherche.

zmbq
la source
3
Oui, cette approche a été reconnue dans la question :)
3
Cela ne tient pas compte de la possibilité que la même valeur existe à la fois dans les «clés» et les «valeurs». alors il entrera en conflit dans cette solution.
user1028741
1
@ user1028741 D'accord, bien que, d'après l'exemple, il semble qu'ils signifiaient "d'un type différent" et non "du même type"
Hutch
6

Bon sang, je vais jeter ma version dans le mix:

public class BijectiveDictionary<TKey, TValue> 
{
    private EqualityComparer<TKey> _keyComparer;
    private Dictionary<TKey, ISet<TValue>> _forwardLookup;
    private EqualityComparer<TValue> _valueComparer;
    private Dictionary<TValue, ISet<TKey>> _reverseLookup;             

    public BijectiveDictionary()
        : this(EqualityComparer<TKey>.Default, EqualityComparer<TValue>.Default)
    {
    }

    public BijectiveDictionary(EqualityComparer<TKey> keyComparer, EqualityComparer<TValue> valueComparer)
        : this(0, EqualityComparer<TKey>.Default, EqualityComparer<TValue>.Default)
    {
    }

    public BijectiveDictionary(int capacity, EqualityComparer<TKey> keyComparer, EqualityComparer<TValue> valueComparer)
    {
        _keyComparer = keyComparer;
        _forwardLookup = new Dictionary<TKey, ISet<TValue>>(capacity, keyComparer);            
        _valueComparer = valueComparer;
        _reverseLookup = new Dictionary<TValue, ISet<TKey>>(capacity, valueComparer);            
    }

    public void Add(TKey key, TValue value)
    {
        AddForward(key, value);
        AddReverse(key, value);
    }

    public void AddForward(TKey key, TValue value)
    {
        ISet<TValue> values;
        if (!_forwardLookup.TryGetValue(key, out values))
        {
            values = new HashSet<TValue>(_valueComparer);
            _forwardLookup.Add(key, values);
        }
        values.Add(value);
    }

    public void AddReverse(TKey key, TValue value) 
    {
        ISet<TKey> keys;
        if (!_reverseLookup.TryGetValue(value, out keys))
        {
            keys = new HashSet<TKey>(_keyComparer);
            _reverseLookup.Add(value, keys);
        }
        keys.Add(key);
    }

    public bool TryGetReverse(TValue value, out ISet<TKey> keys)
    {
        return _reverseLookup.TryGetValue(value, out keys);
    }

    public ISet<TKey> GetReverse(TValue value)
    {
        ISet<TKey> keys;
        TryGetReverse(value, out keys);
        return keys;
    }

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

    public bool TryGetForward(TKey key, out ISet<TValue> values)
    {
        return _forwardLookup.TryGetValue(key, out values);
    }

    public ISet<TValue> GetForward(TKey key)
    {
        ISet<TValue> values;
        TryGetForward(key, out values);
        return values;
    }

    public bool ContainsReverse(TValue value)
    {
        return _reverseLookup.ContainsKey(value);
    }

    public void Clear()
    {
        _forwardLookup.Clear();
        _reverseLookup.Clear();
    }
}

Ajoutez-y des données:

var lookup = new BijectiveDictionary<int, int>();

lookup.Add(1, 2);
lookup.Add(1, 3);
lookup.Add(1, 4);
lookup.Add(1, 5);

lookup.Add(6, 2);
lookup.Add(6, 8);
lookup.Add(6, 9);
lookup.Add(6, 10);

Et puis faites la recherche:

lookup[2] --> 1, 6
lookup[3] --> 1
lookup[8] --> 6
Ostati
la source
J'aime que cela prenne en charge 1: N
Sebastian
@Sebastian, vous pouvez ajouter IEnumerable <KeyValuePair <TKey, TValue >>.
Ostati
4

Vous pouvez utiliser cette méthode d'extension, même si elle utilise l'énumération et peut donc ne pas être aussi performante pour les grands ensembles de données. Si vous êtes préoccupé par l'efficacité, vous avez besoin de deux dictionnaires. Si vous souhaitez regrouper les deux dictionnaires dans une seule classe, consultez la réponse acceptée pour cette question: Dictionnaire bidirectionnel 1 à 1 en C #

public static class IDictionaryExtensions
{
    public static TKey FindKeyByValue<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TValue value)
    {
        if (dictionary == null)
            throw new ArgumentNullException("dictionary");

        foreach (KeyValuePair<TKey, TValue> pair in dictionary)
            if (value.Equals(pair.Value)) return pair.Key;

        throw new Exception("the value is not found in the dictionary");
    }
}
moribvndvs
la source
8
Bien qu'il s'agisse d'un dictionnaire bidirectionnel, la récupération de la valeur est une opération O (n) où elle devrait être une opération O (1). Cela n'a pas d'importance pour les petits ensembles de données, mais peut entraîner des problèmes de performances lors de l'utilisation de grands ensembles. La meilleure réponse pour les performances dans l'espace serait d'utiliser deux dictionnaires avec des données inversées.
Tom
@TomA Je suis entièrement d'accord avec Tom, le seul cas où vous avez besoin d'un vrai dictionnaire bidirectionnel est lorsque vous avez 100K, 1M + entrées, rien de moins que la numérisation, c'est effectivement un NOOP.
Chris Marisic
J'aime cette solution pour ma situation (petites tailles de dict) car je peux toujours utiliser des initialiseurs de collection. Map <A, B> dans la réponse acceptée, je ne pense pas qu'il puisse être utilisé dans les initialiseurs de collection.
CVertex
@ChrisMarisic, cela semble une chose étrange à proclamer. Si cette recherche était appelée dans une boucle serrée, je parie que vous ressentiriez la douleur même avec <500 entrées. Cela dépend également du coût d'un test de comparaison. Je ne pense pas que des déclarations générales comme votre commentaire soient utiles.
Lee Campbell
@LeeCampbell mes déclarations radicales sont basées sur l'expérience de la réalité réelle, comme dans la réalité mesurable et profilée. Si vous souhaitez utiliser un type complexe comme clé d'un dictionnaire, c'est votre problème pas mon problème.
Chris Marisic
1

Bictionary

Voici un mélange de ce que j'ai aimé dans chaque réponse. Il implémente IEnumerablepour pouvoir utiliser l'initialiseur de collection, comme vous pouvez le voir dans l'exemple.

Contrainte d'utilisation:

  • Vous utilisez différents types de données. (c'est-à-dire )T1T2

Code:

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

public class Program
{
    public static void Main()
    {
        Bictionary<string, int> bictionary = 
            new Bictionary<string,int>() {
                { "a",1 }, 
                { "b",2 }, 
                { "c",3 } 
            };

        // test forward lookup
        Console.WriteLine(bictionary["b"]);
        // test forward lookup error
        //Console.WriteLine(bictionary["d"]);
        // test reverse lookup
        Console.WriteLine(bictionary[3]); 
        // test reverse lookup error (throws same error as forward lookup does)
        Console.WriteLine(bictionary[4]); 
    }
}

public class Bictionary<T1, T2> : Dictionary<T1, T2>
{
    public T1 this[T2 index]
    {
        get
        {
            if(!this.Any(x => x.Value.Equals(index)))
               throw new System.Collections.Generic.KeyNotFoundException();
            return this.First(x => x.Value.Equals(index)).Key;
        }
    }
}

Violon:

https://dotnetfiddle.net/mTNEuw

Toddmo
la source
Solution très élégante! Pourriez-vous peut-être en expliquer un peu plus les tripes? Ai-je raison de dire que vous ne pouvez pas faire de Bictionary<string, string>même si toutes les chaînes sont uniques?
Marcus Mangelsdorf
@ Merlin2001, c'est exact. Plus précisément, vous ne pouvez pas faire de recherches directes avec cela. Je vais devoir réfléchir à la façon de surmonter cela. Il compile mais il trouve toujours l'indexeur inversé en premier quand T1 == T2, donc les recherches directes échouent. De plus, je ne peux pas remplacer l'indexeur par défaut car les appels de recherche seraient ambigus. J'ai ajouté cette contrainte et supprimé la précédente, car les valeurs de T1peuvent chevaucher les valeurs de T2.
toddmo
10
Il y a un problème de performances assez sérieux à l'inverse; le dictionnaire est recherché deux fois, avec un succès de performance O (n); il serait beaucoup plus rapide d'utiliser un deuxième dictionnaire et supprimerait la contrainte de type.
Steve Cooper
@SteveCooper, je pourrais peut-être me débarrasser de l'atteinte aux performances en l'enveloppant dans un tryet en convertissant les exceptions en KeyNotFoundExceptions.
toddmo
4
@toddmo vous pourrez peut-être doubler la vitesse de cette façon. Le plus gros problème est que .First et .Any recherchent un élément à la fois, en testant chacun d'eux. Donc, pour tester une liste de 1 000 000 éléments, la recherche prend 1 000 000 fois plus longtemps qu'une liste à 1 élément. Les dictionnaires sont beaucoup plus rapides et ne ralentissent pas au fur et à mesure que vous ajoutez des éléments, donc un deuxième dictionnaire inversé vous fera gagner un temps épique sur de grandes listes. Ce n'est peut-être pas pertinent, mais c'est le genre de chose qui peut convenir avec de petites quantités de données pendant les tests, puis cela tue les performances sur un vrai serveur avec des données sérieuses.
Steve Cooper
1

C'est un vieux problème mais je voulais ajouter deux méthodes d'extension au cas où quelqu'un le trouverait utile. Le second n'est pas aussi utile, mais il fournit un point de départ si des dictionnaires un à un doivent être pris en charge.

    public static Dictionary<VALUE,KEY> Inverse<KEY,VALUE>(this Dictionary<KEY,VALUE> dictionary)
    {
        if (dictionary==null || dictionary.Count == 0) { return null; }

        var result = new Dictionary<VALUE, KEY>(dictionary.Count);

        foreach(KeyValuePair<KEY,VALUE> entry in dictionary)
        {
            result.Add(entry.Value, entry.Key);
        }

        return result;
    }

    public static Dictionary<VALUE, KEY> SafeInverse<KEY, VALUE>(this Dictionary<KEY, VALUE> dictionary)
    {
        if (dictionary == null || dictionary.Count == 0) { return null; }

        var result = new Dictionary<VALUE, KEY>(dictionary.Count);

        foreach (KeyValuePair<KEY, VALUE> entry in dictionary)
        {
            if (result.ContainsKey(entry.Value)) { continue; }

            result.Add(entry.Value, entry.Key);
        }

        return result;
    }
Felipe Ramos
la source
1

Une version modifiée de la réponse de Xavier John, avec un constructeur supplémentaire pour faire avancer et inverser les comparateurs. Cela prendrait en charge les clés insensibles à la casse, par exemple. D'autres constructeurs pourraient être ajoutés, si nécessaire, pour transmettre d'autres arguments aux constructeurs Dictionary avant et arrière.

public class Map<T1, T2> : IEnumerable<KeyValuePair<T1, T2>>
{
    private readonly Dictionary<T1, T2> _forward;
    private readonly Dictionary<T2, T1> _reverse;

    /// <summary>
    /// Constructor that uses the default comparers for the keys in each direction.
    /// </summary>
    public Map()
        : this(null, null)
    {
    }

    /// <summary>
    /// Constructor that defines the comparers to use when comparing keys in each direction.
    /// </summary>
    /// <param name="t1Comparer">Comparer for the keys of type T1.</param>
    /// <param name="t2Comparer">Comparer for the keys of type T2.</param>
    /// <remarks>Pass null to use the default comparer.</remarks>
    public Map(IEqualityComparer<T1> t1Comparer, IEqualityComparer<T2> t2Comparer)
    {
        _forward = new Dictionary<T1, T2>(t1Comparer);
        _reverse = new Dictionary<T2, T1>(t2Comparer);
        Forward = new Indexer<T1, T2>(_forward);
        Reverse = new Indexer<T2, T1>(_reverse);
    }

    // Remainder is the same as Xavier John's answer:
    // https://stackoverflow.com/a/41907561/216440
    ...
}

Exemple d'utilisation, avec une clé insensible à la casse:

Map<int, string> categories = 
new Map<int, string>(null, StringComparer.CurrentCultureIgnoreCase)
{
    { 1, "Bedroom Furniture" },
    { 2, "Dining Furniture" },
    { 3, "Outdoor Furniture" }, 
    { 4, "Kitchen Appliances" }
};

int categoryId = 3;
Console.WriteLine("Description for category ID {0}: '{1}'", 
    categoryId, categories.Forward[categoryId]);

string categoryDescription = "DINING FURNITURE";
Console.WriteLine("Category ID for description '{0}': {1}", 
    categoryDescription, categories.Reverse[categoryDescription]);

categoryDescription = "outdoor furniture";
Console.WriteLine("Category ID for description '{0}': {1}", 
    categoryDescription, categories.Reverse[categoryDescription]);

// Results:
/*
Description for category ID 3: 'Outdoor Furniture'
Category ID for description 'DINING FURNITURE': 2
Category ID for description 'outdoor furniture': 3
*/
Simon Tewsi
la source
1

Voici mon code. Tout est O (1) sauf pour les constructeurs prédéfinis.

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

public class TwoWayDictionary<T1, T2>
{
    Dictionary<T1, T2> _Forwards = new Dictionary<T1, T2>();
    Dictionary<T2, T1> _Backwards = new Dictionary<T2, T1>();

    public IReadOnlyDictionary<T1, T2> Forwards => _Forwards;
    public IReadOnlyDictionary<T2, T1> Backwards => _Backwards;

    public IEnumerable<T1> Set1 => Forwards.Keys;
    public IEnumerable<T2> Set2 => Backwards.Keys;


    public TwoWayDictionary()
    {
        _Forwards = new Dictionary<T1, T2>();
        _Backwards = new Dictionary<T2, T1>();
    }

    public TwoWayDictionary(int capacity)
    {
        _Forwards = new Dictionary<T1, T2>(capacity);
        _Backwards = new Dictionary<T2, T1>(capacity);
    }

    public TwoWayDictionary(Dictionary<T1, T2> initial)
    {
        _Forwards = initial;
        _Backwards = initial.ToDictionary(kvp => kvp.Value, kvp => kvp.Key);
    }

    public TwoWayDictionary(Dictionary<T2, T1> initial)
    {
        _Backwards = initial;
        _Forwards = initial.ToDictionary(kvp => kvp.Value, kvp => kvp.Key);
    }


    public T1 this[T2 index]
    {
        get => _Backwards[index];
        set
        {
            if (_Backwards.TryGetValue(index, out var removeThis))
                _Forwards.Remove(removeThis);

            _Backwards[index] = value;
            _Forwards[value] = index;
        }
    }

    public T2 this[T1 index]
    {
        get => _Forwards[index];
        set
        {
            if (_Forwards.TryGetValue(index, out var removeThis))
                _Backwards.Remove(removeThis);

            _Forwards[index] = value;
            _Backwards[value] = index;
        }
    }

    public int Count => _Forwards.Count;

    public bool Contains(T1 item) => _Forwards.ContainsKey(item);
    public bool Contains(T2 item) => _Backwards.ContainsKey(item);

    public bool Remove(T1 item)
    {
        if (!this.Contains(item))
            return false;

        var t2 = _Forwards[item];

        _Backwards.Remove(t2);
        _Forwards.Remove(item);

        return true;
    }

    public bool Remove(T2 item)
    {
        if (!this.Contains(item))
            return false;

        var t1 = _Backwards[item];

        _Forwards.Remove(t1);
        _Backwards.Remove(item);

        return true;
    }

    public void Clear()
    {
        _Forwards.Clear();
        _Backwards.Clear();
    }
}
Iamsodarncool
la source
Je me demande comment le constructeur se comportera si vous lui passez un dictionnaire existant où les types clé et valeur sont les mêmes. Comment résoudra-t-il l'opportunité d'utiliser les vers l'arrière ou vers l'avant?
Colm Bhandal le
0

La classe d'encapsulation suivante utilise linq (IEnumerable Extensions) sur 1 instance de dictionnaire.

public class TwoWayDictionary<TKey, TValue>
{
    readonly IDictionary<TKey, TValue> dict;
    readonly Func<TKey, TValue> GetValueWhereKey;
    readonly Func<TValue, TKey> GetKeyWhereValue;
    readonly bool _mustValueBeUnique = true;

    public TwoWayDictionary()
    {
        this.dict = new Dictionary<TKey, TValue>();
        this.GetValueWhereKey = (strValue) => dict.Where(kvp => Object.Equals(kvp.Key, strValue)).Select(kvp => kvp.Value).FirstOrDefault();
        this.GetKeyWhereValue = (intValue) => dict.Where(kvp => Object.Equals(kvp.Value, intValue)).Select(kvp => kvp.Key).FirstOrDefault();
    }

    public TwoWayDictionary(KeyValuePair<TKey, TValue>[] kvps)
        : this()
    {
        this.AddRange(kvps);
    }

    public void AddRange(KeyValuePair<TKey, TValue>[] kvps)
    {
        kvps.ToList().ForEach( kvp => {        
            if (!_mustValueBeUnique || !this.dict.Any(item => Object.Equals(item.Value, kvp.Value)))
            {
                dict.Add(kvp.Key, kvp.Value);
            } else {
                throw new InvalidOperationException("Value must be unique");
            }
        });
    }

    public TValue this[TKey key]
    {
        get { return GetValueWhereKey(key); }
    }

    public TKey this[TValue value]
    {
        get { return GetKeyWhereValue(value); }
    }
}

class Program
{
    static void Main(string[] args)
    {
        var dict = new TwoWayDictionary<string, int>(new KeyValuePair<string, int>[] {
            new KeyValuePair<string, int>(".jpeg",100),
            new KeyValuePair<string, int>(".jpg",101),
            new KeyValuePair<string, int>(".txt",102),
            new KeyValuePair<string, int>(".zip",103)
        });


        var r1 = dict[100];
        var r2 = dict[".jpg"];

    }

}
Brett Caswell
la source
0

Cela utilise un indexeur pour la recherche inversée.
La recherche inversée est O (n) mais elle n'utilise pas non plus deux dictionnaires

public sealed class DictionaryDoubleKeyed : Dictionary<UInt32, string>
{   // used UInt32 as the key as it has a perfect hash
    // if most of the lookup is by word then swap
    public void Add(UInt32 ID, string Word)
    {
        if (this.ContainsValue(Word)) throw new ArgumentException();
        base.Add(ID, Word);
    }
    public UInt32 this[string Word]
    {   // this will be O(n)
        get
        {
            return this.FirstOrDefault(x => x.Value == Word).Key;
        }
    } 
}
paparazzi
la source
Par exemple: possible NRE dans this[string Word]. Les problèmes supplémentaires sont les noms de variables ne correspondant pas aux pratiques courantes, les commentaires incohérents avec le code ( UInt16vs UInt32- c'est pourquoi: n'utilisez pas de commentaires!), La solution n'est pas générique, ...
BartoszKP
0

Voici une solution alternative à celles qui ont été suggérées. Suppression de la classe interne et assurance de la cohérence lors de l'ajout / suppression d'éléments

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

public class Map<E, F> : IEnumerable<KeyValuePair<E, F>>
{
    private readonly Dictionary<E, F> _left = new Dictionary<E, F>();
    public IReadOnlyDictionary<E, F> left => this._left;
    private readonly Dictionary<F, E> _right = new Dictionary<F, E>();
    public IReadOnlyDictionary<F, E> right => this._right;

    public void RemoveLeft(E e)
    {
        if (!this.left.ContainsKey(e)) return;
        this._right.Remove(this.left[e]);
        this._left.Remove(e);
    }

    public void RemoveRight(F f)
    {
        if (!this.right.ContainsKey(f)) return;
        this._left.Remove(this.right[f]);
        this._right.Remove(f);
    }

    public int Count()
    {
        return this.left.Count;
    }

    public void Set(E left, F right)
    {
        if (this.left.ContainsKey(left))
        {
            this.RemoveLeft(left);
        }
        if (this.right.ContainsKey(right))
        {
            this.RemoveRight(right);
        }
        this._left.Add(left, right);
        this._right.Add(right, left);
    }


    public IEnumerator<KeyValuePair<E, F>> GetEnumerator()
    {
        return this.left.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.left.GetEnumerator();
    }
}

sirpaddow
la source
0

Il existe un BijectionDictionarytype disponible dans ce dépôt open source:

https://github.com/ColmBhandal/CsharpExtras .

Ce n'est pas qualitativement très différent des autres réponses données. Il utilise deux dictionnaires, comme la plupart de ces réponses.

Ce qui est nouveau, je crois, à propos de ce dictionnaire par rapport aux autres réponses jusqu'à présent, c'est qu'au lieu de se comporter comme un dictionnaire bidirectionnel, il se comporte simplement comme un dictionnaire familier à sens unique et vous permet ensuite de retourner dynamiquement le dictionnaire en utilisant la propriété Reverse. La référence d'objet retournée est peu profonde, elle pourra donc toujours modifier le même objet principal que la référence d'origine. Vous pouvez donc avoir deux références au même objet, sauf que l'une d'elles est retournée.

Une autre chose qui est probablement unique à propos de ce dictionnaire est qu'il y a des tests écrits pour lui dans le projet de test sous ce référentiel. Il a été utilisé par nous en pratique et a été assez stable jusqu'à présent.

Colm Bhandal
la source