Dicionário bidirecional / bidirecional em C #?

87

Quero armazenar palavras em um dicionário da seguinte maneira:

Posso obter código palavra por palavra: dict["SomeWord"]-> 123e obter código palavra por palavra: dict[123]->"SomeWord"

É real? Claro, uma maneira de fazer isso são dois dicionários: Dictionary<string,int>e Dictionary<int,string>mas existe outra maneira?

Neir0
fonte
2
Não há nenhum tipo de dados padrão (a partir do .NET 4) que forneça acesso O (1) nos dois sentidos ... AFAIK :)
Também não que um mapa bidirecional (palavra-chave?) Impõe restrições adicionais, a menos que um mapa multi-bidirecional ...

Respostas:

109

Eu escrevi algumas aulas rápidas que permitem que você faça o que quiser. Você provavelmente precisará estendê-lo com mais recursos, mas é um bom ponto de partida.

O uso do código é semelhante a este:

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

map.Add(42, "Hello");

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

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

Aqui está a definição:

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; }
}
Enigmatividade
fonte
2
@ Pedro77 - Agora sim. ;-)
Enigmatividade
2
@ Pedro77 - Só estava a ser atrevido ao sugerir que a minha aula era a nova solução de "mapa".
Enigmatividade
11
Isso não mantém invariantes de classe nas exceções. É possível _forward.Addter sucesso e _reverse.Addfalhar, deixando você com um par parcialmente adicionado.
5
@hvd - Como eu disse - é uma aula rápida.
Enigmatividade
3
@AaA Não está modificando a Forwardpropriedade do dicionário em si (que tem private set;), mas está modificando o valor naquele dicionário por meio da propriedade Indexer da classe Indexer que o passa para o dicionário. public T4 this[T3 index] { get { return _dictionary[index]; } set { _dictionary[index] = value; } }Isso está interrompendo a pesquisa direta / reversa.
Jeroen van Langen
27

Infelizmente, você precisa de dois dicionários, um para cada direção. No entanto, você pode obter facilmente o dicionário inverso usando LINQ:

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

Expandido no código Enigmativity, adicionando o método 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);
        }
    }
}

Aqui está um caso de uso, verifique os parênteses válidos

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
fonte
7

Você poderia usar dois dicionários, como outros já disseram, mas observe também que se ambos TKeye TValueforem do mesmo tipo (e seus domínios de valor de tempo de execução são conhecidos por serem separados), então você pode usar o mesmo dicionário criando duas entradas para cada chave / valor emparelhamento:

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

Desta forma, um único dicionário pode ser usado para qualquer tipo de pesquisa.

zmbq
fonte
3
Sim, essa abordagem foi reconhecida na pergunta :)
3
Isso ignora a possibilidade de o mesmo valor existir em "chaves" e "valores". então ele entrará em conflito nesta solução.
user1028741
1
@ user1028741 Concordo, embora a partir do exemplo pareça que eles queriam dizer "de um tipo diferente" e não "do mesmo tipo"
Hutch
6

Que diabos, vou colocar minha versão na mistura:

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

Adicione alguns dados a ele:

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 então faça a pesquisa:

lookup[2] --> 1, 6
lookup[3] --> 1
lookup[8] --> 6
Ostati
fonte
Gosto que este suporte 1: N
Sebastian
@Sebastian, você poderia adicionar IEnumerable <KeyValuePair <TKey, TValue >>.
Ostati de
4

Você pode usar esse método de extensão, embora use enumeração e, portanto, pode não ter o mesmo desempenho para grandes conjuntos de dados. Se você está preocupado com a eficiência, precisa de dois dicionários. Se você quiser agrupar os dois dicionários em uma classe, veja a resposta aceita para esta pergunta: Dicionário 1 para 1 bidirecional em 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
fonte
8
Embora este seja um dicionário bidirecional, buscar o valor é uma operação O (n) onde deveria ser uma operação O (1). Isso pode não ser importante para conjuntos de dados pequenos, mas pode causar problemas de desempenho ao trabalhar com conjuntos grandes. A melhor resposta para desempenho no espaço seria usar dois dicionários com dados invertidos.
Tom
@TomA concordo plenamente com Tom, a única instância em que você precisa de um verdadeiro dicionário bidirecional é quando você tem 100K, 1M + entradas, qualquer coisa menos digitalizando é efetivamente um NOOP.
Chris Marisic
Gosto desta solução para a minha situação (tamanhos pequenos de dicionário) porque ainda posso usar inicializadores de coleção. Mapeie <A, B> na resposta aceita, não acho que possa ser usado em inicializadores de coleção.
CVertex
@ChrisMarisic, isso parece uma coisa estranha de se proclamar. Se essa pesquisa fosse chamada em um loop fechado, eu aposto que você sentiria a dor mesmo com <500 entradas. Também depende do custo de um teste de comparação. Eu não acho que declarações abrangentes como o seu comentário sejam úteis.
Lee Campbell
@LeeCampbell minhas declarações radicais são baseadas na experiência na realidade real, como na realidade mensurável e perfilada. Se você quiser usar algum tipo complexo como uma chave para um dicionário, o problema é seu, não meu.
Chris Marisic
1

