Я хочу хранить слова в словаре следующим образом:
Я могу получить код за словом: dict["SomeWord"]
-> 123
и получить код за словом: dict[123]
->"SomeWord"
Это реально? Конечно же один из способов сделать это два словаря: Dictionary<string,int>
и , Dictionary<int,string>
но есть другой способ?
c#
.net
dictionary
Neir0
источник
источник
Ответы:
Я написал пару коротких уроков, которые позволят вам делать то, что вы хотите. Возможно, вам потребуется расширить его дополнительными функциями, но это хорошая отправная точка.
Использование кода выглядит так:
var map = new Map<int, string>(); map.Add(42, "Hello"); Console.WriteLine(map.Forward[42]); // Outputs "Hello" Console.WriteLine(map.Reverse["Hello"]); //Outputs 42
Вот определение:
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; } }
источник
_forward.Add
добиться успеха или_reverse.Add
потерпеть неудачу, оставив вам частично добавленную пару.Forward
собственное свойство словаря (которое имеетprivate set;
), но изменяет значение в этом словаре через свойство Indexer класса Indexer, которое передает его в словарь.public T4 this[T3 index] { get { return _dictionary[index]; } set { _dictionary[index] = value; } }
Итак, это нарушает прямой / обратный поиск.К сожалению, вам понадобится два словаря, по одному на каждое направление. Однако вы можете легко получить обратный словарь с помощью LINQ:
Dictionary<T1, T2> dict = new Dictionary<T1, T2>(); Dictionary<T2, T1> dictInverse = dict.ToDictionary((i) => i.Value, (i) => i.Key);
источник
Расширен код Enigmativity, добавив методы инициализации и 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); } } }
Вот пример использования, проверьте правильность скобок
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; } }
источник
Вы можете использовать два словаря, как говорили другие, но также обратите внимание, что если оба
TKey
иTValue
имеют один и тот же тип (и известно, что их домены значений времени выполнения не пересекаются), вы можете просто использовать один и тот же словарь, создав две записи для каждого ключа / пара значений:dict["SomeWord"]= "123"
а такжеdict["123"]="SomeWord"
Таким образом, для любого типа поиска можно использовать один словарь.
источник
Какого черта, я добавлю свою версию в микс:
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(); } }
Добавьте к нему данные:
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);
А затем выполните поиск:
lookup[2] --> 1, 6 lookup[3] --> 1 lookup[8] --> 6
источник
Вы можете использовать этот метод расширения, хотя он использует перечисление и, следовательно, может быть не таким производительным для больших наборов данных. Если вас беспокоит эффективность, то вам понадобятся два словаря. Если вы хотите объединить два словаря в один класс, см. Принятый ответ на этот вопрос: Двунаправленный словарь 1 к 1 в 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"); } }
источник
Словарь
Вот что мне понравилось в каждом ответе. Он реализует
IEnumerable
так, чтобы он мог использовать инициализатор коллекции, как вы можете видеть в примере.Ограничение использования:
T1
≠
T2
Код:
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; } } }
Скрипка:
https://dotnetfiddle.net/mTNEuw
источник
Bictionary<string, string>
даже если все строки уникальны?T1 == T2
, поэтому прямой поиск не выполняется. Кроме того, я не могу переопределить индексатор по умолчанию, потому что тогда вызовы поиска будут неоднозначными. Я добавил это ограничение и удалил предыдущее, потому что значенияT1
могут перекрываться со значениямиT2
.try
и преобразовав исключения вKeyNotFoundExceptions
.Это старая проблема, но я хотел добавить два метода расширения на случай, если кто-то сочтет это полезным. Второй вариант не так полезен, но он дает отправную точку, если требуется поддержка индивидуальных словарей.
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; }
источник
Модифицированная версия ответа Ксавьера Джона с дополнительным конструктором для прямого и обратного сравнения. Например, это будет поддерживать ключи без учета регистра. При необходимости можно добавить другие конструкторы для передачи дополнительных аргументов конструкторам прямого и обратного словаря.
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 ... }
Пример использования с ключом без учета регистра:
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 */
источник
Вот мой код. Все равно O (1), кроме засеянных конструкторов.
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(); } }
источник
Следующий инкапсулирующий класс использует linq (IEnumerable Extensions) для 1 экземпляра словаря.
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"]; } }
источник
Это использует индексатор для обратного поиска.
Обратный поиск - O (n), но он также не использует два словаря.
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; } } }
источник
this[string Word]
. Дополнительные проблемы - это имена переменных, не соответствующие общепринятой практике, комментарии, несовместимые с кодом (UInt16
vsUInt32
- вот почему: не используйте комментарии!), Решение не является общим, ...Вот альтернативное решение тем, что были предложены. Удален внутренний класс и обеспечена согласованность при добавлении / удалении элементов.
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(); } }
источник
В
BijectionDictionary
этом репозитории с открытым исходным кодом доступен тип:https://github.com/ColmBhandal/CsharpExtras .
Качественно он не сильно отличается от других приведенных ответов. Он использует два словаря, как и большинство этих ответов.
Я считаю, что новизна этого словаря по сравнению с другими ответами до сих пор заключается в том, что вместо того, чтобы вести себя как двусторонний словарь, он просто ведет себя как односторонний знакомый словарь, а затем динамически позволяет вам переворачивать словарь, используя Обратное свойство. Ссылка на перевернутый объект неглубокая, поэтому она все равно сможет изменить тот же основной объект, что и исходная ссылка. Таким образом, у вас может быть две ссылки на один и тот же объект, за исключением того, что одна из них перевернута.
Еще одна вещь, которая, вероятно, уникальна в этом словаре, заключается в том, что для него написано несколько тестов в тестовом проекте в этом репо. Он использовался нами на практике и пока работает довольно стабильно.
источник
Существует расширенная версия ответа Enigmativity, доступная в виде пакета nuget https://www.nuget.org/packages/BidirectionalMap/
Он открыт найденный здесь
источник