Algorithm to identify a unique free polyomino (or polyomino hash)

前端 未结 6 544
心在旅途
心在旅途 2021-02-02 16:13

In short: How to hash a free polyomino?

This could be generalized into: How to efficiently hash an arbitrary collection of 2D integer coordinate

6条回答
  •  梦如初夏
    2021-02-02 16:33

    You can reduce it down to 8 hash operations without the need to flip, rotate, or re-translate.

    Note that this algorithm assumes you are operating with coordinates relative to itself. That is to say it's not in the wild.

    Instead of applying operations that flip, rotate, and translate, instead simply change the order in which you hash.

    For instance, let us take the F pent above. In the simple example, let us presume the hash operation was something like this:

    int hashPolySingle(Poly p)
        int hash = 0
        for x = 0 to p.width
            fory = 0 to p.height
                hash = hash * 31 + p.contains(x,y) ? 1 : 0
        hashPolySingle = hash
    
    int hashPoly(Poly p)
        int hash = hashPolySingle(p)
        p.rotateClockwise() // assume it translates inside
        hash = hash * 31 + hashPolySingle(p)
        // keep rotating for all 4 oritentations
        p.flip()
        // hash those 4
    

    Instead of applying the function to all 8 different orientations of the poly, I would apply 8 different hash functions to 1 poly.

    int hashPolySingle(Poly p, bool flip, int corner)
        int hash = 0
        int xstart, xstop, ystart, ystop
        bool yfirst
        switch(corner)
            case 1: xstart = 0
                    xstop = p.width
                    ystart = 0
                    ystop = p.height
                    yfirst = false
                    break
            case 2: xstart = p.width
                    xstop = 0
                    ystart = 0
                    ystop = p.height
                    yfirst = true
                    break
            case 3: xstart = p.width
                    xstop = 0
                    ystart = p.height
                    ystop = 0
                    yfirst = false
                    break
            case 4: xstart = 0
                    xstop = p.width
                    ystart = p.height
                    ystop = 0
                    yfirst = true
                    break
            default: error()
        if(flip) swap(xstart, xstop)
        if(flip) swap(ystart, ystop)
    
        if(yfirst)
            for y = ystart to ystop
                for x = xstart to xstop
                    hash = hash * 31 + p.contains(x,y) ? 1 : 0
        else
            for x = xstart to xstop
                for y = ystart to ystop
                    hash = hash * 31 + p.contains(x,y) ? 1 : 0
        hashPolySingle = hash
    

    Which is then called in the 8 different ways. You could also encapsulate hashPolySingle in for loop around the corner, and around the flip or not. All the same.

    int hashPoly(Poly p)
        // approach from each of the 4 corners
        int hash = hashPolySingle(p, false, 1)
        hash = hash * 31 + hashPolySingle(p, false, 2)
        hash = hash * 31 + hashPolySingle(p, false, 3)
        hash = hash * 31 + hashPolySingle(p, false, 4)
        // flip it
        hash = hash * 31 + hashPolySingle(p, true, 1)
        hash = hash * 31 + hashPolySingle(p, true, 2)
        hash = hash * 31 + hashPolySingle(p, true, 3)
        hash = hash * 31 + hashPolySingle(p, true, 4)
        hashPoly = hash
    

    In this way, you're implicitly rotating the poly from each direction, but you're not actually performing the rotation and translation. It performs the 8 hashes, which seem to be entirely necessary in order to accurately hash all 8 orientations, but wastes no passes over the poly that are not actually doing hashes. This seems to me to be the most elegant solution.

    Note that there may be a better hashPolySingle() algorithm to use. Mine uses a Cartesian exhaustion algorithm that is on the order of O(n^2). Its worst case scenario is an L shape, which would cause there to be an N/2 * (N-1)/2 sized square for only N elements, or an efficiency of 1:(N-1)/4, compared to an I shape which would be 1:1. It may also be that the inherent invariant imposed by the architecture would actually make it less efficient than the naive algorithm.

    My suspicion is that the above concern can be alleviated by simulating the Cartesian exhaustion by converting the set of nodes into an bi-directional graph that can be traversed, causing the nodes to be hit in the same order as my much more naive hashing algorithm, ignoring the empty spaces. This will bring the algorithm down to O(n) as the graph should be able to be constructed in O(n) time. Because I haven't done this, I can't say for sure, which is why I say it's only a suspicion, but there should be a way to do it.

提交回复
热议问题