获取通用字典的指定值的多个键?
Posted
技术标签:
【中文标题】获取通用字典的指定值的多个键?【英文标题】:Getting multiple keys of specified value of a generic Dictionary? 【发布时间】:2008-11-01 00:49:07 【问题描述】:从 .NET 通用字典中获取键的值很容易:
Dictionary<int, string> greek = new Dictionary<int, string>();
greek.Add(1, "Alpha");
greek.Add(2, "Beta");
string secondGreek = greek[2]; // Beta
但是尝试获取给定值的键并不那么简单,因为可能有多个键:
int[] betaKeys = greek.WhatDoIPutHere("Beta"); // expecting single 2
【问题讨论】:
当您期望单个值时,为什么返回类型int[]
?
@Anar,阅读我对 Domenic 的回答; “重复值不太可能,但并非不可能”。
值的键?我想你的意思是键
我只是无法联系到你。你结束了我的问题并判断我没有尝试。你真是天才,一开始就什么都知道?我只是想要一个提示而不是代码。我今天才联系蟒蛇!我想知道是否有人能给我一个提示来指导我找到一种学习方法。你太粗鲁了。
【参考方案1】:
好的,这里是多双向版本:
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);
【讨论】:
从我在 msdn 中阅读的内容来看,这不应该是 BiLookup 而不是 BiDictionary 吗?并不是说它很重要或其他什么,只是好奇我是否在这里理解正确...... 另外,我使用 GetByFirst 并取回 EmptySecondList,向其中添加了一些内容,然后再次调用 GetByFirst,我不会得到一个包含一些内容的列表而不是一个空列表吗? @Svish:不,因为当您尝试添加到列表时,它会抛出异常(您无法添加到数组中)。是的,BiLookup 可能是一个更好的名字。 虽然我看到这回答了 OP 的问题,但这不是一个有点幼稚的实现吗?不是更现实的实现是 DictionaryListDictionary 以便您实际上可以通过 2 个不同的键查找丰富的对象吗? @ChrisMarisic:不知道你的意思——但我已经用过很多类似的东西,不需要更多了。【参考方案2】:正如其他人所说,字典中没有从值到键的映射。
我刚刚注意到您想从值映射到多个键 - 我将这里的解决方案留给单值版本,但我将为多条目双向映射添加另一个答案.
这里采用的正常方法是有两个字典 - 一个映射一种方式,一个映射另一种方式。将它们封装在一个单独的类中,并计算出当您有重复的键或值时您想要做什么(例如,抛出异常、覆盖现有条目或忽略新条目)。就个人而言,我可能会抛出异常 - 它使成功行为更容易定义。像这样的:
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);
【讨论】:
我不认为有任何理由让它派生自一个具体的类——我不喜欢未经深思熟虑的继承——但它当然可以实现 IEnumerable 等。事实上,它可以实现IDictionaryAdd
调用将失败 - 但如果是第二个,我们就会让系统进入混乱状态。我的方式,在异常之后你仍然有一个一致的集合。
@nawfal:嗯,我不知道这是否是我第一次写答案时这样做的原因......我猜;)【参考方案3】:
字典并不是真的要像这样工作,因为虽然保证了键的唯一性,但值的唯一性却没有。所以例如如果你有
var greek = new Dictionary<int, string> 1, "Alpha" , 2, "Alpha" ;
您希望greek.WhatDoIPutHere("Alpha")
得到什么?
因此,您不能期望这样的东西会被纳入框架。您需要自己的方法来实现自己的独特用途——您想返回一个数组(或IEnumerable<T>
)吗?如果有多个具有给定值的键,你想抛出异常吗?如果没有怎么办?
我个人会选择一个可枚举的,像这样:
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();
【讨论】:
一个优雅的解决方案,但这必须在 2.0 中工作。重复值不太可能但并非不可能,返回一个集合会更好。【参考方案4】:也许最简单的方法是在没有 Linq 的情况下循环对:
int betaKey;
foreach (KeyValuePair<int, string> pair in lookup)
if (pair.Value == value)
betaKey = pair.Key; // Found
break;
betaKey = -1; // Not found
如果你有 Linq,它可以通过这种方式轻松完成:
int betaKey = greek.SingleOrDefault(x => x.Value == "Beta").Key;
【讨论】:
dour,但是你上面有一个 var 类型?!你确定你在3.0?也可以在下面查看我的更新。 抱歉,我使用“var”只是为了减少打字。我不想做线性搜索,字典可能很大。var
是一种语言功能,而不是框架功能。如果你真的想,你可以使用null-coalescing from C#-6.0 and still target CF-2.0。【参考方案5】:
字典不保存值的散列,只保存键,因此使用值对其进行的任何搜索都至少需要线性时间。您最好的选择是简单地遍历字典中的元素并跟踪匹配的键或切换到不同的数据结构,也许维护两个字典映射 key->value 和 value->List_of_keys。如果你做后者,你将用存储来换取查找速度。将@Cybis 示例转换为这样的数据结构并不需要太多。
【讨论】:
【参考方案6】:因为我想要一个完整的双向字典(而不仅仅是地图),所以我添加了缺失的函数以使其成为一个 IDictionary 兼容类。这是基于具有唯一键值对的版本。如果需要,这是文件(大部分工作是通过 XMLDoc):
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
【讨论】:
【参考方案7】:修订:可以进行某种查找,您将需要字典以外的东西,因为如果您考虑一下,字典是单向键。也就是说,这些值可能不是唯一的
这表明您使用的是 c#3.0,因此您可能不必求助于循环,可以使用类似的东西:
var key = (from k in yourDictionary where string.Compare(k.Value, "yourValue", true) == 0 select k.Key).FirstOrDefault();
【讨论】:
字典没有 .FindByValue。我宁愿移动到不同的数据结构而不是循环遍历值。【参考方案8】:字典类没有针对这种情况进行优化,但如果你真的想这样做(在 C# 2.0 中),你可以这样做:
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 的方式。
【讨论】:
【参考方案9】:你不能创建一个具有该功能的 Dictionary 子类吗?
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];
编辑:抱歉,第一次没有正确获取代码。
【讨论】:
这只会切换我使用的键,只返回字符串键的 int 值,我需要双向。而且,正如 Domenic 指出的那样,我可以有重复的字符串值。 如果您的 int 键可以有重复的字符串值,那么当您按字符串查找时,您期望得到什么?对应 int 的列表对象?【参考方案10】:这里提出的“简单”双向字典解决方案很复杂,可能难以理解、维护或扩展。最初的问题也要求“值的键”,但显然可能有多个键(我已经编辑了这个问题)。整个方法相当可疑。
软件更改。编写易于维护的代码应该优先考虑其他“聪明”的复杂解决方法。从字典中的值取回键的方法是循环。字典并非设计为双向的。
【讨论】:
或者可能是第二个字典,将每个值映射到它的键。 @DavidRR 只有键必须是唯一的,所以第二种字典方法不会真正起作用。但是您可以简单地循环遍历字典以获取值的键。 如果问题要求字典支持每个string
键的多个int
值,那么字典可以这样定义:Dictionary<string, List<int>>
。
现在如何在不迭代的情况下实现双向?
关于 OP 的问题,标准的 Dictionary
确实 not 提供双向功能。所以,如果你只有一个标准的Dictionary
并且你想找到与特定值关联的键,你确实必须迭代!但是,对于“大”字典,迭代可能会导致性能不佳。请注意,我自己提供的 the answer 是基于迭代(通过 LINQ)。如果您的初始 Dictionary
不会进一步更改,您可以构建一个反向 Dictionary
一次以加快反向查找。【参考方案11】:
使用 LINQ 进行反向 Dictionary<K, V>
查找。但请记住,Dictionary<K, V>
值中的值可能不同。
演示:
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("\n== 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();
预期输出:
== Enumerating Distinct Values ==
Four => [4, 44]
Five => [5]
One => [1, 11]
Three => [3]
Two => [2]
【讨论】:
我看到的问题是您正在检查字典中的每个元素以获得相反的方向。 O(n) 的搜索时间违背了使用字典的目的;它应该是 O(1)。 @stephen - 同意。正如其他人指出的那样,如果性能是最重要的,那么一个单独的值字典或bi-directional dictionary 将是合适的。但是,如果很少需要进行值查找并且这样做的性能是可以接受的,那么我在这里概述的方法可能值得考虑。也就是说,在我的回答中使用 LINQ 与 OP 对适用于 .NET 2.0 的解决方案的渴望不兼容。 (尽管 .NET 2.0 约束在 2014 年可能不太可能发生。)【参考方案12】: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);
【讨论】:
感谢您发布答案!虽然代码 sn-p 可以回答这个问题,但添加一些附加信息仍然很棒,比如解释等..【参考方案13】:这些答案中的许多现在已经过时了,这是一种使用 LINQ 的现代 C# 方法
由于值不一定是唯一的,您可能会得到多个结果。您可以返回IEnumerable<KeyValuePair<int, string>>
:
var betaKeys = greek.Where(x => x.Value == "beta");
要将其转换为IEnumerable<int>
类型,只需使用.Select()
:
var betaKeys = greek.Where(x => x.Value == "beta").Select(x => x.Key);
【讨论】:
【参考方案14】:作为公认答案 (https://***.com/a/255638/986160) 的一个转折点,假设键将与字典中的符号值相关联。类似于 (https://***.com/a/255630/986160) 但更优雅一点。新颖之处在于消费类可以用作枚举替代方案(但也适用于字符串),并且字典实现了 IEnumerable。
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();
作为一个消费类,你可以拥有
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);
【讨论】:
这与六年前发布的an answer 基本相同,并且如前所述,键与单个值无关。每个键可以有多个值。 我知道,但我的版本实现了 IEnumerable 并且更优雅。另外,消费类示例将 BiDictionary 类置于不同的可用性级别 - 它解决了字符串和 id 的静态枚举问题由 C# 提供。如果您阅读我的回答,我也提到了它!【参考方案15】:那么外行的解决办法
可以编写类似于下面的函数来制作这样的字典:
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;
【讨论】:
以上是关于获取通用字典的指定值的多个键?的主要内容,如果未能解决你的问题,请参考以下文章