Search code examples
c#.netgethashcode

How to include a "joker" value in HashCode


I have the following example class:

public sealed class MyDictKey
{
    public int Type { get; }
    public int SubType { get; }

    public MyDictKey(int type, int subType) // both can only be positive values
    {
        Type = type;
        SubType = subType;
    }

    public override bool Equals(object obj)
    {
        if (obj is MyDictKey other)
        {
            bool typeEqual = other.Type == Type;
            bool subTypeEqual = other.SubType == -1 || SubType == -1 || other.SubType == SubType;
            return typeEqual && subTypeEqual;
        }

        return false;
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = 17;
            hash = hash * 23 + Type.GetHashCode();
            return hash;
        }
    }
}

And the following test (NUnit, if anyone is interested):

    [Test]
    public void CalculatorTest()
    {
        Dictionary<MyDictKey, string> myTypeProcessors = new Dictionary<MyDictKey, string>();

        myTypeProcessors.Add(new MyDictKey(10, 20), "10.20_processor");
        myTypeProcessors.Add(new MyDictKey(3, 4), "3.4_processor");
        myTypeProcessors.Add(new MyDictKey(4, -1), "4.any_processor");
        // -1 means it can process "any" subtype

        for (int i = 0; i < 1000; i++) // should work for any positive number
        {
            bool canGet = myTypeProcessors.TryGetValue(new MyDictKey(4, i), out string value);
            Assert.IsTrue(canGet);
            Assert.That(value, Is.EqualTo("4.any_processor"));

            bool canGet2 = myTypeProcessors.TryGetValue(new MyDictKey(10, i), out string value2);
            if (i == 20)
            {
                Assert.IsTrue(canGet2);
                Assert.That(value2, Is.EqualTo("10.20_processor"));
            }
            else
            {
                Assert.IsFalse(canGet2);
            }
        }
    }

Can I somehow reach the same mechanism only by using the GetHashCode? Since this way, if only the SubTypes differ, the dictionary's TryGetValue will always call into the Equals method. It is important that the new method must not be slower than the original one.

I was thinking of bitwise operators; or are there any magical mathematical formulas for this?

Thank you in advance.


Solution

  • That's not even a well-defined equality function, as it fails to be transitive.

    EG A=(1,1), B =(1,-1), C=(1,2)

    A=B, B=C, but not A=C

    Consider:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
        namespace ConsoleApp5
        {
            class Program
            {
                static void Main(string[] args)
                {
                    var A = new MyDictKey(1, 1);
                    var B = new MyDictKey(1, -1);
                    var C = new MyDictKey(1, 2);
    
                    var h = new HashSet<MyDictKey>();
                    h.Add(A);
                    h.Add(B);
                    h.Add(C);
                    Console.WriteLine(h.Count); //outputs 2
    
    
                    var h2 = new HashSet<MyDictKey>();
                    h2.Add(B);
                    h2.Add(C);
                    h2.Add(A);
                    Console.WriteLine(h2.Count); //outputs 1
    
                    Console.ReadKey();
    
                }
    
                public sealed class MyDictKey
                {
                    public int Type { get; }
                    public int SubType { get; }
    
                    public MyDictKey(int type, int subType) // both can only be positive values
                    {
                        Type = type;
                        SubType = subType;
                    }
    
                    public override bool Equals(object obj)
                    {
                        if (obj is MyDictKey other)
                        {
                            bool typeEqual = other.Type == Type;
                            bool subTypeEqual = other.SubType == -1 || SubType == -1 || other.SubType == SubType;
                            return typeEqual && subTypeEqual;
                        }
    
                        return false;
                    }
    
                    public override int GetHashCode()
                    {
                        unchecked
                        {
                            int hash = 17;
                            hash = hash * 23 + Type.GetHashCode();
                            return hash;
                        }
                    }
                }
            }
        }