4

I have a series of objects whose only different internal state is a fixed-length list(or whatever) of 2-d positions (2 integers). That is, they all have the same number of elements, with (potentially) different 2-d values.

I'm going to be constantly comparing new instances against all previously existent, so it's very important that I write a good hashing function to minimize the number of comparisons.

How would you recommend I hash them?

axtavt
  • 228,184
  • 37
  • 489
  • 472
slezica
  • 63,258
  • 20
  • 91
  • 152
  • Are the values bounded? I.e. do you know min/max for x and y? – sje397 Oct 14 '10 at 14:20
  • 2
    I just want to say this is a great example of an easy way to make an incredibly bad hash function. I've seen `x ^ y` used as a hash function for this kind of thing, which results in about a 99.9% collision rate (assuming a square domain like 1024x1024) – Mark Peters Oct 14 '10 at 14:42
  • The values are bound by known limits, yes. They are x, y > 0 and x < max_x, y < max_y – slezica Oct 14 '10 at 15:33
  • It is very easy to write a reasonable looking hash function that performs poorly. – Tony Ennis Oct 14 '10 at 15:49

3 Answers3

6

the point of choosing 31 as your prime is being able to multiply using a bit shift and a subtract.

Let's say that this is a Point class:

class Point {
    public final int x;
    public final int y;

    public Point(int x, int y)
    {
        this.x = x;
        this.y = y;
    }

    @Override
    public int hashCode()
    {
        int hash = 17;
        hash = ((hash + x) << 5) - (hash + x);
        hash = ((hash + y) << 5) - (hash + y);
        return hash;
    }
}

The point of choosing 31 as your prime is being able to multiply using a bit shift and a single subtract operation. Note that bitshifting by 5 is equivalent to multiplying by 32, and the subtraction makes this equivalent to multiplying by 31. These two operations are much more effecient than a single, true multiplication.

And your object is then:

class TheObject
{
    private final java.util.List<Point> points;

    public TheObject(List<Point> points)
    {
        this.points = points;
    }

    @Override
    public int hashCode()
    {
        int hash = 17;int tmp = 0;
        for (Point p : points)
        {
            tmp = (hash + p.hashCode());
            hash = (tmp << 5) - tmp;
        }
        return hash;
    }
}
Patrick M
  • 9,455
  • 9
  • 56
  • 97
Boris Pavlović
  • 58,387
  • 26
  • 115
  • 142
  • 2
    You could just use `points.hashCode()` for `TheObject`, since a `List` defines its hashcode in terms of its elements. – ColinD Oct 14 '10 at 14:35
  • 3
    Could be simplified a bit, since the first two lines in `Point::hashCode` just add 31*17 to x (which isn't necessary, since it effectively just shifts all the points right) - so you could just do `return 31*x + y;` – sje397 Oct 14 '10 at 14:46
  • +1 - This is the right approach, though maybe larger primes should be used. – Stephen C Oct 14 '10 at 14:52
  • 1
    Beware of integer overflow with this solution - it will happen after about 10 points. I'd replace `hash = 31 * hash...` with `hash += p.hashCode()` which should be sufficient. Initializing the hash variables in each class to 17 probably doesn't do a whole lot. If the point lists are static then calculating the hash value as the points are loaded would be somewhat better. Of course you'd use similar code to what you see above, just at a different time. – Tony Ennis Oct 14 '10 at 15:33
  • @Tony Ennis - What's wrong with an overflow here? http://stackoverflow.com/questions/892618/create-a-hashcode-of-two-numbers/892640#892640 – Ishtar Oct 14 '10 at 18:14
  • @Ishtar I didn't participate in that thread, I don't really know what it's about. – Tony Ennis Oct 14 '10 at 18:35
  • @Tony: I think he was asking (and now I am too) is why do you have to beware of overflow? As long as you're using primes you should be able to overflow to your heart's content without it hurting the quality of the hash. – Mark Peters Oct 14 '10 at 21:16
  • Hmmm I just have an aversion to trusting a number once it's been overflowed. Perhaps it still works for this application, for this JVM. Anyone know for sure if the behavior is defined? – Tony Ennis Oct 14 '10 at 22:21
  • @Tony Ennis - This behavior : integer overflow is defined. See http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.17.1 There is nothing scary about overflows. – Ishtar Oct 16 '10 at 17:39
  • @ishtar That's very interesting. Neither addition or multiplication ever throw runtime errors due to overflow. The only weird thing I see is that you lose control of your sign bit. – Tony Ennis Oct 16 '10 at 17:58
  • @Tony Ennis - Indeed. For hash codes, this would be just fine :) – Ishtar Oct 16 '10 at 18:53
  • @TonyEnnis: In C, compilers may regard signed values as numbers, and trap any case where x+y doesn't give the arithmetically-correct sum, but in the absence of promotions they must regard unsigned values as members of a wrapping algebraic ring [for e.g. 32-bit unsigned integers, it would be the ring of integers which are congruent mod 2^32]. Rather than using "unsigned" values when an algebraic ring is required, Java simply uses the same types for both numbers and algebraic rings, and thus cannot trap overflows. – supercat Mar 25 '14 at 16:08
  • Having just two points you would get a collision as soon as one coordinate larger than 31. e.g. `(32,31), (33,0)` would have same hash equal to `48050`. Instead of using `<< 5` (multiplying by 32) you should multiply by different prime numbers. – Tombart Aug 31 '15 at 11:28
1

Uhm, how about something along the lines of a binary search tree?

To put comparison in pseudocode:

position1 > position2 := 
   (position1.x > position2.x) || 
   ((position1.x == position2.x) && (position1.y > position2.y))

list1.x > list2.x := {
    for (i in 0...n) 
        if (list1[i] > list2[i]) return true;
        else if (list1[i] > list2[i]) return false;
    return false;
}

where n of course is the length of the lists.

I'm not much of a java-pro and I really don't know the standard library, but I suppose, you could just write the tree yourself. Implement a getID-method, that'll try to find this list or insert it otherwise and along with it a unique id, which you can obtain by simply incrementing a counter.

That way, you get an ID (instead of a hash), that has no collisions, whatsoever. In worst case comparing 2 lists is O(n), thus a find/insert is O(n) * O(log(m)) (supposing the tree is balanced) where m is the overall number of all lists.

Determining an ID is thus more expensive than hashing, in worst case, but as said, the result is guaranteed to be unique.

I can say little about average, since you give no numbers. Actually I am surprised you do not want to make a direct comparison, since I'd expect the probability for 2 positions to be equal is less than 1%, thus a list comparison is about O(1), since the probability that you need to compare 5 entries is really small.

Also, it is not clear, whether the lists are mutable or not, since if they are immutable, the cost should be of little importance.

back2dos
  • 15,346
  • 31
  • 48
  • There has to be a point-by-point comparison anyway - if two hash values come up the same, it doesn't mean the lists are identical. So back2dos isn't adding more code but leveraging something that has to exist anyway. _since the probability that you need to compare 5 entries is really small._ that's the crux of it unless the dataset tends to have a lot of duplicated points. – Tony Ennis Oct 14 '10 at 18:39
0

Well depending on the size of your integers, you may want to multiply the first coordinate by the max possible coordinate and add the second. For example, if X and Y are positive and have a limit of 256, you can try X*256+Y for your hash function. If X and Y can be negative as well, you may want to offset them first to make them non-negative. Also, if multiplying X by the max overflows the integer, you may want a multi-int hash value or perhaps mod or bitwise-and the result with UINT_MAX.

Michael Goldshteyn
  • 65,547
  • 23
  • 122
  • 176