Chcę przechowywać słowa w słowniku w następujący sposób:
Mogę uzyskać kod słowny po słowie: dict["SomeWord"]
-> 123
i 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?
c#
.net
dictionary
Neir0
źródło
źródło
Odpowiedzi:
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; } }
źródło
_forward.Add
sukces lub_reverse.Add
porażkę, pozostawiając częściowo dodaną parę.Forward
własnej właściwości słownika (która maprivate 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.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);
źródło
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; } }
źródło
Możesz użyć dwóch słowników, jak powiedzieli inni, ale pamiętaj również, że jeśli oba
TKey
iTValue
są 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"
idict["123"]="SomeWord"
W ten sposób pojedynczy słownik może być używany do każdego typu wyszukiwania.
źródło
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
źródło
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"); } }
źródło
Bictionary
Oto połączenie tego, co mi się podobało w każdej odpowiedzi. Implementuje się,
IEnumerable
więc może używać inicjatora kolekcji, jak widać w przykładzie.Ograniczenie użytkowania:
T1
≠
T2
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
źródło
Bictionary<string, string>
nawet jeśli wszystkie struny są niepowtarzalne?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ściT1
mogą pokrywać się z wartościamiT2
.try
i konwertując wyjątki naKeyNotFoundExceptions
.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; }
źródło
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 */
źródło
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(); } }
źródło
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"]; } }
źródło
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; } } }
źródło
this[string Word]
. Dodatkowymi problemami są nazwy zmiennych nieodpowiadające powszechnym praktykom, komentarz niezgodny z kodem (UInt16
vsUInt32
- dlatego: nie używaj komentarzy!), Rozwiązanie nie jest ogólne, ...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(); } }
źródło
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.
źródło
Istnieje rozszerzona wersja odpowiedzi Enigmativity dostępna jako pakiet nuget https://www.nuget.org/packages/BidirectionalMap/
Jest on otwarty pozyskiwane tutaj
źródło