Bictionary

Aqui está uma mistura do que gostei em cada resposta. Ele implementa IEnumerablepara que possa usar o inicializador de coleção, como você pode ver no exemplo.

Restrição de uso:

  • Você está usando diferentes tipos de dados. (ou seja, )T1T2

Código:

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

toddmo
fonte
Solução muito elegante! Você poderia explicar a essência disso um pouco mais? Estou certo, que você não pode fazer um Bictionary<string, string>mesmo que todas as cordas sejam únicas?
Marcus Mangelsdorf
@ Merlin2001, correto. Mais precisamente, você não poderia fazer pesquisas futuras com isso. Terei que pensar em como superar isso. Ele compila, mas sempre encontra o indexador reverso primeiro quando T1 == T2, portanto, as pesquisas diretas falham. Além disso, não posso substituir o indexador padrão porque as chamadas de pesquisa seriam ambíguas. Eu adicionei esta restrição e removi a anterior, porque os valores de T1podem se sobrepor aos valores de T2.
toddmo,
10
Há um problema de desempenho bastante sério no reverso; o dicionário é pesquisado duas vezes, com um acerto de desempenho O (n); seria muito mais rápido usar um segundo dicionário e removeria a restrição de tipo.
Steve Cooper de
@SteveCooper, talvez eu pudesse me livrar do impacto no desempenho envolvendo-o em um trye convertendo exceções para KeyNotFoundExceptions.
toddmo de
4
@toddmo você pode dobrar a velocidade desta forma. O maior problema é que tanto .First quanto .Qualquer pesquisam um item por vez, testando cada um. Portanto, para testar uma lista de 1.000.000 de itens leva 1.000.000 de vezes mais tempo para pesquisar do que uma lista de 1 elemento. Os dicionários são muito mais rápidos e não ficam lentos à medida que você adiciona mais itens, portanto, um segundo dicionário inverso economiza tempo em listas grandes. Pode não ser relevante, mas é o tipo de coisa que pode ficar bem com pequenas quantidades de dados durante o teste e, em seguida, prejudicar o desempenho em um servidor real com dados sérios.
Steve Cooper de
1

Este é um problema antigo, mas eu queria adicionar dois métodos de extensão, caso alguém ache útil. O segundo não é tão útil, mas fornece um ponto de partida se um a um dicionários precisam ser suportados.

    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
fonte
1

Uma versão modificada da resposta de Xavier John, com um construtor adicional para avançar e reverter Comparadores. Isso daria suporte a chaves que não diferenciam maiúsculas de minúsculas, por exemplo. Outros construtores podem ser adicionados, se necessário, para passar mais argumentos para os construtores de dicionário direto e reverso.

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

Exemplo de uso, com uma chave que não diferencia maiúsculas de minúsculas:

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
fonte
1

Aqui está meu código. Tudo é O (1), exceto para os construtores semeados.

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
fonte
Gostaria de saber como o construtor se comportará se você passar um dicionário existente onde os tipos de chave e valor são iguais. Como isso resolverá se usará as versões anteriores ou posteriores?
Colm Bhandal
0

A seguinte classe de encapsulamento utiliza linq (IEnumerable Extensions) em 1 instância de dicionário.

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
fonte
0

Isso usa um indexador para a pesquisa reversa.
A pesquisa reversa é O (n), mas também não usa dois dicionários

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;
        }
    } 
}
paparazzo
fonte
Por exemplo: possível NRE em this[string Word]. Problemas adicionais são nomes de variáveis ​​que não correspondem às práticas comuns, comentários inconsistentes com o código ( UInt16vs UInt32- é por isso: não use comentários!), A solução não é genérica, ...
BartoszKP
0

Aqui está uma solução alternativa para as que foram sugeridas. Removeu a classe interna e garantiu a coerência ao adicionar / remover itens

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

sombra de sirpadura
fonte
0

Há um BijectionDictionarytipo disponível neste repositório de código aberto:

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

Não é qualitativamente muito diferente das outras respostas dadas. Ele usa dois dicionários, como a maioria dessas respostas.

O que é novo, eu acredito, sobre este dicionário versus as outras respostas até agora, é que em vez de se comportar como um dicionário bidirecional, ele apenas se comporta como um dicionário unilateral familiar e, então, permite dinamicamente que você vire o dicionário usando a propriedade Reverse. A referência do objeto invertido é superficial, portanto, ainda será capaz de modificar o mesmo objeto principal da referência original. Portanto, você pode ter duas referências ao mesmo objeto, exceto que uma delas é invertida.

Outra coisa que provavelmente é única sobre este dicionário é que existem alguns testes escritos para ele no projeto de teste sob esse repo. Tem sido usado por nós na prática e tem estado bastante estável até agora.

Colm Bhandal
fonte