Dizionario bidirezionale / bidirezionale in C #?


90

Voglio memorizzare le parole in un dizionario nel modo seguente:

Posso ottenere codice parola per parola: dict["SomeWord"]-> 123e ottenere codice parola per parola: dict[123]->"SomeWord"

È vero? Ovviamente un modo per farlo sono due dizionari: Dictionary<string,int>e Dictionary<int,string>ma c'è un altro modo?


2
Non esiste un tipo di dati standard (a partire da .NET 4) che fornisce O (1) accesso in entrambi i modi ... AFAIK :)

Inoltre, non che una mappa bidirezionale (parola chiave?) Imponga ulteriori restrizioni, a meno che una mappa multi-bidirezionale ...

Risposte:


110

Ho scritto un paio di lezioni veloci che ti permettono di fare quello che vuoi. Probabilmente avresti bisogno di estenderlo con più funzionalità, ma è un buon punto di partenza.

L'uso del codice è simile a questo:

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

map.Add(42, "Hello");

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

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

Ecco la definizione:

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

2
@ Pedro77 - Adesso sì. ;-)
Enigmativity

2
@ Pedro77 - Ero solo sfacciato suggerendo che la mia classe era la nuova soluzione "mappa".
Enigmativity

12
Questo non mantiene invarianti di classe sulle eccezioni. È possibile _forward.Addche abbia successo e _reverse.Addfallisca, lasciandoti con una coppia parzialmente aggiunta.

5
@hvd - Come ho detto, è una lezione messa insieme velocemente.
Enigmativity

3
@AaA Non sta modificando la Forwardproprietà del dizionario in sé (che ha private set;), ma sta modificando il valore su quel dizionario tramite la proprietà Indexer della classe Indexer che lo passa al dizionario. public T4 this[T3 index] { get { return _dictionary[index]; } set { _dictionary[index] = value; } }Quindi questo interrompe la ricerca in avanti / indietro.
Jeroen van Langen

27

Purtroppo, hai bisogno di due dizionari, uno per ogni direzione. Tuttavia, puoi facilmente ottenere il dizionario inverso usando LINQ:

Dictionary<T1, T2> dict = new Dictionary<T1, T2>();
Dictionary<T2, T1> dictInverse = dict.ToDictionary((i) => i.Value, (i) => i.Key);

11

Espanso sul codice Enigmativity aggiungendo il metodo initializes e 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);
        }
    }
}

Ecco un caso d'uso, controlla le parentesi valide

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

7

Potresti usare due dizionari, come altri hanno detto, ma nota anche che se sia TKeyeTValue sono dello stesso tipo (e i loro domini del valore di runtime sono noti per essere disgiunti), puoi semplicemente usare lo stesso dizionario creando due voci per ogni chiave / accoppiamento valore:

dict["SomeWord"]= "123" e dict["123"]="SomeWord"

In questo modo è possibile utilizzare un singolo dizionario per entrambi i tipi di ricerca.


3
Sì, questo approccio è stato riconosciuto nella domanda :)

3
Ciò ignora la possibilità che lo stesso valore esista sia in "chiavi" che in "valori". quindi entrerà in conflitto in questa soluzione.
user1028741

1
@ user1028741 D'accordo, anche se dall'esempio sembra che intendessero "di un tipo diverso" non "dello stesso tipo"
Hutch

Ciò può comportare risultati imprevisti in futuro, quindi il codice passa attraverso il refactoring. Ad esempio, i lati sinistro e destro iniziano a sovrapporsi. Non aggiunge quasi nulla in termini di prestazioni.
Vinigas

6

Che diamine, inserirò la mia versione nel 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();
    }
}

Aggiungi alcuni dati ad esso:

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

E poi fai la ricerca:

lookup[2] --> 1, 6
lookup[3] --> 1
lookup[8] --> 6

Mi piace che questo supporti 1: N
Sebastian

@Sebastian, potresti aggiungere IEnumerable <KeyValuePair <TKey, TValue >>.
Ostati

4

È possibile utilizzare questo metodo di estensione, sebbene utilizzi l'enumerazione e pertanto potrebbe non essere altrettanto efficiente per set di dati di grandi dimensioni. Se sei preoccupato per l'efficienza, allora hai bisogno di due dizionari. Se si desidera raggruppare i due dizionari in una classe, vedere la risposta accettata per questa domanda: Dizionario bidirezionale da 1 a 1 in 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");
    }
}

8
Sebbene questo sia un dizionario bidirezionale, il recupero del valore è un'operazione O (n) in cui dovrebbe essere un'operazione O (1). Ciò potrebbe non essere importante per set di dati di piccole dimensioni, ma potrebbe causare problemi di prestazioni quando si lavora con set di dati di grandi dimensioni. La migliore risposta per le prestazioni nello spazio sarebbe utilizzare due dizionari con dati invertiti.
Tom

@ TomA sono pienamente d'accordo con Tom, l'unico caso in cui hai bisogno di un vero dizionario bidirezionale è quando hai 100K, 1M + voci, qualsiasi cosa in meno la scansione è effettivamente un NOOP.
Chris Marisic

Mi piace questa soluzione per la mia situazione (dimensioni ridotte dei dict) perché posso ancora utilizzare gli inizializzatori di raccolta. Mappa <A, B> nella risposta accettata non penso possa essere utilizzata negli inizializzatori di raccolta.
CVertex

