C#中的双向/双向字典?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/10966331/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Two-way / bidirectional Dictionary in C#?
提问by Neir0
I want to store words in a dictionary in following way:
我想通过以下方式将单词存储在字典中:
I can get word code by word: dict["SomeWord"]-> 123and get word by word code: dict[123]-> "SomeWord"
我可以逐字获取单词代码:dict["SomeWord"]->123并逐字代码获取单词:dict[123]->"SomeWord"
Is it real? Of course one way to do it is two dictionaries: Dictionary<string,int>and Dictionary<int,string>but is there another way?
这是真的吗?当然,一种方式来做到这一点是两点字典:Dictionary<string,int>和Dictionary<int,string>,但有另一种方式?
采纳答案by Enigmativity
I wrote a quick couple of classes that lets you do what you want. You'd probably need to extend it with more features, but it is a good starting point.
我写了几个快速的类,让你做你想做的事。您可能需要使用更多功能扩展它,但这是一个很好的起点。
The use of the code looks like this:
代码的使用如下所示:
var map = new Map<int, string>();
map.Add(42, "Hello");
Console.WriteLine(map.Forward[42]);
// Outputs "Hello"
Console.WriteLine(map.Reverse["Hello"]);
//Outputs 42
Here's the definition:
这是定义:
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; }
}
回答by zmbq
You could use two dictionaries, as others have said, but note also that if both TKeyand TValueare the of same type (and their runtime value domains are known to be disjoint) then you can just use the same dictionary by creating two entries for each key/value pairing:
正如其他人所说,您可以使用两个字典,但还要注意,如果两个TKey和TValue是相同类型(并且它们的运行时值域已知是不相交的),那么您可以通过为每个键创建两个条目来使用相同的字典/值配对:
dict["SomeWord"]= "123"and dict["123"]="SomeWord"
dict["SomeWord"]= "123"和 dict["123"]="SomeWord"
This way a single dictionary can be used for either type of lookup.
这样,单个字典可用于任一类型的查找。
回答by moribvndvs
You can use this extension method, although it uses enumeration, and thus may not be as performant for large data sets. If you are worried about efficiency, then you need two dictionaries. If you want to wrap the two dictionaries into one class, see the accepted answer for this question: Bidirectional 1 to 1 Dictionary in C#
您可以使用此扩展方法,尽管它使用枚举,因此对于大型数据集可能没有那么高的性能。如果您担心效率,那么您需要两本词典。如果要将两个字典包装到一个类中,请参阅此问题的公认答案:Bidirectional 1 to 1 Dictionary in 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");
}
}
回答by paparazzo
This uses an indexer for the reverse lookup.
The reverse lookup is O(n) but it also does not use two dictionaries
这使用索引器进行反向查找。
反向查找是 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;
}
}
}
回答by Brett Caswell
The following encapsulating class utilizes linq (IEnumerable Extensions) over 1 dictionary instance.
以下封装类在 1 个字典实例上使用 linq(IEnumerable 扩展)。
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"];
}
}
回答by toddmo
Bictionary
字典
Here is a commingling of what I liked in each answer. It implements IEnumerableso it can use collection initializer, as you can see in the example.
这是我在每个答案中喜欢的内容的混合。它实现了IEnumerable所以它可以使用集合初始值设定项,如您在示例中所见。
Usage Constraint:
使用限制:
- You are using different datatypes. (i.e.,
T1≠T2)
- 您正在使用不同的数据类型。(即,)
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;
}
}
}
Fiddle:
小提琴:
回答by Xavier John
Expanded on Enigmativity code by adding initializes and Contains method.
通过添加初始化和包含方法扩展了 Enigmativity 代码。
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);
}
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);
}
}
}
Here is a use case, check valid parentheses
这是一个用例,检查有效的括号
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;
}
}
回答by Felipe Ramos
This is an old issue but I wanted to add a two extension methods in case anyone finds it useful. The second is not as useful but it provides a starting point if one to one dictionaries need to be supported.
这是一个老问题,但我想添加两个扩展方法,以防有人发现它有用。第二个不是那么有用,但如果需要支持一对一的字典,它提供了一个起点。
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;
}
回答by Hasan Baidoun
Regrettably, you need two dictionaries, one for each direction. However, you can easily get the inverse dictionary using LINQ:
遗憾的是,您需要两本词典,每个方向一本。但是,您可以使用 LINQ 轻松获取逆字典:
Dictionary<T1, T2> dict = new Dictionary<T1, T2>();
Dictionary<T2, T1> dictInverse = dict.ToDictionary((i) => i.Value, (i) => i.Key);
回答by Simon Tewsi
A modified version of Xavier John's answer, with an additional constructor to take forward and reverse Comparers. This would support case-insensitive keys, for example. Further constructors could be added, if needed, to pass further arguments to the forward and reverse Dictionary constructors.
Xavier John 答案的修改版本,带有一个额外的构造函数来进行正向和反向比较器。例如,这将支持不区分大小写的键。如果需要,可以添加更多构造函数,以将更多参数传递给正向和反向字典构造函数。
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
...
}
Usage example, with a case-insensitive key:
使用示例,使用不区分大小写的键:
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
*/

