What is the best way to implement this composite GetHashCode()

前端 未结 5 452
南方客
南方客 2020-12-01 01:46

I have a simple class:

public class TileName {
    int Zoom, X, Y;

    public override bool Equals (object obj)
    {
        var o = obj as TileName;
              


        
相关标签:
5条回答
  • 2020-12-01 02:28
    public override int GetHashCode ()
    {
        return (Zoom.ToString() + "-" + X.ToString() + "-" + Y.ToString()).GetHashCode();
    }
    
    0 讨论(0)
  • 2020-12-01 02:30

    Like described by Jon Skeet in this SO answer, it is best practice to pick some prime numbers and multiply these with the single hash codes, then sum everything up.

    public int GetHashCode()
    {
        unchecked
        {
            int hash = 17;
            // Maybe nullity checks, if these are objects not primitives!
            hash = hash * 23 + Zoom.GetHashCode();
            hash = hash * 23 + X.GetHashCode();
            hash = hash * 23 + Y.GetHashCode();
            return hash;
        }
    }
    

    The problems with xor hashes are:

    • if X is equal to Y then your hash will be just Zoom, because then X ^ Y = X ^ X = 0 holds
    • xor is a symmetric operator, it will produce the exact same hashes for the objects [Zoom = 3, X = 5, Y = 7], [Zoom = 3, X = 7, Y = 5], [Zoom = 7, X = 5, Y = 3] etc.

    These facts make the xor-method more likely to cause collisions.

    In addition to Jons post, consider using a unchecked context, for explicitly ignoring overflows. Because like the MSDN says:

    If neither checked nor unchecked is used, a constant expression uses the default overflow checking at compile time, which is checked. Otherwise, if the expression is non-constant, the run-time overflow checking depends on other factors such as compiler options and environment configuration.

    So while usually overflows will be unchecked, it may be that it fails somewhen in some environment or built with some compiler option. But in this case you want to explicitly not check these overflows.

    Update:

    By the way: someInt.GetHashCode() returns someInt. Like this, it is of course the fastest possible and a perfect hash distribution without a single collision. How else would you map an int to an int-hash? :) So what I wanted to say: Your first approach:

    return (Zoom + X + Y).GetHashCode();
    

    and your second one:

    return Zoom.GetHashCode() + X.GetHashCode() + Y.GetHashCode();
    

    are exactly the same. You dont even have to call GetHashCode and both are very likely to have collisions. Maybe even worse than the xor method, if you very likely have small integer values for all three ints.

    Update 2:

    As I wrote in the comment to ChaosPandions post: If you just have those three int values, and X, Y and Zoom are relatively small numbers (smaller than 1000 or 10000) this one may be also a good hash generator:

    public int GetHashCode()
    {
        return (X << 16) ^ (Y << 8) ^ Zoom;
    }
    

    It just distributes the bits in the hash value (example in big-endian for readability):

    00000000 00000000 00000011 00110001    X = 817
    00000000 00000000 00011011 11111010    Y = 7162
    00000000 00000000 00000010 10010110    Zoom = 662
    
    00000011 00110001 00000000 00000000    X << 16
    00000000 00011011 11111010 00000000    Y << 8
    00000000 00000000 00000010 10010110    Zoom
    
    00000011 00101010 11111000 10010110    (X << 16) ^ (Y << 8) ^ Zoom
    
    0 讨论(0)
  • 2020-12-01 02:42

    I know this questions is kind of old but nowadays you can easily create your hash code by using System.HashCode class

    https://docs.microsoft.com/en-us/dotnet/api/system.hashcode.combine?view=netcore-3.1

    In this specific case it would look like

    public override int GetHashCode()
    {
        return HashCode.Combine(Zoom, X, Y);
    }
    
    
    0 讨论(0)
  • 2020-12-01 02:49

    Neither of the implementations in your question are ideal. For example, they'll return exactly the same hash for { Zoom=1, X=2, Y=3 }, { Zoom=2, X=3, Y=1 }, { Zoom=3, X=1, Y=2 } etc etc.

    I usually use something like this:

    public override int GetHashCode()
    {
        // 269 and 47 are primes
        int hash = 269;
        hash = (hash * 47) + Zoom.GetHashCode();
        hash = (hash * 47) + X.GetHashCode();
        hash = (hash * 47) + Y.GetHashCode();
        return hash;
    }
    

    (From memory, I think the C# compiler uses something similar when it generates the GetHashCode methods for anonymous types.)

    0 讨论(0)
  • 2020-12-01 02:51

    I've actually found this to be really effective.

    public override int GetHashCode ()
    {
        return Zoom.GetHashCode() ^ X.GetHashCode() ^ Y.GetHashCode();
    }
    
    0 讨论(0)
提交回复
热议问题