@ChrisMarisic, sembra una cosa strana da proclamare. Se questa ricerca venisse chiamata in un loop ristretto, scommetto, sentiresti il ​​dolore anche con <500 voci. Dipende anche dal costo di un test di confronto. Non penso che affermazioni radicali come il tuo commento siano utili.
Lee Campbell

@LeeCampbell le mie affermazioni radicali si basano sull'esperienza nella realtà reale, come nella realtà misurabile e profilata. Se vuoi usare un tipo complesso come chiave per un dizionario, questo è un tuo problema, non un mio problema.
Chris Marisic

1

Bictionary

Ecco una commistione di ciò che mi è piaciuto in ogni risposta. Si implementa in IEnumerablemodo da poter utilizzare l'inizializzatore di raccolta, come puoi vedere nell'esempio.

Vincolo di utilizzo:

  • Stai utilizzando diversi tipi di dati. (cioè )T1T2

Codice:

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

Violino:

https://dotnetfiddle.net/mTNEuw


Soluzione molto elegante! Potresti forse spiegarci un po 'di più il coraggio? Ho ragione, che non puoi creare un Bictionary<string, string>anche se tutte le corde sono uniche?
Marcus Mangelsdorf

@ Merlin2001, è corretto. Più precisamente, non è possibile eseguire ricerche in avanti con quello. Dovrò pensare a come superarlo. Viene compilato ma trova sempre per primo l'indicizzatore inverso quando T1 == T2, quindi le ricerche dirette falliscono. Inoltre, non posso sovrascrivere l'indicizzatore predefinito perché le chiamate di ricerca sarebbero ambigue. Ho aggiunto questo vincolo e rimosso quello precedente, perché i valori di T1possono sovrapporsi ai valori di T2.
Toddmo

10
C'è un problema di prestazioni piuttosto serio sul retro; il dizionario viene cercato due volte, con un risultato positivo O (n); sarebbe molto più veloce usare un secondo dizionario e rimuoverebbe il vincolo di tipo.
Steve Cooper

@SteveCooper, forse potrei sbarazzarmi del calo delle prestazioni avvolgendolo in un trye convertendo le eccezioni in KeyNotFoundExceptions.
Toddmo

4
@toddmo potresti essere in grado di raddoppiare la velocità in questo modo. Il problema più grande è che sia. First che. Any cercano un elemento alla volta, testandoli ciascuno. Quindi testare un elenco di 1.000.000 di elementi richiede 1.000.000 di volte più tempo per la ricerca rispetto a un elenco di 1 elemento. I dizionari sono molto più veloci e non rallentano man mano che aggiungi più elementi, quindi un secondo dizionario inverso farà risparmiare enormi quantità di tempo su elenchi di grandi dimensioni. Potrebbe non essere rilevante, ma è il genere di cosa che può andare bene con piccole quantità di dati durante i test, e quindi uccide le prestazioni su un server reale con dati seri.
Steve Cooper

1

Questo è un vecchio problema ma volevo aggiungere due metodi di estensione nel caso qualcuno lo trovasse utile. Il secondo non è così utile ma fornisce un punto di partenza se è necessario supportare dizionari uno a uno.

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

1

Una versione modificata della risposta di Xavier John, con un costruttore aggiuntivo per portare avanti e indietro i comparatori. Ciò supporterebbe chiavi senza distinzione tra maiuscole e minuscole, ad esempio. Ulteriori costruttori potrebbero essere aggiunti, se necessario, per passare ulteriori argomenti ai costruttori di dizionario diretto e inverso.

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
    ...
}

Esempio di utilizzo, con una chiave senza distinzione tra maiuscole e minuscole:

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
*/

1

Ecco il mio codice. Tutto è O (1) eccetto i costruttori con seed.

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

Mi chiedo come si comporterà il costruttore se gli passi un dizionario esistente dove i tipi di chiave e valore sono gli stessi. Come si risolverà se utilizzare le retrovie rispetto a quelle in avanti?
Colm Bhandal

0

La seguente classe di incapsulamento utilizza linq (IEnumerable Extensions) su 1 istanza di dizionario.

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"];

    }

}

0

Questo utilizza un indicizzatore per la ricerca inversa.
La ricerca inversa è O (n) ma non utilizza nemmeno due dizionari

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

Ad esempio: possibile NRE in this[string Word]. Ulteriori problemi sono nomi di variabili che non corrispondono a pratiche comuni, commenti incoerenti con il codice ( UInt16vs UInt32- ecco perché: non usare commenti!), La soluzione non è generica, ...
BartoszKP

0

Ecco una soluzione alternativa a quelle suggerite. Rimossa la classe interna e assicurata la coerenza durante l'aggiunta / rimozione di oggetti

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


0

C'è un BijectionDictionary tipo disponibile in questo repository open source:

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

Non è qualitativamente molto diverso dalle altre risposte fornite. Utilizza due dizionari, come la maggior parte di quelle risposte.

Ciò che è nuovo, credo, di questo dizionario rispetto alle altre risposte finora, è che invece di comportarsi come un dizionario a due vie, si comporta semplicemente come un dizionario familiare unidirezionale e quindi ti consente dinamicamente di capovolgere il dizionario usando la proprietà Reverse. Il riferimento all'oggetto capovolto è superficiale, quindi sarà ancora in grado di modificare lo stesso oggetto principale del riferimento originale. Quindi puoi avere due riferimenti allo stesso oggetto, tranne uno di loro è capovolto.

Un'altra cosa che probabilmente è unica in questo dizionario è che ci sono alcuni test scritti per esso nel progetto di test sotto quel repository. È stato utilizzato da noi in pratica ed è stato abbastanza stabile finora.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.