Saya ingin menyimpan kata-kata dalam kamus dengan cara berikut:
Saya bisa mendapatkan kode kata demi kata: dict["SomeWord"]
-> 123
dan mendapatkan kode kata demi kata: dict[123]
->"SomeWord"
Apakah itu nyata Tentu saja satu cara untuk melakukannya adalah dengan dua kamus: Dictionary<string,int>
dan Dictionary<int,string>
apakah ada cara lain?
c#
.net
dictionary
Neir0
sumber
sumber
Jawaban:
Saya menulis beberapa kelas singkat yang memungkinkan Anda melakukan apa yang Anda inginkan. Anda mungkin perlu memperluasnya dengan lebih banyak fitur, tetapi ini adalah titik awal yang baik.
Penggunaan kode terlihat seperti ini:
var map = new Map<int, string>(); map.Add(42, "Hello"); Console.WriteLine(map.Forward[42]); // Outputs "Hello" Console.WriteLine(map.Reverse["Hello"]); //Outputs 42
Berikut definisinya:
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; } }
sumber
_forward.Add
berhasil, dan_reverse.Add
gagal, meninggalkan Anda dengan pasangan yang ditambahkan sebagian.Forward
properti kamus itu sendiri (yang dimilikinyaprivate set;
), tetapi memodifikasi nilai pada kamus itu melalui properti Pengindeks dari kelas Pengindeks yang meneruskannya ke kamus.public T4 this[T3 index] { get { return _dictionary[index]; } set { _dictionary[index] = value; } }
Jadi itu melanggar pencarian maju / mundur.Sayangnya, Anda membutuhkan dua kamus, satu untuk setiap arah. Namun, Anda bisa dengan mudah mendapatkan kamus terbalik menggunakan LINQ:
Dictionary<T1, T2> dict = new Dictionary<T1, T2>(); Dictionary<T2, T1> dictInverse = dict.ToDictionary((i) => i.Value, (i) => i.Key);
sumber
Memperluas kode Enigmativitas dengan menambahkan metode inisialisasi dan Berisi.
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); } } }
Ini adalah kasus penggunaan, periksa tanda kurung yang valid
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; } }
sumber
Anda dapat menggunakan dua kamus, seperti yang dikatakan orang lain, tetapi perhatikan juga bahwa jika keduanya
TKey
danTValue
berjenis sama (dan domain nilai waktu prosesnya diketahui saling terpisah) maka Anda dapat menggunakan kamus yang sama dengan membuat dua entri untuk setiap kunci / nilai pasangan:dict["SomeWord"]= "123"
dandict["123"]="SomeWord"
Dengan cara ini, satu kamus dapat digunakan untuk kedua jenis pencarian.
sumber
Apa-apaan ini, saya akan memasukkan versi saya ke dalam campuran:
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(); } }
Tambahkan beberapa data ke dalamnya:
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);
Lalu lakukan pencarian:
lookup[2] --> 1, 6 lookup[3] --> 1 lookup[8] --> 6
sumber
Anda dapat menggunakan metode ekstensi ini, meskipun menggunakan enumerasi, dan oleh karena itu mungkin tidak berfungsi untuk kumpulan data yang besar. Jika Anda khawatir tentang efisiensi, maka Anda memerlukan dua kamus. Jika Anda ingin menggabungkan dua kamus menjadi satu kelas, lihat jawaban yang diterima untuk pertanyaan ini: Kamus Dua Arah 1 ke 1 di 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"); } }
sumber
Bictionary
Berikut adalah percampuran apa yang saya suka di setiap jawaban. Itu mengimplementasikan
IEnumerable
sehingga dapat menggunakan penginisialisasi koleksi, seperti yang Anda lihat di contoh.Batasan Penggunaan:
T1
≠
T2
Kode:
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; } } }
Biola:
https://dotnetfiddle.net/mTNEuw
sumber
Bictionary<string, string>
meskipun semua string itu unik?T1 == T2
, sehingga pencarian maju gagal. Selain itu, saya tidak dapat mengganti pengindeks default karena panggilan pencarian akan menjadi ambigu. Saya telah menambahkan batasan ini dan menghapus yang sebelumnya, karena nilaiT1
dapat tumpang tindih dengan nilaiT2
.try
dan mengonversi pengecualian menjadiKeyNotFoundExceptions
.Ini adalah masalah lama tetapi saya ingin menambahkan dua metode ekstensi jika ada yang menganggapnya berguna. Yang kedua tidak begitu berguna tetapi memberikan titik awal jika kamus satu ke satu perlu didukung.
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; }
sumber
Versi modifikasi jawaban Xavier John, dengan konstruktor tambahan untuk maju dan mundur Pembanding. Ini akan mendukung kunci case-insensitive, misalnya. Konstruktor selanjutnya dapat ditambahkan, jika diperlukan, untuk meneruskan argumen lebih lanjut ke konstruktor Dictionary maju dan mundur.
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 ... }
Contoh penggunaan, dengan kunci case-insensitive:
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 */
sumber
Ini kode saya. Semuanya adalah O (1) kecuali untuk konstruktor unggulan.
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(); } }
sumber
Kelas encapsulating berikut menggunakan linq (IEnumerable Extensions) lebih dari 1 instance kamus.
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"]; } }
sumber
Ini menggunakan pengindeks untuk pencarian terbalik.
Pencarian terbalik adalah O (n) tetapi juga tidak menggunakan dua kamus
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; } } }
sumber
this[string Word]
. Masalah tambahan adalah nama variabel yang tidak sesuai dengan praktik umum, komentar tidak konsisten dengan kode (UInt16
vsUInt32
- itu sebabnya: jangan gunakan komentar!), Solusi tidak umum, ...Berikut solusi alternatif untuk yang disarankan. Menghapus kelas dalam dan memastikan koherensi saat menambahkan / menghapus item
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(); } }
sumber
Ada sebuah
BijectionDictionary
tipe yang tersedia di repo open source ini:https://github.com/ColmBhandal/CsharpExtras .
Secara kualitatif tidak jauh berbeda dengan jawaban lain yang diberikan. Ini menggunakan dua kamus, seperti kebanyakan jawaban itu.
Apa yang baru, saya yakin, tentang kamus ini vs. jawaban lain sejauh ini, adalah bahwa alih-alih berperilaku seperti kamus dua arah, ia hanya berperilaku seperti kamus satu arah yang sudah dikenal dan kemudian secara dinamis memungkinkan Anda membalik kamus menggunakan properti Reverse. Referensi objek yang dibalik bersifat dangkal, sehingga masih dapat memodifikasi objek inti yang sama dengan referensi aslinya. Jadi Anda bisa memiliki dua referensi ke objek yang sama, kecuali salah satunya dibalik.
Hal lain yang mungkin unik tentang kamus ini adalah bahwa ada beberapa tes yang ditulis untuk itu dalam proyek pengujian di bawah repo itu. Ini telah digunakan oleh kami dalam praktik dan sejauh ini cukup stabil.
sumber
Ada versi jawaban Enigmativity yang diperluas yang tersedia sebagai paket nuget https://www.nuget.org/packages/BidirectionalMap/
Ini bersumber terbuka di sini
sumber