簡體   English   中英

C# 列為字典鍵

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM