.NET
中是否有可以容纳2个密钥和一个值的字典。
像
Dictionary(Of TKey, Of TKey, TValue)
我需要存储两个按键,并且在某些时候通过按键1查看项目,有时则按键2查看。
我目前的解决方案是维护两个词典
Dictionary<string, long> Dict1 = new Dictionary<string, long>();
Dictionary<long, long> Dict2 = new Dictionary<long, long>();
当需要添加项目时,我会将其添加到两个词典中。
Dict1.Add("abc", 111);
Dict2.Add(345, 111);
然后我会从其中一个字典中查找一个项目,具体取决于我需要查看哪个键。
删除或更新项目时我会这样做。
我已经考虑过复合键,但我不知道如何设置它,我也不想失去搜索项目的速度。
.NET
中是否有一些解决方案可以包含可以容纳多个密钥的字典?
答案 0 :(得分:13)
如果你希望你的价值从任何一个键“可以找到”,我会像你现在一样使用两个词典。 但是我会将其包含在类中,方法名称为FindByXXX
和FindByYYY
。
更难的问题是如何执行删除,因为您需要在删除时知道这两个键。也许您的值存储了两个键,因此您可以将值传递给delete方法。也许你永远不需要从词典中删除项目。或者需要删除项目的代码知道两个密钥。
因此没有标准字典来执行此操作,因为每个用户之间的要求不同。
(请注意,您不希望使用复合键的字典,因为只要您希望查找某个项目,就需要知道这两个键。)
答案 1 :(得分:10)
也许,这样的事情:
public class TwoKeyDictionary<Tkey1, Tkey2, TValue>
{
private object m_data_lock = new object();
private Dictionary<Tkey1, Tkey2> m_dic1 = new Dictionary<Tkey1, Tkey2>();
private Dictionary<Tkey2, TValue> m_dic2 = new Dictionary<Tkey2, TValue>();
public void AddValue(Tkey1 key1, Tkey2 key2, TValue value)
{
lock(m_data_lock)
{
m_dic1[key1] = key2;
m_dic2[key2] = value;
}
}
public TValue getByKey1(Tkey1 key1)
{
lock(m_data_lock)
return m_dic2[m_dic1[key1]];
}
public TValue getByKey2(Tkey key2)
{
lock(m_data_lock)
return m_dic2[key2];
}
public void removeByKey1(Tkey1 key1)
{
lock(m_data_lock)
{
Tkey2 tmp_key2 = m_dic1[key1];
m_dic1.Remove(key1);
m_dic2.Remove(tmp_key2);
}
}
public void removeByKey2(Tkey2 key2)
{
lock(m_data_lock)
{
Tkey1 tmp_key1 = m_dic1.First((kvp) => kvp.Value.Equals(key2)).Key;
m_dic1.Remove(tmp_key1);
m_dic2.Remove(key2);
}
}
}
我可以提供第二种解决方案,但与第一种解决方案相比似乎更加缓慢和丑陋。
public class TwoKeysDictionary<K1, K2, V>
{
private class TwoKeysValue<K1, K2, V>
{
public K1 Key1 { get; set; }
public K2 Key2 { get; set; }
public V Value { get; set; }
}
private List<TwoKeysValue<K1, K2, V>> m_list = new List<TwoKeysValue<K1, K2, V>>();
public void Add(K1 key1, K2 key2, V value)
{
lock (m_list)
m_list.Add(new TwoKeysValue<K1, K2, V>() { Key1 = key1, Key2 = key2, Value = value });
}
public V getByKey1(K1 key1)
{
lock (m_list)
return m_list.First((tkv) => tkv.Key1.Equals(key1)).Value;
}
public V getByKey2(K2 key2)
{
lock (m_list)
return m_list.First((tkv) => tkv.Key2.Equals(key2)).Value;
}
public void removeByKey1(K1 key1)
{
lock (m_list)
m_list.Remove(m_list.First((tkv) => tkv.Key1.Equals(key1)));
}
public void removeByKey2(K2 key2)
{
lock (m_list)
m_list.Remove(m_list.First((tkv) => tkv.Key2.Equals(key2)));
}
}
在非常糟糕的情况下,当Keys是一个大结构(即大值类型)并且Keys等于大小,并且值是小值类型(例如,一个字节)时,使用第一个解决方案: Key1的集合,两组Key2,一组值= 3组大对象和1组小值。 使用第二种解决方案:一组Key1,一组Key2,一组值= 2组大对象和小组值。 即使用第一个解决方案需要50%(或更低)更多的内存空间而不是第二个解决方案,但第二个解决方案与第一个解决方案相比非常非常慢。
答案 2 :(得分:4)
您的解决方案对应用程序的内存占用量有很大影响。随着字典的增长,存储实际数据所需的内存量(对于值类型)至少需要两倍。
你可能从不同的角度来看待这个问题。有两个词典:
var lookupDictionary = new Dictionary<string, string>();
var valuesDictionary = new Dictionary<string, [YourValueType]>();
从这里开始非常简单。
// Add a new entry into the values dictionary and give it a unique key
valuesDictionary.Add("FooBar", "FUBAR VALUE");
// Add any number of lookup keys with the same value key
lookupDictionary.Add("Foo", "FooBar");
lookupDictionary.Add("Bar", "FooBar");
lookupDictionary.Add("Rab", "FooBar");
lookupDictionary.Add("Oof", "FooBar");
如果您需要从valuesDictionary
找到某些内容,请先点击lookupDictionary
。这将为您提供valuesDictionary
中要查找的值的关键字。
修改强>
我还没有在我的回答中解决删除问题,所以在这里:D
您可以点击lookupDictionary
查找值键,然后删除lookupDictionary
中具有值的所有条目。
应该足够简单且安全,因为valuesDictionary
保证具有唯一键,因此您不会意外地删除查找键以获取其他值。
然而,正如Ian Ringrose在评论中指出的那样,您将对lookupDictionary
进行全面扫描以进行删除。这可能会对紧密环路等性能产生不良影响。
我现在无法想到解决这个问题的好方法。也许其他人可能会对如何改进它有一些想法。
我希望这会有所帮助。
答案 3 :(得分:4)
如果您使用的是C#7.0,最好的方法是使用元组类型和文字:
// Declare
var dict = new Dictionary<(string, long), long>();
// Add
dict.Add(("abc", 345), 111);
// Get
var searchedValue = dict[("abc", 345)];
答案 4 :(得分:2)
你不能只使用一个词典而不会失去查找速度。原因是,如果要创建复合键,则在覆盖GetHashCode时不会返回有意义的值。这意味着需要对每个键进行相等比较,直到找到字典条目。在这种情况下,您还可能遇到组合键的潜在问题:因为您的Equals方法将检查一个属性或另一个属性是否相等,以下键基本上是重复键{Id = 1,Name =“Bob”} { Id = 1,Name =“Anna”},这并没有给我一种温暖的模糊感。
这使您可以使用自己的类包装字典或字典对。
答案 5 :(得分:1)
有趣的问题,这是一个解决方案。 您必须为要支持的每个键类型添加索引器。
public class NewDic<T>
{
public void Add(string key1, long key2, T value)
{
mDic.Add(key1, value);
mDic.Add(key2, value);
}
public T this[string s]
{
get { return mDic[s]; }
}
public T this[long l]
{
get { return mDic[l]; }
}
Dictionary<object, T> mDic = new Dictionary<object, T>();
}
NewDic<long> dic = new NewDic<long>();
dic.Add("abc", 20, 10);
Console.WriteLine(dic["abc"]);
Console.WriteLine(dic[20]);
答案 6 :(得分:1)
这不是一个合适的字典,但可以用于简单的字典添加删除功能。
这也可以是通用的,在密钥类型中正确实现IComparable,并相应地更改字典代码。 (注意,不允许使用键的默认值来管理歧义!)
internal class KeyValueSet //this dictionary item is tailor made for this example
{
public string KeyStr { get; set; }
public int KeyInt { get; set; }
public int Value { get; set; }
public KeyValueSet() { }
public KeyValueSet(string keyStr, int keyInt, int value)
{
KeyStr = keyStr;
KeyInt = keyInt;
Value = value;
}
}
public class DoubleKeyDictionary
{
List<KeyValueSet> _list = new List<KeyValueSet>();
private void Add(KeyValueSet set)
{
if (set == null)
throw new InvalidOperationException("Cannot add null");
if (string.IsNullOrEmpty(set.KeyStr) && set.KeyInt == 0)
throw new InvalidOperationException("Invalid key");
if (!string.IsNullOrEmpty(set.KeyStr) && _list.Any(l => l.KeyStr.Equals(set.KeyStr))
|| set.KeyInt != 0 && _list.Any(l => l.KeyInt == set.KeyInt))
throw new InvalidOperationException("Either of keys exists");
_list.Add(set);
}
public void Add(string keyStr, int keyInt, int value)
{
Add(new KeyValueSet { KeyInt = keyInt, KeyStr = keyStr, Value = value });
}
public void Add(string key, int value)
{
Add(new KeyValueSet { KeyInt = 0, KeyStr = key, Value = value });
}
public void Add(int key, int value)
{
Add(new KeyValueSet { KeyInt = key, KeyStr = string.Empty, Value = value });
}
public void Remove(int key)
{
if (key == 0)
throw new InvalidDataException("Key not found");
var val = _list.First(l => l.KeyInt == key);
_list.Remove(val);
}
public void Remove(string key)
{
if (string.IsNullOrEmpty(key))
throw new InvalidDataException("Key not found");
var val = _list.First(l => l.KeyStr == key);
_list.Remove(val);
}
public void Remove(KeyValueSet item)
{
_list.Remove(item);
}
public int this[int index]
{
get
{
if (index != 0 && _list.Any(l => l.KeyInt == index))
return _list.First(l => l.KeyInt == index).Value;
throw new InvalidDataException("Key not found");
}
set
{
Add(index, value);
}
}
public int this[string key]
{
get
{
if (!string.IsNullOrEmpty(key) && _list.Any(l => l.KeyStr == key))
return _list.First(l => l.KeyStr == key).Value;
throw new InvalidDataException("Key not found");
}
set
{
Add(key, value);
}
}
}
测试DoubleKeyDictionary
var dict = new DoubleKeyDictionary();
dict.Add(123, 1);
dict.Add(234, 2);
dict.Add("k1", 3);
dict.Add("k2", 4);
dict[456] = 5;
dict["k3"] = 6;
dict.Add("k4", 567, 7);
dict.Remove(123);
Console.WriteLine(dict[234]); //2
Console.WriteLine(dict["k2"]); //4
Console.WriteLine(dict[456]); //5
Console.WriteLine(dict[567]); //7
Console.WriteLine(dict["k4"]); //7
Console.WriteLine(dict[123]); //exception
答案 7 :(得分:0)
Dictionary<Tuple<string, long>, long>
怎么样?元组按值进行比较,因此它应以预期的方式唯一地进行索引。此外,现在您不必将long
值包装在两个地方(并处理在任何地方同步值的奇妙痛苦)。
这种做法怎么样?基本上,仍然使用基于字典的策略,但通过具有重载索引器属性的类来对其进行外观。所以它看起来像字典,感觉像字典,但支持多个键(不像字典,LOL)。
public class MultiKeyDictionary<TFirstKey, TSecondKey, TValue>
{
private readonly Dictionary<TFirstKey, TValue> firstKeyDictionary =
new Dictionary<TFirstKey, TValue>();
private readonly Dictionary<TSecondKey, TFirstKey> secondKeyDictionary =
new Dictionary<TSecondKey, TFirstKey>();
public TValue this[TFirstKey idx]
{
get
{
return firstKeyDictionary[idx];
}
set
{
firstKeyDictionary[idx] = value;
}
}
public TValue this[TSecondKey idx]
{
get
{
var firstKey = secondKeyDictionary[idx];
return firstKeyDictionary[firstKey];
}
set
{
var firstKey = secondKeyDictionary[idx];
firstKeyDictionary[firstKey] = value;
}
}
public IEnumerable<KeyValuePair<TFirstKey, TValue>> GetKeyValuePairsOfFirstKey()
{
return firstKeyDictionary.ToList();
}
public IEnumerable<KeyValuePair<TSecondKey, TValue>> GetKeyValuePairsOfSecondKey()
{
var r = from s in secondKeyDictionary
join f in firstKeyDictionary on s.Value equals f.Key
select new KeyValuePair<TSecondKey, TValue>(s.Key, f.Value);
return r.ToList();
}
public void Add(TFirstKey firstKey, TSecondKey secondKey, TValue value)
{
firstKeyDictionary.Add(firstKey, value);
secondKeyDictionary.Add(secondKey, firstKey);
}
public bool Remove(TFirstKey firstKey)
{
if (!secondKeyDictionary.Any(f => f.Value.Equals(firstKey))) return false;
var secondKeyToDelete = secondKeyDictionary.First(f => f.Value.Equals(firstKey));
secondKeyDictionary.Remove(secondKeyToDelete.Key);
firstKeyDictionary.Remove(firstKey);
return true;
}
public bool Remove(TSecondKey secondKey)
{
if (!secondKeyDictionary.ContainsKey(secondKey)) return false;
var firstKey = secondKeyDictionary[secondKey];
secondKeyDictionary.Remove(secondKey);
firstKeyDictionary.Remove(firstKey);
return true;
}
}
测试代码......
static void Main(string[] args)
{
var dict = new MultiKeyDictionary<string, long, long>();
dict.Add("abc", 111, 1234);
dict.Add("def", 222, 7890);
dict.Add("hij", 333, 9090);
Console.WriteLine(dict["abc"]); // expect 1234
Console.WriteLine(dict["def"]); // expect 7890
Console.WriteLine(dict[333]); // expect 9090
Console.WriteLine();
Console.WriteLine("removing def");
dict.Remove("def");
Console.WriteLine();
Console.WriteLine("now we have:");
foreach (var d in dict.GetKeyValuePairsOfFirstKey())
{
Console.WriteLine($"{d.Key} : {d.Value}");
}
Console.WriteLine();
Console.WriteLine("removing 333");
dict.Remove(333);
Console.WriteLine();
Console.WriteLine("now we have:");
foreach (var d in dict.GetKeyValuePairsOfSecondKey())
{
Console.WriteLine($"{d.Key} : {d.Value}");
}
Console.ReadLine();
}
答案 8 :(得分:0)
根据您对问题的评论中的建议,您只需使用Object
密钥即可Dictionary
:
Dictionary<Object, long> dict = new Dictionary<Object, long>();
dict.Add("abc", 111);
dict.Add(345, 111);
要获得更清晰的解决方案,您可以将此字典包装在自定义类中并创建Add
方法的版本:
public void Add(ISet<Object> keys, T value){
foreach(Object k in keys)
{
_privateDict.Add(k, value);
}
}
答案 9 :(得分:0)
起初我以为我可以创建一个代表IDictionary<TKey1, TValue>
和IDictionary<TKey2, TValue>
的类,并且只需要一个字典作为字段,并以最小的逻辑将大多数方法委托给单个字典。
这种方法的问题是TKey1
和TKey2
可能属于同一类型,这是一个问题,因为这个新类将实现两次相同的接口。当TKey1
为string
且TKey2
时,运行时应调用哪种方法也是string
?
正如上面其他人所建议的那样,最好创建一个在幕后使用一个或两个词典的数据结构。例如,如果您提前知道要使用string
和int
作为密钥,则可以使用此方法:
public class StringIntDictionary<TValue> : IDictionary<string, TValue>, IDictionary<int, TValue>
{
private IDictionary<object, TValue> _dictionary = new Dictionary<object, TValue>();
// implement interface below, delegate to _dictionary
}
这样您就可以使用string
和int
键:
var dict = StringIntDictionary<bool>();
dict["abc"] = true;
dict[123] = true;
答案 10 :(得分:0)
作为本地解决方案,我使用简单的方法:
想象一下,我有一个由字符串标识的产品集合和一个带有每个产品按钮的表单。
管理按钮的状态时,我需要按字符串键找到按钮。 处理点击时,我需要按按钮实例查找产品ID。
我没有维护两个单独的词典,而是执行以下操作:
public class SPurchaseOption
{
public Button Button;
public string ProductID;
public string SomeOtherAssociatedData;
}
Dictionary<object, SPurchaseOption> purchaseOptions;
当初始化按钮时,我将两个条目附加到Dictionary
,即
Key: ProductID, Value: "SPurchaseOption"
Key: Button, Value: "SPurchaseOption"
对于更通用的方法,如果你需要一个常用的组件,你将需要围绕两个词典构建一个包装,即:
public class DoubleKeyedDictionary<TKey1, TKey2, TValue>
{
class SItem
{
public TKey1 key1;
public TKey2 key2;
public TValue value;
}
Dictionary<TKey1, SItem> dic1;
Dictionary<TKey2, SItem> dic2;
}
这将通过任何键访问值和替代键。
答案 11 :(得分:0)
一种非常粗略的方法,直到我找到更好的方法为止。
class MyClass
{
public string StringKey = "";
public int IntKey = 0;
public override Equals(object obj)
{
// Code to return true if all fields are equal
}
}
Dictionary <MyClass, string> MyDict;
MyClass myClass;
MyDict[MyDict.Keys.FirstOrDefault(x => x.Equals(MyClass))];
以我的金钱,对use tuples的回答是正确的答案。不幸的是,我的NuGet太旧了,无法获得我想使用的ValueTuple包,因此我的字段不是'item1','item2'等。这比我在这里所做的要更加混乱。当我更改VS / NuGet版本时,在这种情况下都是ValueTuples。本周第二次我遇到了需求!
答案 12 :(得分:0)
在效率上有更好的地方。
public class MultiKeyDictionary<TKeyType1, TKeyType2, TValueType>
{
private readonly object threadLock = new object();
private readonly Dictionary<TKeyType1, TValueType> _dictionary1 = new Dictionary<TKeyType1, TValueType>();
private readonly Dictionary<TKeyType2, TValueType> _dictionary2 = new Dictionary<TKeyType2, TValueType>();
private readonly Dictionary<TKeyType1, TKeyType2> _Key1Key2Map = new Dictionary<TKeyType1, TKeyType2>();
private readonly Dictionary<TKeyType2, TKeyType1> _Key2Key1Map = new Dictionary<TKeyType2, TKeyType1>();
public bool Add(TKeyType1 key1, TKeyType2 key2, TValueType v)
{
if (ContainsKey1(key1) || ContainsKey2(key2))
return false;
_dictionary1.Add(key1, v);
_dictionary2.Add(key2, v);
_Key1Key2Map.Add(key1, key2);
_Key2Key1Map.Add(key2, key1);
return true;
}
public bool ContainsKey1(TKeyType1 key)
{
return _dictionary1.ContainsKey(key);
}
public bool ContainsKey2(TKeyType2 key)
{
return _dictionary2.ContainsKey(key);
}
//Note if TKeyType1 and TKeyType2 are the same then we are forced to use GetBy functions
public TValueType GetByKey1(TKeyType1 key)
{
return _dictionary1[key];
}
public TValueType GetByKey2(TKeyType2 key)
{
return _dictionary2[key];
}
public bool SetByKey1(TKeyType1 key, TValueType val)
{
if (ContainsKey1(key))
return false;
lock (threadLock)
{
var key2 = _Key1Key2Map[key];
_dictionary1[key] = val;
_dictionary2[key2] = val;
}
return true;
}
public bool SetByKey2(TKeyType2 key, TValueType val)
{
if (ContainsKey2(key))
return false;
lock (threadLock)
{
var key1 = _Key2Key1Map[key];
_dictionary1[key1] = val;
_dictionary2[key] = val;
}
return true;
}
public void RemoveUsingKey1(TKeyType1 key)
{
lock (threadLock)
{
var key2 = _Key1Key2Map[key];
_dictionary1.Remove(key);
_dictionary2.Remove(key2);
_Key1Key2Map.Remove(key);
_Key2Key1Map.Remove(key2);
}
}
public void RemoveUsingKey2(TKeyType2 key)
{
lock (threadLock)
{
var key1 = _Key2Key1Map[key];
_dictionary1.Remove(key1);
_dictionary2.Remove(key);
_Key1Key2Map.Remove(key1);
_Key2Key1Map.Remove(key);
}
}
public bool Contains(TKeyType1 key)
{
return _dictionary1.ContainsKey(key);
}
public bool Contains(TKeyType2 key)
{
return _dictionary2.ContainsKey(key);
}
public TValueType this[TKeyType1 key]
{
get => GetByKey1(key);
set => SetByKey1(key, value);
}
public TValueType this[TKeyType2 key]
{
get => GetByKey2(key);
set => SetByKey2(key, value);
}
public void Remove(TKeyType1 key)
{
RemoveUsingKey1(key);
}
public void Remove(TKeyType2 key)
{
RemoveUsingKey2(key);
}
public int Count => _dictionary1.Count;
public Dictionary<TKeyType1, TValueType>.KeyCollection Key1s => _dictionary1.Keys;
public Dictionary<TKeyType2, TValueType>.KeyCollection Key2s => _dictionary2.Keys;
public Dictionary<TKeyType1, TValueType>.ValueCollection Values => _dictionary1.Values;
public void Clear()
{
lock (threadLock)
{
_dictionary1.Clear();
_dictionary2.Clear();
_Key1Key2Map.Clear();
_Key2Key1Map.Clear();
}
}
//Map between Keys
public TKeyType2 Key2(TKeyType1 key)
{
return _Key1Key2Map[key];
}
public TKeyType1 Key1(TKeyType2 key)
{
return _Key2Key1Map[key];
}
}
答案 13 :(得分:0)
我已经创建了自己的版本。它更复杂一点。文档应该解释一些功能。本质上,它允许以合理的方式处理相同值的 2 个键,自动合并条目,大概是线程安全的(未经测试),允许将键映射在一起,并处理删除条目,同时具有字典的功能根据。添加条目时,但其中一个键已经存在,它只会添加该键并覆盖该值。它在逻辑上与其他形式的集合非常不同,所以这些是我能够实现的最多的。
某些带有密钥对的结构似乎不合适,因为我需要它来根据需要向现有条目任意添加第二个密钥,并考虑到合并功能。我还考虑了两个键使用相同类型的情况,但也考虑了它们不使用相同类型的情况。
/// <summary> A collection that internally uses a list (which in turn internally uses an array), and two dictionaries for the index.
/// This allows operating it based on two keys and provides means to (automatically) map keys to each other.
/// The indexing of the internal list is treated specially. In order to not infringe on the validity of the dictionaries' references to the indexes,
/// they are kept identical. Removing is handled by setting the entries to 'null', and once a new item is added, they are overwritten. </summary>
/// <typeparam name="TKey1"> The first key. </typeparam>
/// <typeparam name="TKey2"> The second key. </typeparam>
/// <typeparam name="T"> The stored value type. </typeparam>
public class TwoKeyDictionary<TKey1, TKey2, T> : IEnumerable<TwoKeyDictionaryEntry<TKey1, TKey2, T>>, IReadOnlyCollection<TwoKeyDictionaryEntry<TKey1, TKey2, T>>
{
private readonly Dictionary<TKey1, int> _keys01 = new Dictionary<TKey1, int> ();
private readonly Dictionary<TKey2, int> _keys02 = new Dictionary<TKey2, int> ();
private readonly List<TwoKeyDictionaryEntry<TKey1, TKey2, T>> _items = new List<TwoKeyDictionaryEntry<TKey1, TKey2, T>> ();
private int _freeIndex = 0; // The index of the first free slot.
private int _freeCount = 0; // Free before the last value.
private readonly object _lock = new object ();
public TwoKeyDictionary () { }
/// <summary> Adds an item. </summary>
public bool Add (TKey1 key, T value)
{
return AddByKey1 (key, value);
}
/// <summary> Adds an item. </summary>
public bool Add (TKey2 key, T value)
{
return AddByKey2 (key, value);
}
/// <summary> Adds an item. </summary>
public bool AddByKey1 (TKey1 key, T value)
{
lock (_lock)
{
return AddByKey1Internal (key, value);
}
}
/// <summary> Adds an item. </summary>
public bool AddByKey2 (TKey2 key, T value)
{
lock (_lock)
{
return AddByKey2Internal (key, value);
}
}
/// <summary> Adds an item with two keys. If either key already exists, it will map the other key to it. The value will only be overwritten if it's 'null'. </summary>
public bool Add (TKey1 key1, TKey2 key2, T value)
{
return Add (key1, key2, value, false);
}
/// <summary> Adds an item with two keys. If either key already exists, it will map the other key to it. The value will only be overwritten if it's 'null'.
/// This may also define how the key is mapped, if occurring. </summary>
public bool Add (TKey1 key1, TKey2 key2, T value, bool mapToKey2)
{
lock (_lock)
{
return AddInternal (key1, key2, value, mapToKey2);
}
}
/// <summary> Maps both keys together. If either key exists, it will add the other one to it. If both exist, it will merge the entries and delete the other.
/// By default this will map to key1. </summary>
public bool Map (TKey1 key1, TKey2 key2)
{
return MapToKey1 (key1, key2);
}
/// <summary> Maps both keys together. If either key exists, it will add the other one to it. If both exist, it will merge the entries and delete the one with key2. </summary>
public bool MapToKey1 (TKey1 key1, TKey2 key2)
{
lock (_lock)
{
return MapToKey1Internal (key1, key2);
}
}
/// <summary> Maps both keys together. If either key exists, it will add the other one to it. If both exist, it will merge the entries and delete the one with key1. </summary>
public bool MapToKey2 (TKey1 key1, TKey2 key2)
{
lock (_lock)
{
return MapToKey2Internal (key1, key2);
}
}
/// <summary> Removes an entry based on key1. If there is a key2 mapped to it, it will be removed as well. </summary>
public bool Remove (TKey1 key)
{
return RemoveByKey1 (key);
}
/// <summary> Removes an entry based on key2. If there is a key1 mapped to it, it will be removed as well. </summary>
public bool Remove (TKey2 key)
{
return RemoveByKey2 (key);
}
/// <summary> Removes an entry based on key1. If there is a key2 mapped to it, it will be removed as well. </summary>
public bool RemoveByKey1 (TKey1 key)
{
lock (_lock)
{
return RemoveByKey1Internal (key);
}
}
/// <summary> Removes an entry based on key2. If there is a key1 mapped to it, it will be removed as well. </summary>
public bool RemoveByKey2 (TKey2 key)
{
lock (_lock)
{
return RemoveByKey2Internal (key);
}
}
/// <summary> Removes an entry based on both, key1 and key2. Any entries related to either keys will be removed. </summary>
public bool Remove (TKey1 key1, TKey2 key2)
{
lock (_lock)
{
return RemoveByKey1Internal (key1) | RemoveByKey2Internal (key2);
}
}
/// <summary> Tries to return a value based on key1. </summary>
public bool TryGetValue (TKey1 key, out T value)
{
return TryGetValueByKey1 (key, out value);
}
/// <summary> Tries to return a value based on key2. </summary>
public bool TryGetValue (TKey2 key, out T value)
{
return TryGetValueByKey2 (key, out value);
}
/// <summary> Tries to return a value based on key1. </summary>
public bool TryGetValueByKey1 (TKey1 key, out T value)
{
if (key == null) { value = default; return false; }
if (_keys01.TryGetValue (key, out int index))
{
TwoKeyDictionaryEntry<TKey1, TKey2, T> entry = _items[index];
if (entry != null)
{
value = entry.Value;
return true;
}
}
value = default;
return false;
}
/// <summary> Tries to return a value based on key2. </summary>
public bool TryGetValueByKey2 (TKey2 key, out T value)
{
if (key == null) { value = default; return false; }
if (_keys02.TryGetValue (key, out int index))
{
TwoKeyDictionaryEntry<TKey1, TKey2, T> entry = _items[index];
if (entry != null)
{
value = entry.Value;
return true;
}
}
value = default;
return false;
}
/// <summary> Tries to return a value based on key1 or key2. Prioritizes key1. </summary>
public bool TryGetValue (TKey1 key1, TKey2 key2, out T value)
{
return TryGetValue (key1, key2, false, out value);
}
/// <summary> Tries to return a value based on key1 or key2. </summary>
public bool TryGetValue (TKey1 key1, TKey2 key2, bool prioritizeKey2, out T value)
{
return prioritizeKey2 ? TryGetValue (key1, out value) || TryGetValue (key2, out value) : TryGetValue (key2, out value) || TryGetValue (key1, out value);
}
/// <summary> Returns 'true' if they key and the entry still exists. The stored value itself may still be 'null' regardless. </summary>
public bool ContainsKey (TKey1 key)
{
return ContainsKey1 (key);
}
/// <summary> Returns 'true' if they key and the entry still exists. The stored value itself may still be 'null' regardless. </summary>
public bool ContainsKey (TKey2 key)
{
return ContainsKey2 (key);
}
/// <summary> Returns 'true' if they key and the entry still exists. The stored value itself may still be 'null' regardless. </summary>
public bool ContainsKey1 (TKey1 key)
{
if (key == null) return false;
if (_keys01.TryGetValue (key, out int index)) return _items[index] != null;
else return false;
}
/// <summary> Returns 'true' if they key and the entry still exists. The stored value itself may still be 'null' regardless. </summary>
public bool ContainsKey2 (TKey2 key)
{
if (key == null) return false;
if (_keys02.TryGetValue (key, out int index)) return _items[index] != null;
else return false;
}
/// <summary> Returns 'true' if they key and the entry still exists. The stored value itself may still be 'null' regardless. </summary>
public bool ContainsKey (TKey1 key1, TKey2 key2)
{
return ContainsKey1 (key1) || ContainsKey2 (key2);
}
#region Internal
// Returns true if this wasn't the last position.
private bool GetFreeIndex (bool apply, out int index)
{
if (_freeCount == 0)
{
index = _items.Count;
return false;
}
else
{
index = _freeIndex;
if (apply)
{
// We must find the next free slot.
int freeIndex = _freeIndex + 1;
int count = _items.Count;
while (freeIndex < count && _items[freeIndex] != null)
{
freeIndex++;
}
if (freeIndex == count) _freeCount = 0;
else Interlocked.Decrement (ref _freeCount);
_freeIndex = freeIndex;
}
return true;
}
}
private bool MapToKey1Internal (TKey1 key1, TKey2 key2)
{
if (key1 == null || key2 == null) return false;
bool s1 = _keys01.TryGetValue (key1, out int index1);
bool s2 = _keys02.TryGetValue (key2, out int index2);
if (s1 && s2)
{
TwoKeyDictionaryEntry<TKey1, TKey2, T> e1 = _items[index1];
TwoKeyDictionaryEntry<TKey1, TKey2, T> e2 = _items[index2];
RemoveByKey2Internal (key2);
e1.Key2 = key2;
if (e1.Value == null) e1.Value = e2.Value;
return true;
}
else if (s1)
{
_items[index1].Key2 = key2;
_keys02.Add (key2, index1);
return true;
}
else if (s2)
{
_items[index2].Key1 = key1;
_keys01.Add (key1, index2);
return true;
}
else return false;
}
private bool MapToKey2Internal (TKey1 key1, TKey2 key2)
{
if (key1 == null || key2 == null) return false;
bool s1 = _keys01.TryGetValue (key1, out int index1);
bool s2 = _keys02.TryGetValue (key2, out int index2);
if (s1 && s2)
{
TwoKeyDictionaryEntry<TKey1, TKey2, T> e1 = _items[index1];
TwoKeyDictionaryEntry<TKey1, TKey2, T> e2 = _items[index2];
RemoveByKey1Internal (key1);
e2.Key1 = key1;
if (e2.Value == null) e2.Value = e1.Value;
return true;
}
else if (s1)
{
_items[index1].Key2 = key2;
return true;
}
else if (s2)
{
_items[index2].Key1 = key1;
return true;
}
else return false;
}
private bool AddByKey1Internal (TKey1 key, T value)
{
if (key == null) return false;
if (_keys01.TryGetValue (key, out int index))
{
TwoKeyDictionaryEntry<TKey1, TKey2, T> entry = _items[index];
if (entry != null)
{
entry.Value = value;
return true;
}
else
{
_keys01.Remove (key);
return AddByKey1Internal (key, value);
}
}
else
{
TwoKeyDictionaryEntry<TKey1, TKey2, T> item = new TwoKeyDictionaryEntry<TKey1, TKey2, T> (key, default, value);
if (GetFreeIndex (true, out int freeIndex))
{
_items[freeIndex] = item;
}
else
{
_items.Add (item);
}
_keys01.Add (key, freeIndex);
return true;
}
}
private bool AddByKey2Internal (TKey2 key, T value)
{
if (key == null) return false;
if (_keys02.TryGetValue (key, out int index))
{
TwoKeyDictionaryEntry<TKey1, TKey2, T> entry = _items[index];
if (entry != null)
{
entry.Value = value;
return true;
}
else
{
_keys02.Remove (key);
return AddByKey2Internal (key, value);
}
}
else
{
TwoKeyDictionaryEntry<TKey1, TKey2, T> item = new TwoKeyDictionaryEntry<TKey1, TKey2, T> (default, key, value);
if (GetFreeIndex (true, out int freeIndex))
{
_items[freeIndex] = item;
}
else
{
_items.Add (item);
}
_keys02.Add (key, freeIndex);
return true;
}
}
private bool AddInternal (TKey1 key1, TKey2 key2, T value, bool mapToKey2)
{
if (key1 == null) return AddByKey2Internal (key2, value);
else if (key2 == null) return AddByKey1Internal (key1, value);
bool hasKey1 = _keys01.TryGetValue (key1, out int index1);
bool hasKey2 = _keys02.TryGetValue (key2, out int index2);
if (hasKey1 && hasKey2)
{
// We have 2 different entries' keys that point to the same value. Merge them to one key, remove the other.
if (mapToKey2)
{
if (MapToKey2Internal (key1, key2))
{
_items[index2].Value = value;
}
}
else
{
if (MapToKey1Internal (key1, key2))
{
_items[index1].Value = value;
}
}
}
else if (hasKey1)
{
TwoKeyDictionaryEntry<TKey1, TKey2, T> entry = _items[index1];
entry.Key2 = key2;
entry.Value = value;
}
else if (hasKey2)
{
TwoKeyDictionaryEntry<TKey1, TKey2, T> entry = _items[index2];
entry.Key1 = key1;
entry.Value = value;
}
else
{
_items.Add (new TwoKeyDictionaryEntry<TKey1, TKey2, T> (key1, key2, value));
}
return true;
}
private bool RemoveByKey1Internal (TKey1 key)
{
if (key == null) return false;
if (_keys01.TryGetValue (key, out int index))
{
TwoKeyDictionaryEntry<TKey1, TKey2, T> entry = _items[index];
if (entry != null)
{
_keys01.Remove (key);
if (entry.Key2 != null) _keys02.Remove (entry.Key2);
if (index == _items.Count - 1)
{
_items.RemoveAt (index);
}
else
{
_items[index] = null;
_freeIndex = _freeCount > 0 ? Math.Min (_freeIndex, index) : index;
Interlocked.Increment (ref _freeCount);
}
return true;
}
else
{
_keys01.Remove (key);
}
}
return false;
}
private bool RemoveByKey2Internal (TKey2 key)
{
if (key == null) return false;
if (_keys02.TryGetValue (key, out int index))
{
TwoKeyDictionaryEntry<TKey1, TKey2, T> entry = _items[index];
if (entry != null)
{
_keys02.Remove (key);
if (entry.Key1 != null) _keys01.Remove (entry.Key1);
if (index == _items.Count - 1)
{
_items.RemoveAt (index);
}
else
{
_items[index] = null;
_freeIndex = _freeCount > 0 ? Math.Min (_freeIndex, index) : index;
Interlocked.Increment (ref _freeCount);
}
return true;
}
else
{
_keys02.Remove (key);
}
}
return false;
}
#endregion
#region Interface Implementations
public int Count => _items.Count (j => j != null);
public IEnumerator<TwoKeyDictionaryEntry<TKey1, TKey2, T>> GetEnumerator ()
{
return _items.Where (j => j != null).GetEnumerator ();
}
IEnumerator IEnumerable.GetEnumerator ()
{
return _items.Where (j => j != null).GetEnumerator ();
}
#endregion
}
/// <summary> The entry class of <see cref="TwoKeyDictionary{TKey1, TKey2, T}"/>, which grants references to the keys in both dictionaries used. </summary>
/// <typeparam name="TKey1"> The first key. </typeparam>
/// <typeparam name="TKey2"> The second key. </typeparam>
/// <typeparam name="T"> The stored value type. </typeparam>
public class TwoKeyDictionaryEntry<TKey1, TKey2, T>
{
public TKey1 Key1 { get; internal set; }
public TKey2 Key2 { get; internal set; }
public T Value { get; internal set; }
internal TwoKeyDictionaryEntry () { }
internal TwoKeyDictionaryEntry (TKey1 key1, TKey2 key2, T value)
{
Key1 = key1;
Key2 = key2;
Value = value;
}
public override string ToString ()
{
return $"{Key1?.ToString () ?? "---"} | {Key2?.ToString () ?? "---"} | {Value}";
}
}