Dwukierunkowy / dwukierunkowy słownik w C #?


90

Chcę przechowywać słowa w słowniku w następujący sposób:

Mogę uzyskać kod słowny po słowie: dict["SomeWord"]-> 123i uzyskać kod słowo po słowie: dict[123]->"SomeWord"

Czy jest to prawdziwe? Oczywiście jeden sposób, aby zrobić to dwa słowniki: Dictionary<string,int>a Dictionary<int,string>jednak jest jakiś inny sposób?


2
Nie ma standardowego typu danych (od .NET 4), który zapewnia dostęp O (1) w obie strony ... AFAIK :)

Nie chodzi też o to, że mapa dwukierunkowa (słowo kluczowe?) Narzuca dodatkowe ograniczenia, chyba że mapa dwukierunkowa ...

Odpowiedzi:


110

Napisałem kilka krótkich zajęć, dzięki którym możesz robić, co chcesz. Prawdopodobnie będziesz musiał go rozszerzyć o więcej funkcji, ale jest to dobry punkt wyjścia.

Sposób użycia kodu wygląda następująco:

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

map.Add(42, "Hello");

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

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

Oto definicja:

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 - Teraz tak. ;-)
Enigmativity

2
@ Pedro77 - Byłem bezczelny, sugerując, że moja klasa była nowym rozwiązaniem „mapowym”.
Enigmativity

12
Nie utrzymuje to niezmienników klas w wyjątkach. Możliwe, że odniesiesz _forward.Addsukces lub _reverse.Addporażkę, pozostawiając częściowo dodaną parę.

5
@hvd - Jak powiedziałem - to szybko zestawione zajęcia.
Enigmativity

3
@AaA Nie modyfikuje Forwardwłasnej właściwości słownika (która ma private set;), ale modyfikuje wartość w tym słowniku za pośrednictwem właściwości Indexer klasy Indexer, która przekazuje ją do słownika. public T4 this[T3 index] { get { return _dictionary[index]; } set { _dictionary[index] = value; } }To jest zerwanie wyszukiwania do przodu / wstecz.
Jeroen van Langen

27

Niestety potrzebujesz dwóch słowników, po jednym dla każdego kierunku. Możesz jednak łatwo uzyskać słownik odwrotny za pomocą LINQ:

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

11

Rozszerzony w kodzie Enigmativity, dodając metodę initializes i 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);
        }
    }
}

Oto przypadek użycia, sprawdź prawidłowe nawiasy

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

Możesz użyć dwóch słowników, jak powiedzieli inni, ale pamiętaj również, że jeśli oba TKeyi TValuesą tego samego typu (a ich domeny wartości w czasie wykonywania są rozłączne), możesz po prostu użyć tego samego słownika, tworząc dwa wpisy dla każdego klucza / parowanie wartości:

dict["SomeWord"]= "123" i dict["123"]="SomeWord"

W ten sposób pojedynczy słownik może być używany do każdego typu wyszukiwania.


3
Tak, takie podejście zostało potwierdzone w pytaniu :)

3
To ignoruje możliwość istnienia tej samej wartości zarówno w „kluczach”, jak i „wartościach”. wtedy będzie to sprzeczne z tym rozwiązaniem.
user1028741

1
@ user1028741 Zgoda, chociaż z przykładu wynika, że ​​mieli na myśli „innego typu” nie ”tego samego typu”
Hutch

Może to skutkować nieoczekiwanymi wynikami w przyszłości, a następnie kod przechodzi przez refaktoryzację. Np. Wtedy lewa i prawa strona zaczynają się pokrywać. Nie dodaje prawie nic w wydajności.
Vinigas

6

Co do cholery, wrzucę moją wersję do miksu:

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

Dodaj do niego trochę danych:

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

A następnie wyszukaj:

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

Podoba mi się, że to obsługuje 1: N
Sebastian,

@Sebastian, możesz dodać IEnumerable <KeyValuePair <TKey, TValue >>.
Ostati

4

Możesz użyć tej metody rozszerzenia, chociaż używa ona wyliczenia, a zatem może nie być tak wydajna dla dużych zestawów danych. Jeśli martwisz się o wydajność, potrzebujesz dwóch słowników. Jeśli chcesz zawinąć dwa słowniki w jedną klasę, zapoznaj się z zaakceptowaną odpowiedzią na to pytanie: Słownik dwukierunkowy 1 do 1 w języku 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
Chociaż jest to słownik dwukierunkowy, pobieranie wartości jest operacją O (n), w której powinno być operacją O (1). Może to nie mieć znaczenia w przypadku małych zestawów danych, ale może powodować problemy z wydajnością podczas pracy z dużymi. Najlepszą odpowiedzią na wydajność w przestrzeni byłoby użycie dwóch słowników z odwróconymi danymi.
Tom

@TomA W pełni zgadzam się z Tomem, jedyny przypadek, w którym potrzebujesz prawdziwego słownika dwukierunkowego, to sytuacja, w której masz 100 000, 1 mln + wpisów, cokolwiek mniej skanuje, jest to w rzeczywistości NOOP.
Chris Marisic,

Podoba mi się to rozwiązanie w mojej sytuacji (małe rozmiary dyktowania), ponieważ nadal mogę używać inicjatorów kolekcji. Mapa <A, B> w zaakceptowanej odpowiedzi Myślę, że nie może być używana w inicjatorach kolekcji.
CVertex,

@ChrisMarisic, to brzmi dziwnie. Gdyby to wyszukiwanie zostało wywołane w ciasnej pętli, założę się, że czułbyś ból nawet przy <500 wpisach. Zależy to również od kosztu testów porównawczych. Nie wydaje mi się, by tak obszerne stwierdzenia, jak twój komentarz, były pomocne.
Lee Campbell,

