Algorithm 识别唯一自由PolyMino(或PolyMino哈希)的算法

Algorithm 识别唯一自由PolyMino(或PolyMino哈希)的算法,algorithm,hash,coordinates,Algorithm,Hash,Coordinates,简而言之:如何散列免费的PolyMino? 这可以概括为:如何有效地散列二维整数坐标的任意集合,其中一个集合包含唯一的非负整数对,并且当且仅当没有平移、旋转或翻转可以将其完全映射到另一个集合时,一个集合才被认为是唯一的? 对于不耐烦的读者,请注意我完全了解暴力手段。我在寻找一个更好的方法——或者一个非常令人信服的证据,证明没有其他方法可以存在 我正在研究一些不同的算法来生成随机变量。我想测试它们的输出,以确定它们的随机性——即给定顺序的某些实例比其他实例生成的频率更高。从视觉上看,很容易识别一个

简而言之:如何散列免费的PolyMino?

这可以概括为:如何有效地散列二维整数坐标的任意集合,其中一个集合包含唯一的非负整数对,并且当且仅当没有平移、旋转或翻转可以将其完全映射到另一个集合时,一个集合才被认为是唯一的?

对于不耐烦的读者,请注意我完全了解暴力手段。我在寻找一个更好的方法——或者一个非常令人信服的证据,证明没有其他方法可以存在

我正在研究一些不同的算法来生成随机变量。我想测试它们的输出,以确定它们的随机性——即给定顺序的某些实例比其他实例生成的频率更高。从视觉上看,很容易识别一个自由的Polymino的不同方向,例如,下面的维基百科插图显示了“F”pentomino()的所有8个方向:

如何在这个polymino上输入一个数字——也就是说,散列一个免费的polymino?我不想依赖于“命名”Polyminos的预先列表。无论如何,广泛一致的名称只适用于第4和第5号令

这不一定等同于枚举给定顺序的所有自由(或单边或固定)多米诺。我只想计算给定配置出现的次数。如果生成算法从未生成某个polymino,那么它将不会被计算在内

计数的基本逻辑是:

testcount = 10000 // Arbitrary
order = 6         // Create hexominos in this test
hashcounts = new hashtable
for i = 1 to testcount
    poly = GenerateRandomPolyomino(order)
    hash = PolyHash(poly)
    if hashcounts.contains(hash) then  
        hashcounts[hash]++
    else
        hashcounts[hash] = 1 
我要找的是一个高效的
PolyHash
算法。输入PolyMinos仅定义为一组坐标。T tetronimo的一个方向可以是,例如:

[[1,0], [0,1], [1,1], [2,1]]:

 |012
-+---
0| X
1|XXX
您可以假设输入PolyMino已被规范化,以便与X轴和Y轴对齐,并且只有正坐标。正式而言,每套:

  • 在x值为0时,将至少有1个坐标
  • y值为0时,将至少有1个坐标
  • 在x<0或y<0的位置没有任何坐标
我真的在寻找新的算法,以避免下面描述的一般暴力方法所需的整数运算数量的增加

暴力

建议使用蛮力解决方案,包括使用每个坐标作为二进制标志将每个集合散列为无符号整数,并取所有可能旋转(在我的例子中为翻转)的最小散列,其中每个旋转/翻转也必须转换为原点。这将导致每个输入集总共进行23次集合操作,以获得“自由”散列:

  • 旋转(6倍)
  • 翻转(1x)
  • 翻译(7x)
  • 散列(8x)
  • 查找计算哈希的最小值(1x)
其中,获取每个散列的操作序列为:

  • 散列
  • 旋转、平移、散列
  • 旋转、平移、散列
  • 旋转、平移、散列
  • 翻转、翻译、散列
  • 旋转、平移、散列
  • 旋转、平移、散列
  • 旋转、平移、散列

  • 如果你真的害怕散列冲突,一个有效的散列函数是为坐标生成一个散列函数x+order*y,然后循环遍历一个工件的所有坐标,将(order^i)*散列(coord[i])添加到工件散列中。这样,您就可以保证不会发生任何哈希冲突。

    我最近也处理过同样的问题。我用简单的方法解决了这个问题 (1) 为PolyMino生成唯一的ID,这样每个相同的poly将具有相同的UID。例如,查找边界框,规格化边界框的角,并收集非空单元格集。 (2) 通过旋转(和翻转,如果合适的话)Polymino生成所有可能的排列,并寻找重复的排列

    除了简单之外,这种野蛮方法的优点是,如果
    多边形可以通过其他方式进行区分,例如,如果其中一些多边形是彩色或编号的。

    您可以将其减少为8个哈希操作,而无需翻转、旋转或重新转换

    请注意,此算法假定您使用的是相对于自身的坐标。也就是说它不是在野外

    与其应用翻转、旋转和平移操作,不如简单地更改散列的顺序

    例如,让我们以上面的F pent为例。在这个简单的示例中,我们假设哈希操作是这样的:

    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
    
    我将对1个多边形应用8个不同的散列函数,而不是将函数应用于多边形的所有8个不同方向

    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
    
    然后以8种不同的方式调用。您还可以将hashPolySingle封装在拐角处的for循环中,以及翻转与否。尽管如此

    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
    
    通过这种方式,可以隐式地从每个方向旋转多边形,但实际上并没有执行旋转和平移。它执行8个散列,这似乎是完全必要的,以便准确地散列所有8个方向,但不浪费任何经过多边形的过程,而多边形实际上没有进行散列。在我看来,这是最优雅的解决方案

    请注意,可能有更好的hashPolySingle()算法可供使用。Mine使用笛卡尔穷举算法,其顺序为
    O(n^2)
    。其最坏情况是L形,这将导致只有
    N
    元素才会出现
    N/2*(N-1)/2
    大小的正方形,或者效率为
    1:(N-1)/4
    ,而I形为
    1:1
    。也可能是架构强加的固有不变量实际上会使其效率低于原始算法

    我的怀疑是,通过模拟笛卡尔穷举,将节点集转换为可遍历的双向图,从而导致节点被击中,可以缓解上述问题
    2,2,3,1,2,2,3,2,2,3,2,1
    
    1,2,2,3,1,2,2,3,2,2,3,2
    
       1- 2- 2- 3- 1- 2- 2- 3- 2- 2- 3- 2
    = 01-10-10-11-01-10-10-11-10-10-11-10
    = 00000000011010110110101110101110
    = 0x006B6BAE
    
        1*3^11 + 2*3^10 + 2*3^9 + 3*3^8 + 1*3^7 + 2*3^6 
      + 2*3^5  + 3*3^4  + 2*3^3 + 2*3^2 + 3*3^1 + 2*3^0
    = 0x0005795F