Je souhaite stocker des mots dans un dictionnaire de la manière suivante:
Je peux obtenir un code mot par mot: dict["SomeWord"]
-> 123
et obtenir un code mot par mot: dict[123]
->"SomeWord"
Est-ce que c'est réel? Bien sûr, une façon de faire est de deux dictionnaires: Dictionary<string,int>
et Dictionary<int,string>
mais y a-t-il une autre façon?
c#
.net
dictionary
Neir0
la source
la source
Réponses:
J'ai écrit quelques cours rapides qui vous permettent de faire ce que vous voulez. Vous auriez probablement besoin de l'étendre avec plus de fonctionnalités, mais c'est un bon point de départ.
L'utilisation du code ressemble à ceci:
var map = new Map<int, string>(); map.Add(42, "Hello"); Console.WriteLine(map.Forward[42]); // Outputs "Hello" Console.WriteLine(map.Reverse["Hello"]); //Outputs 42
Voici la définition:
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; } }
la source
_forward.Add
de réussir et_reverse.Add
d'échouer, vous laissant avec une paire partiellement ajoutée.Forward
propriété de dictionnaire lui-même (qui possèdeprivate set;
), mais il modifie la valeur de ce dictionnaire via la propriété Indexer de la classe Indexer qui la transmet au dictionnaire.public T4 this[T3 index] { get { return _dictionary[index]; } set { _dictionary[index] = value; } }
C'est donc interrompre la recherche avant / arrière.Malheureusement, vous avez besoin de deux dictionnaires, un pour chaque direction. Cependant, vous pouvez facilement obtenir le dictionnaire inverse en utilisant LINQ:
Dictionary<T1, T2> dict = new Dictionary<T1, T2>(); Dictionary<T2, T1> dictInverse = dict.ToDictionary((i) => i.Value, (i) => i.Key);
la source
Développé sur le code Enigmativity en ajoutant la méthode initializes et 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); } } }
Voici un cas d'utilisation, vérifiez les parenthèses valides
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; } }
la source
Vous pouvez utiliser deux dictionnaires, comme d'autres l'ont dit, mais notez également que si les deux
TKey
etTValue
sont du même type (et que leurs domaines de valeur d'exécution sont connus pour être disjoints), vous pouvez simplement utiliser le même dictionnaire en créant deux entrées pour chaque clé / valeur appariement:dict["SomeWord"]= "123"
etdict["123"]="SomeWord"
De cette façon, un seul dictionnaire peut être utilisé pour l'un ou l'autre type de recherche.
la source
Bon sang, je vais jeter ma version dans le 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(); } }
Ajoutez-y des données:
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);
Et puis faites la recherche:
lookup[2] --> 1, 6 lookup[3] --> 1 lookup[8] --> 6
la source
Vous pouvez utiliser cette méthode d'extension, même si elle utilise l'énumération et peut donc ne pas être aussi performante pour les grands ensembles de données. Si vous êtes préoccupé par l'efficacité, vous avez besoin de deux dictionnaires. Si vous souhaitez regrouper les deux dictionnaires dans une seule classe, consultez la réponse acceptée pour cette question: Dictionnaire bidirectionnel 1 à 1 en 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"); } }
la source
Bictionary
Voici un mélange de ce que j'ai aimé dans chaque réponse. Il implémente
IEnumerable
pour pouvoir utiliser l'initialiseur de collection, comme vous pouvez le voir dans l'exemple.Contrainte d'utilisation:
T1
≠
T2
Code:
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; } } }
Violon:
https://dotnetfiddle.net/mTNEuw
la source
Bictionary<string, string>
même si toutes les chaînes sont uniques?T1 == T2
, donc les recherches directes échouent. De plus, je ne peux pas remplacer l'indexeur par défaut car les appels de recherche seraient ambigus. J'ai ajouté cette contrainte et supprimé la précédente, car les valeurs deT1
peuvent chevaucher les valeurs deT2
.try
et en convertissant les exceptions enKeyNotFoundExceptions
.C'est un vieux problème mais je voulais ajouter deux méthodes d'extension au cas où quelqu'un le trouverait utile. Le second n'est pas aussi utile, mais il fournit un point de départ si des dictionnaires un à un doivent être pris en charge.
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; }
la source
Une version modifiée de la réponse de Xavier John, avec un constructeur supplémentaire pour faire avancer et inverser les comparateurs. Cela prendrait en charge les clés insensibles à la casse, par exemple. D'autres constructeurs pourraient être ajoutés, si nécessaire, pour transmettre d'autres arguments aux constructeurs Dictionary avant et arrière.
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 ... }
Exemple d'utilisation, avec une clé insensible à la casse:
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 */
la source
Voici mon code. Tout est O (1) sauf pour les constructeurs prédéfinis.
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(); } }
la source
La classe d'encapsulation suivante utilise linq (IEnumerable Extensions) sur 1 instance de dictionnaire.
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"]; } }
la source
Cela utilise un indexeur pour la recherche inversée.
La recherche inversée est O (n) mais elle n'utilise pas non plus deux dictionnaires
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; } } }
la source
this[string Word]
. Les problèmes supplémentaires sont les noms de variables ne correspondant pas aux pratiques courantes, les commentaires incohérents avec le code (UInt16
vsUInt32
- c'est pourquoi: n'utilisez pas de commentaires!), La solution n'est pas générique, ...Voici une solution alternative à celles qui ont été suggérées. Suppression de la classe interne et assurance de la cohérence lors de l'ajout / suppression d'éléments
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(); } }
la source
Il existe un
BijectionDictionary
type disponible dans ce dépôt open source:https://github.com/ColmBhandal/CsharpExtras .
Ce n'est pas qualitativement très différent des autres réponses données. Il utilise deux dictionnaires, comme la plupart de ces réponses.
Ce qui est nouveau, je crois, à propos de ce dictionnaire par rapport aux autres réponses jusqu'à présent, c'est qu'au lieu de se comporter comme un dictionnaire bidirectionnel, il se comporte simplement comme un dictionnaire familier à sens unique et vous permet ensuite de retourner dynamiquement le dictionnaire en utilisant la propriété Reverse. La référence d'objet retournée est peu profonde, elle pourra donc toujours modifier le même objet principal que la référence d'origine. Vous pouvez donc avoir deux références au même objet, sauf que l'une d'elles est retournée.
Une autre chose qui est probablement unique à propos de ce dictionnaire est qu'il y a des tests écrits pour lui dans le projet de test sous ce référentiel. Il a été utilisé par nous en pratique et a été assez stable jusqu'à présent.
la source
Il existe une version étendue de la réponse d'Enigmativity disponible sous forme de package nuget https://www.nuget.org/packages/BidirectionalMap/
C'est open source ici
la source