views:

92

answers:

3

Hi!

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?

A: 

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
Doesn't that just give you a hash for one point?
sje397
+3  A: 

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 = 31*hash + x;
        hash = 31*hash + y;
        return hash;
    }
}

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;
        for (Point p : points)
        {
            hash = 31 * hash + p.hashCode();
        }
        return hash;
    }
}
Boris Pavlović
You could just use `points.hashCode()` for `TheObject`, since a `List` defines its hashcode in terms of its elements.
ColinD
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
+1 - This is the right approach, though maybe larger primes should be used.
Stephen C
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
Thanks, everyone!
Santiago Lezica
@Tony Ennis - What's wrong with an overflow here? http://stackoverflow.com/questions/892618/create-a-hashcode-of-two-numbers/892640#892640
Ishtar
@Ishtar I didn't participate in that thread, I don't really know what it's about.
Tony Ennis
@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
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
@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
@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
@Tony Ennis - Indeed. For hash codes, this would be just fine :)
Ishtar
+1  A: 

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
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