使用无序对作为其键的 C# 字典?

Faf*_*les 8 c# dictionary unity-game-engine unordered-set unordered

我正在尝试创建一个 C# 字典,它将一对无序索引作为其键。

例如:

exampleDictionary[new UnorderedPair(x,y)]并且exampleDictionary[new UnorderedPair(y,x)]应该都返回相同的值。

除了使用 HashSet 之外,还有其他方法可以创建自定义无序集合吗?或者某种创建无序元组的方法?

这个问题与我想要完成的问题类似,只不过是在 C# 而不是 python 中。

der*_*ugo 8

如果类型不是您自己的或者您不能或不想修改,请参阅Theodor Zoulias 的答案

否则,假设这UnorderedPair是您自己的课程,您可以修改您可以做的事情,例如

[Serializable]
public class UnorderedPair<T> : IEquatable<UnorderedPair<T>>
{
    public T X;
    public T Y;

    public UnorderedPair()
    {
        
    }

    public UnorderedPair(T x, T y)
    {
        X = x;
        Y = y;
    }

    public bool Equals(UnorderedPair<T> other)
    {
        if (ReferenceEquals(null, other))
        {
            return false;
        }

        if (ReferenceEquals(this, other))
        {
            return true;
        }

        // For equality simply include the swapped check
        return X.Equals(other.X) && Y.Equals(other.Y) || X.Equals(other.Y) && Y.Equals(other.X);
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj))
        {
            return false;
        }

        if (ReferenceEquals(this, obj))
        {
            return true;
        }

        if (obj.GetType() != GetType())
        {
            return false;
        }

        return Equals((UnorderedPair<T>)obj);
    }

    public override int GetHashCode()
    {
        // and for the HashCode (used as key in HashSet and Dictionary) simply order them by size an hash them again ^^
        var hashX = X == null ? 0 : X.GetHashCode();
        var hashY = Y == null ? 0 : Y.GetHashCode();
        return HashCode.Combine(Math.Min(hashX,hashY), Math.Max(hashX,hashY));
    }

    public static bool operator ==(UnorderedPair<T> left, UnorderedPair<T> right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(UnorderedPair<T> left, UnorderedPair<T> right)
    {
        return !Equals(left, right);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后例如

var testDict = new Dictionary<UnorderedPair<int>, string>();
testDict.Add(new UnorderedPair<int>(1,2), "Hello World!");
Console.WriteLine(testDict[new UnorderedPair<int>(2,1)]);
Run Code Online (Sandbox Code Playgroud)

根据 Jodrell 在评论中的建议,您甚至可以使类型可交换 - 不确定是否需要这样做 - 但这样您甚至可以拥有一对不同的类型:

[Serializable]
public class UnorderedPair<TX, TY> : IEquatable<UnorderedPair<TX, TY>>
{
    public TX X;
    public TY Y;

    public UnorderedPair()
    {
        
    }

    public UnorderedPair(TX x, TY y)
    {
        X = x;
        Y = y;
    }

    public UnorderedPair(TY y, TX x)
    {
        X = x;
        Y = y;
    }

    public override int GetHashCode()
    {
        // and for the HashCode (used as key in HashSet and Dictionary) simply order them by size an hash them again ^^
        var hashX = X == null ? 0 : X.GetHashCode();
        var hashY = Y == null ? 0 : Y.GetHashCode();
        var combine = HashCode.Combine(Math.Min(hashX, hashY), Math.Max(hashX, hashY));
        return combine;
    }

    public bool Equals(UnorderedPair<TX, TY> other)
    {
        if (ReferenceEquals(null, other))
        {
            return false;
        }

        if (ReferenceEquals(this, other))
        {
            return true;
        }

        if (typeof(TX) != typeof(TY))
        {
            return EqualityComparer<TX>.Default.Equals(X, other.X) && EqualityComparer<TY>.Default.Equals(Y, other.Y);
         
        }
        
        return  EqualityComparer<TX>.Default.Equals(X, other.X) && EqualityComparer<TY>.Default.Equals(Y, other.Y)
            || X.Equals(other.Y) && Y.Equals(other.X);
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj))
        {
            return false;
        }

        if (ReferenceEquals(this, obj))
        {
            return true;
        }

        return obj switch
        {
            UnorderedPair<TX, TY> other => Equals(other),
            UnorderedPair<TY, TX> otherSwapped => Equals(otherSwapped),
            _ => false
        };
    }

    public static bool operator ==(UnorderedPair<TX, TY> left, UnorderedPair<TX, TY> right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(UnorderedPair<TX, TY> left, UnorderedPair<TX, TY> right)
    {
        return !Equals(left, right);
    }

    public static implicit operator UnorderedPair<TX, TY>(UnorderedPair<TY, TX> pair)
    {
        return new UnorderedPair<TX, TY>(pair.Y, pair.X);
    }
}
Run Code Online (Sandbox Code Playgroud)

和

var testDict = new Dictionary<UnorderedPair<int, double>, string>();
testDict.Add(new UnorderedPair<int, double>(1,2.5), "Hello World!");
Console.WriteLine(testDict[new UnorderedPair<double,int>(2.5,1)]);
Run Code Online (Sandbox Code Playgroud)

(.NET Fiddle 适用于两者)


The*_*ias 5

您可以编写一个自定义IEqualityComparer<UnorderedPair<T>>实现,并将其作为参数传递给Dictionary<UnorderedPair<TKey>, TValue>. 这样你就不必UnorderedPair<T>通过重写它的Equals和GetHashCode方法来修改你的类型。下面是结构体比较器的示例ValueTuple<T1, T2>,其中 和T1是T2相同类型:

class UnorderedValueTupleEqualityComparer<T> : IEqualityComparer<(T, T)>
{
    private readonly IEqualityComparer<T> _comparer;

    public UnorderedValueTupleEqualityComparer(IEqualityComparer<T> comparer = default)
    {
        _comparer = comparer ?? EqualityComparer<T>.Default;
    }

    public bool Equals((T, T) x, (T, T) y)
    {
        if (_comparer.Equals(x.Item1, y.Item1)
            && _comparer.Equals(x.Item2, y.Item2)) return true;
        if (_comparer.Equals(x.Item1, y.Item2)
            && _comparer.Equals(x.Item2, y.Item1)) return true;
        return false;
    }

    public int GetHashCode((T, T) obj)
    {
        int h1 = _comparer.GetHashCode(obj.Item1);
        int h2 = _comparer.GetHashCode(obj.Item2);
        if (h1 > h2) (h1, h2) = (h2, h1);
        return HashCode.Combine(h1, h2);
    }
}
Run Code Online (Sandbox Code Playgroud)

使用示例:

Dictionary<(int, int), string> dictionary = new(
    new UnorderedValueTupleEqualityComparer<int>());
Run Code Online (Sandbox Code Playgroud)