Getting multiple keys of specified value of a generic Dictionary?
从.NET通用字典中很容易获得键的值:
1 2 3 4 | Dictionary<int, string> greek = new Dictionary<int, string>(); greek.Add(1,"Alpha"); greek.Add(2,"Beta"); string secondGreek = greek[2]; // Beta |
但是尝试获得给定值的键并不是那么简单,因为可能有多个键:
1 | int[] betaKeys = greek.WhatDoIPutHere("Beta"); // expecting single 2 |
好的,这里有多个双向版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | using System; using System.Collections.Generic; using System.Text; class BiDictionary<TFirst, TSecond> { IDictionary<TFirst, IList<TSecond>> firstToSecond = new Dictionary<TFirst, IList<TSecond>>(); IDictionary<TSecond, IList<TFirst>> secondToFirst = new Dictionary<TSecond, IList<TFirst>>(); private static IList<TFirst> EmptyFirstList = new TFirst[0]; private static IList<TSecond> EmptySecondList = new TSecond[0]; public void Add(TFirst first, TSecond second) { IList<TFirst> firsts; IList<TSecond> seconds; if (!firstToSecond.TryGetValue(first, out seconds)) { seconds = new List<TSecond>(); firstToSecond[first] = seconds; } if (!secondToFirst.TryGetValue(second, out firsts)) { firsts = new List<TFirst>(); secondToFirst[second] = firsts; } seconds.Add(second); firsts.Add(first); } // Note potential ambiguity using indexers (e.g. mapping from int to int) // Hence the methods as well... public IList<TSecond> this[TFirst first] { get { return GetByFirst(first); } } public IList<TFirst> this[TSecond second] { get { return GetBySecond(second); } } public IList<TSecond> GetByFirst(TFirst first) { IList<TSecond> list; if (!firstToSecond.TryGetValue(first, out list)) { return EmptySecondList; } return new List<TSecond>(list); // Create a copy for sanity } public IList<TFirst> GetBySecond(TSecond second) { IList<TFirst> list; if (!secondToFirst.TryGetValue(second, out list)) { return EmptyFirstList; } return new List<TFirst>(list); // Create a copy for sanity } } class Test { static void Main() { BiDictionary<int, string> greek = new BiDictionary<int, string>(); greek.Add(1,"Alpha"); greek.Add(2,"Beta"); greek.Add(5,"Beta"); ShowEntries(greek,"Alpha"); ShowEntries(greek,"Beta"); ShowEntries(greek,"Gamma"); } static void ShowEntries(BiDictionary<int, string> dict, string key) { IList<int> values = dict[key]; StringBuilder builder = new StringBuilder(); foreach (int value in values) { if (builder.Length != 0) { builder.Append(","); } builder.Append(value); } Console.WriteLine("{0}: [{1}]", key, builder); } } |
正如其他人所说,字典中没有从值到键的映射。
我刚刚注意到您想要从值映射到多个键-我将把这个解决方案留给单值版本,但是我将为多条目双向映射添加另一个答案。
这里通常采用的方法是有两个字典——一个是单向映射,另一个是双向映射。将它们封装在一个单独的类中,并计算出当您有重复的键或值时要做什么(例如抛出异常、覆盖现有条目或忽略新条目)。就我个人而言,我可能会提出一个例外——它使成功行为更容易定义。像这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | using System; using System.Collections.Generic; class BiDictionary<TFirst, TSecond> { IDictionary<TFirst, TSecond> firstToSecond = new Dictionary<TFirst, TSecond>(); IDictionary<TSecond, TFirst> secondToFirst = new Dictionary<TSecond, TFirst>(); public void Add(TFirst first, TSecond second) { if (firstToSecond.ContainsKey(first) || secondToFirst.ContainsKey(second)) { throw new ArgumentException("Duplicate first or second"); } firstToSecond.Add(first, second); secondToFirst.Add(second, first); } public bool TryGetByFirst(TFirst first, out TSecond second) { return firstToSecond.TryGetValue(first, out second); } public bool TryGetBySecond(TSecond second, out TFirst first) { return secondToFirst.TryGetValue(second, out first); } } class Test { static void Main() { BiDictionary<int, string> greek = new BiDictionary<int, string>(); greek.Add(1,"Alpha"); greek.Add(2,"Beta"); int x; greek.TryGetBySecond("Beta", out x); Console.WriteLine(x); } } |
字典实际上并不是这样工作的,因为虽然保证了键的唯一性,但值的唯一性却不是这样的。例如,如果
1 |
你希望得到什么样的东西?
因此,您不能期望像这样的东西进入框架。您需要自己的方法来实现自己的独特用途——您想返回一个数组(或
就我个人而言,我会选择一个可枚举的,比如:
1 2 3 4 5 6 7 8 9 10 11 | IEnumerable<TKey> KeysFromValue<TKey, TValue>(this Dictionary<TKey, TValue> dict, TValue val) { if (dict == null) { throw new ArgumentNullException("dict"); } return dict.Keys.Where(k => dict[k] == val); } var keys = greek.KeysFromValue("Beta"); int exceptionIfNotExactlyOne = greek.KeysFromValue("Beta").Single(); |
如果没有LINQ,最简单的方法可能是循环访问这些对:
1 2 3 4 5 6 7 8 9 10 | int betaKey; foreach (KeyValuePair<int, string> pair in lookup) { if (pair.Value == value) { betaKey = pair.Key; // Found break; } } betaKey = -1; // Not found |
如果你有Linq,它可以很容易做到:
1 | int betaKey = greek.SingleOrDefault(x => x.Value =="Beta").Key; |
因为我想要一个完整的双向字典(不仅仅是一个映射),我添加了缺失的函数,使其成为一个IDictionary兼容类。这是基于具有唯一键值对的版本。如果需要,这里是文件(大多数工作是通过xmldoc完成的):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Common { /// <summary>Represents a bidirectional collection of keys and values.</summary> /// <typeparam name="TFirst">The type of the keys in the dictionary</typeparam> /// <typeparam name="TSecond">The type of the values in the dictionary</typeparam> [System.Runtime.InteropServices.ComVisible(false)] [System.Diagnostics.DebuggerDisplay("Count = {Count}")] //[System.Diagnostics.DebuggerTypeProxy(typeof(System.Collections.Generic.Mscorlib_DictionaryDebugView<,>))] //[System.Reflection.DefaultMember("Item")] public class BiDictionary<TFirst, TSecond> : Dictionary<TFirst, TSecond> { IDictionary<TSecond, TFirst> _ValueKey = new Dictionary<TSecond, TFirst>(); /// <summary> PropertyAccessor for Iterator over KeyValue-Relation </summary> public IDictionary<TFirst, TSecond> KeyValue => this; /// <summary> PropertyAccessor for Iterator over ValueKey-Relation </summary> public IDictionary<TSecond, TFirst> ValueKey => _ValueKey; #region Implemented members /// <Summary>Gets or sets the value associated with the specified key.</Summary> /// <param name="key">The key of the value to get or set.</param> /// <Returns>The value associated with the specified key. If the specified key is not found, /// a get operation throws a <see cref="KeyNotFoundException"/>, and /// a set operation creates a new element with the specified key.</Returns> /// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is null.</exception> /// <exception cref="T:System.Collections.Generic.KeyNotFoundException"> /// The property is retrieved and <paramref name="key"/> does not exist in the collection.</exception> /// <exception cref="T:System.ArgumentException"> An element with the same key already /// exists in the <see cref="ValueKey"/> <see cref="Dictionary<TFirst,TSecond>"/>.</exception> public new TSecond this[TFirst key] { get { return base[key]; } set { _ValueKey.Remove(base[key]); base[key] = value; _ValueKey.Add(value, key); } } /// <Summary>Gets or sets the key associated with the specified value.</Summary> /// <param name="val">The value of the key to get or set.</param> /// <Returns>The key associated with the specified value. If the specified value is not found, /// a get operation throws a <see cref="KeyNotFoundException"/>, and /// a set operation creates a new element with the specified value.</Returns> /// <exception cref="T:System.ArgumentNullException"><paramref name="val"/> is null.</exception> /// <exception cref="T:System.Collections.Generic.KeyNotFoundException"> /// The property is retrieved and <paramref name="val"/> does not exist in the collection.</exception> /// <exception cref="T:System.ArgumentException"> An element with the same value already /// exists in the <see cref="KeyValue"/> <see cref="Dictionary<TFirst,TSecond>"/>.</exception> public TFirst this[TSecond val] { get { return _ValueKey[val]; } set { base.Remove(_ValueKey[val]); _ValueKey[val] = value; base.Add(value, val); } } /// <Summary>Adds the specified key and value to the dictionary.</Summary> /// <param name="key">The key of the element to add.</param> /// <param name="value">The value of the element to add.</param> /// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> or <paramref name="value"/> is null.</exception> /// <exception cref="T:System.ArgumentException">An element with the same key or value already exists in the <see cref="Dictionary<TFirst,TSecond>"/>.</exception> public new void Add(TFirst key, TSecond value) { base.Add(key, value); _ValueKey.Add(value, key); } /// <Summary>Removes all keys and values from the <see cref="Dictionary<TFirst,TSecond>"/>.</Summary> public new void Clear() { base.Clear(); _ValueKey.Clear(); } /// <Summary>Determines whether the <see cref="Dictionary<TFirst,TSecond>"/> contains the specified /// KeyValuePair.</Summary> /// <param name="item">The KeyValuePair to locate in the <see cref="Dictionary<TFirst,TSecond>"/>.</param> /// <Returns>true if the <see cref="Dictionary<TFirst,TSecond>"/> contains an element with /// the specified key which links to the specified value; otherwise, false.</Returns> /// <exception cref="T:System.ArgumentNullException"><paramref name="item"/> is null.</exception> public bool Contains(KeyValuePair<TFirst, TSecond> item) => base.ContainsKey(item.Key) & _ValueKey.ContainsKey(item.Value); /// <Summary>Removes the specified KeyValuePair from the <see cref="Dictionary<TFirst,TSecond>"/>.</Summary> /// <param name="item">The KeyValuePair to remove.</param> /// <Returns>true if the KeyValuePair is successfully found and removed; otherwise, false. This /// method returns false if <paramref name="item"/> is not found in the <see cref="Dictionary<TFirst,TSecond>"/>.</Returns> /// <exception cref="T:System.ArgumentNullException"><paramref name="item"/> is null.</exception> public bool Remove(KeyValuePair<TFirst, TSecond> item) => base.Remove(item.Key) & _ValueKey.Remove(item.Value); /// <Summary>Removes the value with the specified key from the <see cref="Dictionary<TFirst,TSecond>"/>.</Summary> /// <param name="key">The key of the element to remove.</param> /// <Returns>true if the element is successfully found and removed; otherwise, false. This /// method returns false if <paramref name="key"/> is not found in the <see cref="Dictionary<TFirst,TSecond>"/>.</Returns> /// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is null.</exception> public new bool Remove(TFirst key) => _ValueKey.Remove(base[key]) & base.Remove(key); /// <Summary>Gets the key associated with the specified value.</Summary> /// <param name="value">The value of the key to get.</param> /// <param name="key">When this method returns, contains the key associated with the specified value, /// if the value is found; otherwise, the default value for the type of the key parameter. /// This parameter is passed uninitialized.</param> /// <Returns>true if <see cref="ValueKey"/> contains an element with the specified value; /// otherwise, false.</Returns> /// <exception cref="T:System.ArgumentNullException"><paramref name="value"/> is null.</exception> public bool TryGetValue(TSecond value, out TFirst key) => _ValueKey.TryGetValue(value, out key); #endregion } } |
字典不保留值的散列,只保留键,因此任何使用值的搜索都至少需要线性时间。最好的办法是简单地迭代字典中的元素,跟踪匹配的键或者切换到不同的数据结构,或者维护两个字典映射键->值和值->键列表。如果您选择后者,您将用存储交换查找速度。将@cybis示例转换成这样的数据结构不需要太多。
字典类没有针对这种情况进行优化,但是如果您真的想这样做(在C 2.0中),您可以这样做:
1 2 3 4 5 6 7 8 9 | public List<TKey> GetKeysFromValue<TKey, TVal>(Dictionary<TKey, TVal> dict, TVal val) { List<TKey> ks = new List<TKey>(); foreach(TKey k in dict.Keys) { if (dict[k] == val) { ks.Add(k); } } return ks; } |
我更喜欢优雅的LINQ解决方案,但这是2.0方式。
修订版:好吧,要想找到某种东西,你需要字典以外的东西,因为如果你考虑一下,字典是单向键。也就是说,值可能不是唯一的
也就是说,看起来您使用的是C 3.0,因此您可能不必使用循环,可以使用以下类似的方法:
1 | var key = (from k in yourDictionary where string.Compare(k.Value,"yourValue", true) == 0 select k.Key).FirstOrDefault(); |
使用LINQ进行反向
论证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | using System; using System.Collections.Generic; using System.Linq; class ReverseDictionaryLookupDemo { static void Main() { var dict = new Dictionary<int, string>(); dict.Add(4,"Four"); dict.Add(5,"Five"); dict.Add(1,"One"); dict.Add(11,"One"); // duplicate! dict.Add(3,"Three"); dict.Add(2,"Two"); dict.Add(44,"Four"); // duplicate! Console.WriteLine(" == Enumerating Distinct Values =="); foreach (string value in dict.Values.Distinct()) { string valueString = String.Join(",", GetKeysFromValue(dict, value)); Console.WriteLine("{0} => [{1}]", value, valueString); } } static List<int> GetKeysFromValue(Dictionary<int, string> dict, string value) { // Use LINQ to do a reverse dictionary lookup. // Returns a 'List<T>' to account for the possibility // of duplicate values. return (from item in dict where item.Value.Equals(value) select item.Key).ToList(); } } |
预期输出:
1 2 3 4 5 6 | == Enumerating Distinct Values == Four => [4, 44] Five => [5] One => [1, 11] Three => [3] Two => [2] |
这里提出的"简单"双向字典解决方案很复杂,可能难以理解、维护或扩展。同样,最初的问题要求"一个值的键",但显然可能有多个键(我已经编辑了这个问题)。整个方法相当可疑。
软件变更。编写易于维护的代码应该优先考虑其他"聪明"的复杂解决方法。从字典中的值中获取键的方法是循环。字典不是双向的。
1 2 3 4 5 6 7 8 9 10 11 | Dictionary<string, string> dic = new Dictionary<string, string>(); dic["A"] ="Ahmed"; dic["B"] ="Boys"; foreach (string mk in dic.Keys) { if(dic[mk] =="Ahmed") { Console.WriteLine("The key that contains "Ahmed" is" + mk); } } |
你不能创建一个字典的子类吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <wyn> public class MyDict < TKey, TValue > : Dictionary < TKey, TValue > { private Dictionary < TValue, TKey > _keys; public TValue this[TKey key] { get { return base[key]; } set { base[key] = value; _keys[value] = key; } } public MyDict() { _keys = new Dictionary < TValue, TKey >(); } public TKey GetKeyFromValue(TValue value) { return _keys[value]; } } </wyn> |
编辑:抱歉,第一次没有正确获得代码。
那么外行的解决方案
可以编写类似于以下函数的函数来生成这样的字典:
1 2 3 | public Dictionary<TValue, TKey> Invert(Dictionary<TKey, TValue> dict) { Dictionary<TValue, TKey> ret = new Dictionary<TValue, TKey>(); foreach (var kvp in dict) {ret[kvp.value] = kvp.key;} return ret; } |
作为公认答案(https://stackoverflow.com/a/255638/986160)的一个转折点,假设这些键将与字典中的符号值相关联。类似于(https://stackoverflow.com/a/255630/986160),但有点优雅。其新颖之处在于消费类可以用作枚举选项(但也用于字符串),并且字典实现了IEnumerable。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Collections; namespace MyApp.Dictionaries { class BiDictionary<TFirst, TSecond> : IEnumerable { IDictionary<TFirst, TSecond> firstToSecond = new Dictionary<TFirst, TSecond>(); IDictionary<TSecond, TFirst> secondToFirst = new Dictionary<TSecond, TFirst>(); public void Add(TFirst first, TSecond second) { firstToSecond.Add(first, second); secondToFirst.Add(second, first); } public TSecond this[TFirst first] { get { return GetByFirst(first); } } public TFirst this[TSecond second] { get { return GetBySecond(second); } } public TSecond GetByFirst(TFirst first) { return firstToSecond[first]; } public TFirst GetBySecond(TSecond second) { return secondToFirst[second]; } public IEnumerator GetEnumerator() { return GetFirstEnumerator(); } public IEnumerator GetFirstEnumerator() { return firstToSecond.GetEnumerator(); } public IEnumerator GetSecondEnumerator() { return secondToFirst.GetEnumerator(); } } } |
作为一个消费阶层,你可以
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace MyApp.Dictionaries { class Greek { public static readonly string Alpha ="Alpha"; public static readonly string Beta ="Beta"; public static readonly string Gamma ="Gamma"; public static readonly string Delta ="Delta"; private static readonly BiDictionary<int, string> Dictionary = new BiDictionary<int, string>(); static Greek() { Dictionary.Add(1, Alpha); Dictionary.Add(2, Beta); Dictionary.Add(3, Gamma); Dictionary.Add(4, Delta); } public static string getById(int id){ return Dictionary.GetByFirst(id); } public static int getByValue(string value) { return Dictionary.GetBySecond(value); } } } |