C # 中的双向/双向字典?

我想用以下方式在字典中存储单词:

我可以逐字得到单词代码: dict["SomeWord"]-> 123和得到单词代码: dict[123]-> "SomeWord"

是真的吗?当然一种方法是两个字典: Dictionary<string,int>Dictionary<int,string>,但是有没有其他的方法呢?

61922 次浏览

正如其他人所说,你可以使用两个字典,但是也要注意,如果 TKeyTValue都是相同类型的(并且它们的运行时值域是不相交的) ,那么你可以通过为每个键/值对创建两个条目来使用同一个字典:

dict["SomeWord"]= "123"dict["123"]="SomeWord"

This way a single dictionary can be used for either type of lookup.

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: 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");
}
}

我写了几个简短的类,可以让你做你想做的事情。您可能需要使用更多的特性来扩展它,但这是一个很好的起点。

代码的使用如下:

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; }
}

这将使用一个索引器进行反向查找。
The reverse lookup is O(n) but it also does not use two dictionaries

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;
}
}
}

The following encapsulating class utilizes linq (IEnumerable Extensions) over 1 dictionary instance.

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"];


}


}

字典

Here is a commingling of what I liked in each answer. It implements IEnumerable so it can use collection initializer, as you can see in the example.

使用限制:

  • 您正在使用不同的数据类型(即 T1T2)

密码:

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;
}
}
}

提琴:

Https://dotnetfiddle.net/mtneuw

通过添加 initialize 和 Containsmethod 在 Enigmvity 代码上进行了扩展。

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;
}
}

这是一个老问题,但我想添加两个扩展方法,以防有人发现它有用。第二种方法没有那么有用,但是如果需要支持一对一的字典,它提供了一个起点。

    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;
}

Regrettably, you need two dictionaries, one for each direction. However, you can easily get the inverse dictionary using LINQ:

Dictionary<T1, T2> dict = new Dictionary<T1, T2>();
Dictionary<T2, T1> dictInverse = dict.ToDictionary((i) => i.Value, (i) => i.Key);

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
*/

管他呢,我把我的版本加进去:

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

这是我的代码,除了种子构造函数,其他都是 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();
}
}

这里有一个替代建议的解决方案: 删除内部类,并确保添加/删除项目时的一致性

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();
}
}


我做了一个扩展版本的谜题的答案可作为一个 nugget 软件包 Https://www.nuget.org/packages/bidirectionalmap/

它是开源的 给你