简体   繁体   English

C# 列为字典键

[英]C# List as Dictionary key

I have a dictionary which is keyed by a List:我有一本由列表键入的字典:

private Dictionary<List<custom_obj>, string> Lookup;

I'm trying to use ContainsKey, but it doesn't seem to be working, and I have no idea why.我正在尝试使用 ContainsKey,但它似乎不起作用,我也不知道为什么。 Here is the debug information from my Visual Studio Immediate Window:这是来自我的 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

In my common sense, that last ContainsKey should be true.按照我的常识,最后一个 ContainsKey 应该是真的。 Hopefully I've included enough information here... any ideas?希望我在这里包含了足够的信息……有什么想法吗?

Thanks!谢谢!

The List<custom_obj> instance acting as a key is referentially unequal to the instance referred to by searchObject.充当键的List<custom_obj>实例在引用上不等于 searchObject 引用的实例。

If you want the dictionary to use the values in the list instead of referential equality to find matching keys, you must supply an IEqualityComparer in the constructor of the dictionary (since you can't override Equals and GetHashCode in List<T> ).如果您希望字典使用列表中的值而不是引用相等性来查找匹配键,则必须在字典的构造函数中提供 IEqualityComparer(因为您不能覆盖List<T>中的 Equals 和 GetHashCode)。

You have two separate List s that contain the same elements.您有两个包含相同元素的单独List The correct way to find out if two lists are equal is with the SequenceEqual method.判断两个列表是否相等的正确方法是使用SequenceEqual方法。

You cannot by default do what you are trying to do.你不能默认做你想做的事。 You can however, write a custom IEqualityComparer and pass it into the Dictionary constructor.但是,您可以编写自定义IEqualityComparer并将其传递给Dictionary构造函数。

Here is a sample generic IEqualityComparer :这是一个示例通用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;
    }
}

You may want to improve on the GetHashCode implementation, as this was a quick-and-dirty solution.您可能希望改进GetHashCode实现,因为这是一个快速而肮脏的解决方案。

This will only work if the actual list instance used in the lookup is the same as the instance that was added as a key.这仅在查找中使用的实际列表实例与添加为键的实例相同时才有效。 It will not compare the list contents.它不会比较列表内容。 This is the same behavior you will get if you try to compare two List objects directly.如果您尝试直接比较两个 List 对象,这与您将获得的行为相同。

Are you certain that the instance you are using in your lookup method is the same instance that is among your dictionary's keys?您确定您在查找方法中使用的实例与字典键中的实例相同吗? That is the only thing I can think of.这是我唯一能想到的。

I'm aware that it's been such a long time我知道这已经很长时间了

But I've been facing this problem recently and I didn't like this HashCode solution and the other that appear in Google where HashCode is being recalculated on .Add() .但是我最近一直面临这个问题,我不喜欢这个 HashCode 解决方案和另一个出现在 Google 中的.Add()上重新计算 HashCode 的解决方案。

Both of those solutions were too slow for me and I decided to try some tree-based approach这两个解决方案对我来说都太慢了,我决定尝试一些基于树的方法

Basically my solution represents all those lists as a tree and stores pairs of list's elements and NULL or if that was the last element, then the value基本上我的解决方案将所有这些列表表示为一棵树,并存储成对的列表元素和 NULL 或者如果这是最后一个元素,则值

For example, for those lists, the tree will look like:例如,对于这些列表,树将如下所示:

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

Here you can see visualization of this tree在这里你可以看到这棵树的可视化

enter image description here在此处输入图片说明

If you want longer description / whole source code / nuget, then here:如果你想要更长的描述/整个源代码/nuget,那么这里:

https://trolololo.xyz/dictionary_with_list_as_key https://trolololo.xyz/dictionary_with_list_as_key

https://github.com/Swiftly1/DictionaryList https://github.com/Swiftly1/DictionaryList

Code snapshot (C# 9):代码快照(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;
    }
}

Usage:用法:

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