我想通过以下方式将单词存储在字典中:
我可以按单词获取单词代码:dict["SomeWord"]
->123
并按单词获取单词代码:dict[123]
->"SomeWord"
这是真的吗?当然,一种方式来做到这一点是两点字典:Dictionary<string,int>
和Dictionary<int,string>
,但有另一种方式?
我想通过以下方式将单词存储在字典中:
我可以按单词获取单词代码:dict["SomeWord"]
->123
并按单词获取单词代码:dict[123]
->"SomeWord"
这是真的吗?当然,一种方式来做到这一点是两点字典:Dictionary<string,int>
和Dictionary<int,string>
,但有另一种方式?
Answers:
我写了几节简单的课,让您做自己想做的事。您可能需要使用更多功能进行扩展,但这是一个很好的起点。
该代码的使用如下所示:
var map = new Map<int, string>();
map.Add(42, "Hello");
Console.WriteLine(map.Forward[42]);
// Outputs "Hello"
Console.WriteLine(map.Reverse["Hello"]);
//Outputs 42
定义如下:
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; }
}
Forward
字典属性(具有private set;
),而是通过将其传递到字典的Indexer类的Indexer属性来修改该字典上的值。 public T4 this[T3 index] { get { return _dictionary[index]; } set { _dictionary[index] = value; } }
因此,这破坏了正向/反向查找。
通过添加initializes和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);
}
}
}
这是一个用例,请检查有效的括号
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;
}
}
正如其他人所说,您可以使用两个字典,但也请注意,如果TKey
和TValue
属于相同类型(并且它们的运行时值域是不相交的),则可以通过为每个键创建两个条目来使用相同的字典/值配对:
dict["SomeWord"]= "123"
和 dict["123"]="SomeWord"
这样,单个字典可用于两种类型的查找。
哎呀,我把我的版本混在一起:
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();
}
}
向其中添加一些数据:
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);
然后进行查找:
lookup[2] --> 1, 6
lookup[3] --> 1
lookup[8] --> 6
您可以使用此扩展方法,尽管它使用枚举,因此可能不适用于大型数据集。如果您担心效率,那么您需要两个字典。如果要将两个词典包装到一个类中,请参见此问题的可接受答案:C#中的双向1到1词典
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");
}
}
这是我在每个答案中喜欢的东西的混合体。它实现了,IEnumerable
因此可以使用集合初始化程序,如示例中所示。
使用限制:
T1
≠
T2
码:
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;
}
}
}
小提琴:
Bictionary<string, string>
即使所有字符串都是唯一的,您也无法做到?
T1 == T2
,因此前向查找失败。此外,我无法覆盖默认的索引器,因为这样查找调用将变得模棱两可。我添加了此约束,并删除了前一个约束,因为的值T1
可以与的值重叠T2
。
try
并将异常转换为来摆脱性能下降的困扰KeyNotFoundExceptions
。
这是一个老问题,但是我想添加两个扩展方法,以防有人觉得有用。第二种方法不那么有用,但是它为需要支持一对一的字典提供了一个起点。
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;
}
Xavier John答案的修改版本,带有一个额外的构造函数以进行正向和反向比较器。例如,这将支持不区分大小写的键。如果需要,可以添加其他构造函数,以将更多参数传递给正向和反向Dictionary构造函数。
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
...
}
具有不区分大小写的键的用法示例:
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
*/
这是我的代码。除种子构造函数外,其他所有内容均为O(1)。
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();
}
}
以下封装类在1个字典实例上使用linq(IEnumerable Extensions)。
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"];
}
}
这将使用索引器进行反向查找。
反向查找为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;
}
}
}
this[string Word]
。另外的问题是不对应的共同做法,与代码(评论不一致的变量名UInt16
VS UInt32
-这就是为什么!:不使用意见),解决方案是不通用的,...
这是建议的替代解决方案。删除内部类并确保添加/删除项目时的连贯性
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();
}
}
BijectionDictionary
此开源存储库中有可用的类型:
https://github.com/ColmBhandal/CsharpExtras。
从本质上讲,它与给出的其他答案没有太大不同。像大多数答案一样,它使用两个字典。
我相信关于这本词典与迄今为止的其他答案的新颖之处在于,它的行为不像双向词典,它的行为就像单向的熟悉词典,然后动态地允许您使用反向属性。翻转的对象参考很浅,因此仍然可以修改与原始参考相同的核心对象。因此,您可以有两个对同一个对象的引用,只是其中之一被翻转了。
该词典可能与众不同的另一件事是,在该存储库下的测试项目中为此词典编写了一些测试。我们已经在实践中使用了它,到目前为止,它已经相当稳定了。
谜题答案的扩展版本可以通过nuget包 https://www.nuget.org/packages/BidirectionalMap/
它在这里开源