@LeeCampbell moje ogólne wypowiedzi są oparte na doświadczeniach z rzeczywistej rzeczywistości, tak jak w rzeczywistości mierzalnej i sprofilowanej. Jeśli chcesz użyć jakiegoś złożonego typu jako klucza do słownika, to twój problem, a nie mój problem.
Chris Marisic,

1

Bictionary

Oto połączenie tego, co mi się podobało w każdej odpowiedzi. Implementuje się, IEnumerablewięc może używać inicjatora kolekcji, jak widać w przykładzie.

Ograniczenie użytkowania:

  • Używasz różnych typów danych. (tj. )T1T2

Kod:

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

Skrzypce:

https://dotnetfiddle.net/mTNEuw


Bardzo eleganckie rozwiązanie! Czy mógłbyś wyjaśnić nieco głębiej? Czy mam rację, że nie możesz zrobić Bictionary<string, string>nawet jeśli wszystkie struny są niepowtarzalne?
Marcus Mangelsdorf

@ Merlin2001, Zgadza się. Dokładniej, nie można z tym wyszukiwać do przodu. Muszę się zastanowić, jak to przezwyciężyć. Kompiluje, ale zawsze znajduje najpierw indeksator wsteczny T1 == T2, więc wyszukiwanie do przodu kończy się niepowodzeniem. Ponadto nie mogę zastąpić domyślnego indeksatora, ponieważ wtedy wywołania wyszukiwania byłyby niejednoznaczne. Dodałem to ograniczenie i usunąłem poprzednie, ponieważ wartości T1mogą pokrywać się z wartościami T2.
toddmo

10
Na odwrocie jest dość poważny problem z wydajnością; słownik jest przeszukiwany dwukrotnie, z wynikiem O (n); znacznie szybciej byłoby użyć drugiego słownika i usunąć ograniczenie typu.
Steve Cooper

@SteveCooper, może mógłbym pozbyć się pogorszenia wydajności, opakowując go w a tryi konwertując wyjątki na KeyNotFoundExceptions.
toddmo

4
@toddmo możesz w ten sposób podwoić prędkość. Większy problem polega na tym, że zarówno .First, jak i .Any wyszukują jeden element na raz, testując każdy z nich. Tak więc, aby przetestować listę 1 000 000 pozycji, wyszukiwanie trwa 1 000 000 razy dłużej niż w przypadku listy 1-elementowej. Słowniki są znacznie szybsze i nie zwalniają w miarę dodawania kolejnych pozycji, więc drugi słownik odwrotny pozwoli zaoszczędzić ogromną ilość czasu na dużych listach. Może to nie mieć znaczenia, ale jest to coś, co może być dobre w przypadku niewielkich ilości danych podczas testowania, a następnie morduje wydajność na prawdziwym serwerze z poważnymi danymi.
Steve Cooper

1

To stary problem, ale chciałem dodać dwie metody rozszerzające na wypadek, gdyby ktoś uznało je za przydatne. Drugi nie jest tak przydatny, ale stanowi punkt wyjścia, jeśli trzeba obsługiwać słowniki jeden do jednego.

    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

Zmodyfikowana wersja odpowiedzi Xaviera Johna z dodatkowym konstruktorem do wykonywania porównujących w przód i w tył. Obsługuje to na przykład klucze bez rozróżniania wielkości liter. W razie potrzeby można dodać dalsze konstruktory, aby przekazać dalsze argumenty do konstruktorów Dictionary do przodu i do tyłu.

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

Przykład użycia z kluczem bez rozróżniania wielkości liter:

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

Oto mój kod. Wszystko jest O (1) z wyjątkiem początkowych konstruktorów.

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

Zastanawiam się, jak zachowa się konstruktor, jeśli przekażesz mu istniejący słownik, w którym typy kluczy i wartości są takie same. Jak rozstrzygnie się, czy użyć odwrotnych, czy przyszłych?
Colm Bhandal

0

Następująca klasa hermetyzująca używa linq (IEnumerable Extensions) w 1 wystąpieniu słownika.

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

Używa indeksatora do wyszukiwania wstecznego.
Wyszukiwanie wsteczne to O (n), ale również nie używa dwóch słowników

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

Na przykład: możliwy NRE w this[string Word]. Dodatkowymi problemami są nazwy zmiennych nieodpowiadające powszechnym praktykom, komentarz niezgodny z kodem ( UInt16vs UInt32- dlatego: nie używaj komentarzy!), Rozwiązanie nie jest ogólne, ...
BartoszKP

0

Oto alternatywne rozwiązanie do tych, które zostały zasugerowane. Usunięto klasę wewnętrzną i zapewniono spójność podczas dodawania / usuwania przedmiotów

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

Tam jest BijectionDictionary tym repozytorium open source dostępny typ:

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

Jakościowo nie różni się zbytnio od innych udzielonych odpowiedzi. Używa dwóch słowników, jak większość odpowiedzi.

Uważam, że nowość w tym słowniku w porównaniu z innymi dotychczasowymi odpowiedziami polega na tym, że zamiast zachowywać się jak słownik dwukierunkowy, zachowuje się on po prostu jak jednokierunkowy, znajomy słownik, a następnie dynamicznie umożliwia przerzucanie słownika za pomocą właściwość Reverse. Odwrócone odniesienie do obiektu jest płytkie, więc nadal będzie można modyfikować ten sam główny obiekt, co oryginalne odniesienie. Możesz więc mieć dwa odniesienia do tego samego obiektu, z wyjątkiem tego, że jedno z nich jest odwrócone.

Inną prawdopodobnie unikalną rzeczą w tym słowniku jest to, że w projekcie testowym w ramach tego repozytorium napisano dla niego kilka testów. Jest używany przez nas w praktyce i do tej pory był dość stabilny.


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.