[英]C# List as Dictionary key
我有一本由列表鍵入的字典:
private Dictionary<List<custom_obj>, string> Lookup;
我正在嘗試使用 ContainsKey,但它似乎不起作用,我也不知道為什么。 這是來自我的 Visual Studio Immediate Window 的調試信息:
?Lookup.Keys.ElementAt(7)[0]
{custom_obj}
Direction: Down
SID: 2540
?Lookup.Keys.ElementAt(7)[1]
{custom_obj}
Direction: Down
SID: 2550
searchObject[0]
{custom_obj}
Direction: Down
SID: 2540
searchObject[1]
{custom_obj}
Direction: Down
SID: 2550
?Lookup.ContainsKey(searchObject)
false
按照我的常識,最后一個 ContainsKey 應該是真的。 希望我在這里包含了足夠的信息……有什么想法嗎?
謝謝!
充當鍵的List<custom_obj>
實例在引用上不等於 searchObject 引用的實例。
如果您希望字典使用列表中的值而不是引用相等性來查找匹配鍵,則必須在字典的構造函數中提供 IEqualityComparer(因為您不能覆蓋List<T>
中的 Equals 和 GetHashCode)。
您有兩個包含相同元素的單獨List
。 判斷兩個列表是否相等的正確方法是使用SequenceEqual
方法。
你不能默認做你想做的事。 但是,您可以編寫自定義IEqualityComparer
並將其傳遞給Dictionary
構造函數。
這是一個示例通用IEqualityComparer
:
class ListComparer<T> : IEqualityComparer<List<T>>
{
public bool Equals(List<T> x, List<T> y)
{
return x.SequenceEqual(y);
}
public int GetHashCode(List<T> obj)
{
int hashcode = 0;
foreach (T t in obj)
{
hashcode ^= t.GetHashCode();
}
return hashcode;
}
}
您可能希望改進GetHashCode
實現,因為這是一個快速而骯臟的解決方案。
這僅在查找中使用的實際列表實例與添加為鍵的實例相同時才有效。 它不會比較列表內容。 如果您嘗試直接比較兩個 List 對象,這與您將獲得的行為相同。
您確定您在查找方法中使用的實例與字典鍵中的實例相同嗎? 這是我唯一能想到的。
我知道這已經很長時間了
但是我最近一直面臨這個問題,我不喜歡這個 HashCode 解決方案和另一個出現在 Google 中的.Add()
上重新計算 HashCode 的解決方案。
這兩個解決方案對我來說都太慢了,我決定嘗試一些基於樹的方法
基本上我的解決方案將所有這些列表表示為一棵樹,並存儲成對的列表元素和 NULL 或者如果這是最后一個元素,則值
例如,對於這些列表,樹將如下所示:
Key: { 1, 2, 10 }; Value: 30
Key: { 1, 2, 3 }; Value: 50
Key: { 1, 2, 3, 3 }; Value: 70
Key: { 1, 2, 3, 3, 5 }; Value: 80
Key: { 1, 2, 3, 1 }; Value: 100
在這里你可以看到這棵樹的可視化
如果你想要更長的描述/整個源代碼/nuget,那么這里:
https://trolololo.xyz/dictionary_with_list_as_key
https://github.com/Swiftly1/DictionaryList
代碼快照(C# 9):
internal class Node<T, U>
{
public bool IsRoot { get; set; } = false;
private T _ArrayValue;
public Node(T arrayValue, ValueWrapper<U>? storedValue)
{
ArrayValue = arrayValue;
StoredValue = storedValue;
}
public T ArrayValue
{
get
{
if (IsRoot)
throw new Exception("Root does not contain value.");
return _ArrayValue;
}
set { _ArrayValue = value; }
}
public ValueWrapper<U>? StoredValue { get; set; }
public List<Node<T, U>> Children { get; set; } = new List<Node<T, U>>();
public Node<T, U> Add(T arr, ValueWrapper<U> value)
{
var newNode = new Node<T, U>(arr, value.HasValue ? value : null);
Children.Add(newNode);
return newNode;
}
}
public class ValueWrapper<T>
{
public ValueWrapper(bool hasValue, T value)
{
HasValue = hasValue;
Value = value;
}
public bool HasValue { get; private set; }
public T Value { get; private set; }
public override string ToString()
{
return $"{(HasValue ? Value!.ToString() : "NULL")}";
}
}
public class DictionaryList<T, U>
{
private readonly Node<T, U> Root = new Node<T, U>(default!, null) { IsRoot = true };
/// <summary>
/// This parameter indicates whether key contains NULLs e.g [UserA, null, new User()].
/// Allowing NULLs within keys has some performance - speed and memory penalty, that's why it is disabled by default.
/// </summary>
public bool AllowNULLsInKeys { get; set; }
/// <summary>
///
/// </summary>
/// <param name="allow_keys_with_nulls">This parameter indicates whether key contains NULLs e.g [UserA, null, new User()].
/// Allowing NULLs within keys has some performance - speed and memory penalty, that's why it is disabled by default.</param>
public DictionaryList(bool allow_keys_with_nulls = false)
{
AllowNULLsInKeys = allow_keys_with_nulls;
}
public void Add(List<T> data, U value)
{
var current = Root;
for (int i = 0; i < data.Count; i++)
{
T item = data[i];
if (!AllowNULLsInKeys && item == null)
throw new ArgumentException($"Element at index '{i}' is NULL. It cannot be used as a Key's element. " +
$"If you want to use NULLs inside Keys, then either use constructor 'DictionaryList(true)' or set property 'AllowNULLsInKeys' to true.");
Node<T, U> found = FindNode(current, item);
var isLast = i == data.Count - 1;
if (found != null)
{
if (isLast)
{
if (found.StoredValue is null)
{
found.StoredValue = new ValueWrapper<U>(true, value);
}
else
{
if (found.StoredValue.HasValue && !found.StoredValue.Value!.Equals(value))
{
throw new ArgumentException($"Value: '{value}' cannot be saved because there's already value:" +
$" {found.StoredValue.Value}. Key: {string.Join(",", data)}");
}
}
}
current = found;
}
else
{
var wrapper2 = new ValueWrapper<U>(isLast, value);
current = current.Add(item, wrapper2);
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Node<T, U> FindNode(Node<T, U> current, T item)
{
if (AllowNULLsInKeys)
{
for (int i = 0; i < current.Children.Count; i++)
{
if (Equals(current.Children[i].ArrayValue, item))
return current.Children[i];
}
}
else
{
for (int i = 0; i < current.Children.Count; i++)
{
if (current.Children[i].ArrayValue!.Equals(item))
return current.Children[i];
}
}
return null;
}
public bool TryGet(List<T> data, out U? value)
{
var current = Root;
for (int i = 0; i < data.Count; i++)
{
T item = data[i];
Node<T, U> found = FindNode(current, item);
var isLast = i == data.Count - 1;
if (found != null)
{
if (isLast)
{
if (found.StoredValue == null || !found.StoredValue.HasValue)
goto Fail;
value = found.StoredValue.Value;
return true;
}
current = found;
}
else
{
goto Fail;
}
}
Fail:
value = default;
return false;
}
}
用法:
var dict = new DictionaryList<int, int>();
var list1 = new List<int> { 1, 2, 3, 4, 5 };
var list2 = new List<int> { 1, 2, 3, 4, 5, 5, 6 };
dict.Add(list1, 5);
dict.Add(list2, 10);
if (dict.TryGet(list1, out var value1))
Console.WriteLine(value1);
dict.TryGet(list2, out var value2);
Console.WriteLine(value2);
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.