找到3x3打孔的所有组合

时间:2011-10-04 20:05:38

标签: algorithm combinations

我参加了一个狂欢节,在每个地方,他们用特殊的打孔标记你的节目。打孔器是3x3空间的网格。在每个空间中,有一个针刺穿你的纸张或没有。这让我想知道你可以使用这个工具制作多少种不同的模式。我的第一个想法是:2 ^ 9 = 512,但是所有9个空间都是无针的并不是真正的一拳,所以真的:511。

然后复杂性打击了我。特别是因为工人在打纸时并不是那么小心,所以这些看起来都很明显:

x..  .x.  ...  etc.
.x.  x..  .x.
...  ...  ..x

问题:如何编写测试以考虑轮换和转移?


到目前为止的勤奋和思想:

  • Binary感觉就像这个等式的明显部分
  • 当找到一个独特的模式时,将其存储在内存中,以便可以对其进行未来的模式测试
  • 有4种旋转可能性。
    编辑:我所说的“旋转”是指你可以采取任何形状并将其旋转90度。考虑左上角是点的图案。您可以将其旋转/旋转90度并获得右上角的点。再次这样做,它在右下方。再次,它在左下角。使用纯2 ^ 9计算,这些是4种不同的组合。然而,对于这个问题,这些正是我试图清除的那种重复。
  • 对于每次轮换,有25种方法可以使3x3网格重叠:

重叠:

/ = the spaces in the new one to test
\ = the spaces in a verified unique one

1               2               25
/ / / . . . .   . / / / . . .   . . . . . . .
/ / / . . . .   . / / / . . .   . . . . . . .
/ / X \ \ . .   . / X X \ . .   . . \ \ \ . .
. . \ \ \ . .   . . \ \ \ . .   . . \ \ \ . .
. . \ \ \ . .   . . \ \ \ . .   . . \ \ X / /
. . . . . . .   . . . . . . .   . . . . / / /
. . . . . . .   . . . . . . .   . . . . / / /
  • 如果任一模式包含不在重叠区域中的引脚,则不需要测试重叠。按位AND可以帮到这里。
  • 如果您将2个模式中的每个模式的每个位置都设为字符串,则可以检查是否相等
  • 这两个想法可以合并以提高效率吗?

7 个答案:

答案 0 :(得分:7)

我们只需要考虑第一行和第一列中有冲压的模式。如果第一行为空,则可以向上移动模式。如果第一列为空,则可以向左移动图案。在任何一种情况下,我们都可以得到一个类似的模式,我们会考虑。

对于这些模式,我们需要检查旋转的版本是否相同。我们通过应用最多三个90度旋转来实现这一点,可能向左移动以移除前导空列(第一行永远不会为空)并找到具有最低数值的模式。

然后我们可以将此值添加到哈希集中,该哈希集只保留唯一值。

不包括空模式,因为它的所有行都是空的。

为了实现这一点,我们将模式编码为连续位:

012
345
678

我们需要的操作大多非常简单:

Test for an empty row:    (n & 7) == 0     // bits 0,1,2 not set
Test for an empty column: (n & 73) == 0    // bits 0,3,6 not set
Shift pattern up:         n -> (n >> 3)
Shift pattern left:       n -> (n >> 1)

最棘手的部分是旋转,它实际上只是重新排列所有位:

n -> ((n & 1) << 2) + ((n & 2) << 4) + ((n & 4) << 6)
   + ((n & 8) >> 2) + (n & 16) + ((n & 32) << 2)
   + ((n & 64) >> 6) + ((n & 128) >> 4) + ((n & 256) >> 2);

在C#中:

public static int Count3x3() {
    HashSet<int> patterns = new HashSet<int>();
    for (int i = 0; i < 512; i++) {
        if ((i & 7) == 0 || (i & 73) == 0)
            continue;
        int nLowest = i;
        int n = i;
        do {
            nLowest = Math.Min(nLowest, n);
            n = ((n & 1) << 2) + ((n & 2) << 4) + ((n & 4) << 6)
                + ((n & 8) >> 2) + (n & 16) + ((n & 32) << 2)
                + ((n & 64) >> 6) + ((n & 128) >> 4) + ((n & 256) >> 2);
            while ((n & 73) == 0)
                n >>= 1;
        } while (n != i);
        patterns.Add(nLowest);
    }
    return patterns.Count;
}

此函数返回116.我的机器所用时间为0.023毫秒。

编辑:使用4次观察可以获得额外7倍的提升:

  1. 我们可以使用简单的访问数组而不是哈希集。如果之前看过一个模式,我们就不算数了。这也消除了跟踪内环中“最低”模式的需要。如果访问了模式,那么也会访问其最低旋转模式。
  2. 如果我们没有180度旋转对称,那么第3次旋转将不会产生原始图案。总是第四次旋转,所以没必要。
  3. 旋转表达式可以略微简化。
  4. 因此,如果我们应用这些观察并展开内部do循环,我们得到以下结果:

    static int Rotate(int n) {
        n = ((n & (1+32)) << 2) + ((n & 2) << 4) + ((n & 4) << 6)
            + ((n & (8+256)) >> 2) + (n & 16)
            + ((n & 64) >> 6) + ((n & 128) >> 4);
        while ((n & 73) == 0) 
            n >>= 1;
        return n;
    }
    public static int Count3x3_3() {
        bool[] visited = new bool[512];
        int count = 0, r;
        for (int i = 0; i < 512; i++) {
            if (visited[i])
                continue;
            if ((i & 7) == 0 || (i & 73) == 0)
                continue;
            count++;
            if ((r = Rotate(i)) == i) continue;
            visited[r] = true;
            if ((r = Rotate(r)) == i) continue;
            visited[r] = true;
            visited[Rotate(r)] = true;
        }
        return count;
    }
    

    在同一台机器上运行大约3μs。

答案 1 :(得分:3)

我的解决方案:116种独特的形状

当测试2个形状是否相等时,比较引脚数可以节省大量时间。但我最大的突破是意识到所有这25个位置都可以被这个替换:要检查两个3x3形状中的每一个是否相等,用两个零连接线,然后修剪前导零和尾随零。 concat零是为了防止环绕。例如:

010 => 01000 => 0100010100000 => 1000101
101    10100
000    000

000 => 00000 => 0000001000101 => 1000101
010    01000
101    101

然后只测试结果是否相等。这是4个简单的迭代(每个旋转1个)而不是100个(25个位置* 4个旋转)更复杂的迭代。


时间:
仅字符串:

  • 蛮力,每次轮换所有25个位置:2018ms
  • ... 00 ... 00 ...修剪:75ms
  • 更多优化:59毫秒

OOP和更好的缓存:17毫秒

答案 2 :(得分:1)

首先,除了翻译之外,我们可以查看两个相同的旋转,作为彼此的旋转。想象一下,打孔图案位于球体表面上:我们可以通过沿着水平轴和垂直轴旋转球体来“平移”它(就像它握在我们手中一样。)

我们在这里沿着第三个剩余轴旋转我们的球体,也可以捕获两个相当于旋转的冲头(如90度转弯)。

现在我们已经将问题简化为“球体表面有多少独特的打孔图案,直到旋转?”对于像这样对称的唯一对象进行计数,您需要not-Burnside's LemmaThis book是一本很好的入门书。

答案 3 :(得分:1)

我认为这不像球体情况,因为你不能围绕边缘旋转? IE:

XOO
XXO
XOO

不同
OOX
XOX
OOX

我试着在纸上手工计数,看看我得到了什么。考虑2x2情况 - 你有1个0点,1个有1个点,2个有2个点(相邻或对角线),1个有3个点,1个有4个;总共5个(如果忽略空的情况,则为4个)。请注意,枚举是对称的,因为将空白计为完全空格是相同的。对于3x3的情况,我得到了这个:

C(0) = 1
C(1) = 1
C(2) = 5
C(3) = 10
C(4) = 21

然后通过对称性,21,10,5,1,1

我得到了76.我很容易错位,特别是在4/5的情况下。

我能想到自动枚举这些的唯一方法是移动和旋转模式以查看它们是否与先前列举的模式相匹配。移动是棘手的,因为你只能移动直到你“碰撞”边缘。

答案 4 :(得分:1)

值得指出的是,如果你真的需要每个形状“看起来”独特,无论它如何旋转或移动,你都很少有选择。例如,单个打孔,无论它在网格中的哪个位置,都将始终看起来相同。此外,假设方形网格和圆形销,并假设较小的间距差异(√2)是微不足道的,那么连续2个对角线的孔将看起来与两个相邻的销钉相同,因为所有观察者看到的是靠近的两个孔。同样,对角线中的3个看起来就像直线上的3个,这极大地限制了您的选择。

请注意,形状可能比我们之后的组合更好,因为我们不关心实际组合是什么,只是结果是什么形状在纸上。

我认为我们可以假定无论形状如何,它都可以旋转和移动,使得左上方的针被打孔(特别是如果允许在45度上旋转),这使我们能够缩小搜索范围更深入。我们使用以下规则来实现这一目标:

  1. 如果有任何角落被打孔,请旋转网格,直到打孔角落在左上角
  2. 否则将图案向上和向左移动。
  3. 重复步骤1
  4. 如果我们走得这么远,那么我们知道只有顶部中间位置被打孔(因为我们知道两个角都没有),在这种情况下我们将图案旋转45度,使得顶部中间现在是左上角。 QED。
  5. 我做了一个非常快速的纸笔蛮力搜索可能的形状,看起来可行选项的列表非常小,你可以在几分钟内将它们全部列出。

答案 5 :(得分:1)

您不是要求计算转换和旋转之前的唯一模式的数量,而是要求进行一致性测试。

选择3x3网格的位串表示。我会逐行选择,自上而下。通过设置相应孔被打孔的位,我们现在可以将9位整数映射到打孔模式(反之亦然。)

对于任何特定的表示,我们可以设计代表旋转和翻译的比特操作。有些翻译在某些模式上是非法的,因为我们希望避免“缠绕”。

正如轮换和翻译是可逆的一样,我们的运营也是如此。如果两个动作将模式A映射到B,然后映射到B到C,我们当然可以组成动作来进行转换,将A转换为C.不执行任何操作(身份转换)也是合法的,因此我们可以从A.可达性到达A.因此,通过变换是打孔模式的等价关系,因此等效模式对空间进行划分。

有一种为每个打孔模式分配正整数分数的方法,我们可以调用有序的原则:包含模式的等价类将包含最低分数的唯一模式(包括翻译和旋转)。我们将选择这种最小模式作为等价类的代表。如果两个模式具有相同的等价类代表,则它们必然是一致的。如果他们不这样做,他们必然不一致。

鉴于一种模式,我们如何找到其最小权重代表?通过检查,贪婪的算法不能保证工作。我们可以找到一个无数的启发式优化算法,或者我们可以注意到我们只推了9位并且穷尽地搜索空间。应该注意的是,出于同样的原因,这很好地计算了一次,并且永远地在查找表中推送。

这是详尽的搜索。注意适当的缓存它仍然很快(不到一秒钟。)

#!/usr/bin/env ruby

require 'set'

class PunchPattern < String
  @@representatives = Hash.new do |h, k|
    equivalence_class = k.closure
    representative = equivalence_class.min

    k.closure.each do |node|
      h[node] = representative
    end

    representative
  end

  def initialize(s)
    raise "9 digits of 0 and 1, pls" unless s =~ /[01]{9}/
    super
  end

  def left
    return nil unless self =~ /0..0..0../
    PunchPattern.new([self[1...3], 0, self[4...6], 0, self[7...9], 0].join)
  end

  def right
    return nil unless self =~ /..0..0..0/
    PunchPattern.new([0, self[0...2], 0, self[3...5], 0, self[6...8]].join)
  end

  def up
    return nil unless self =~ /000....../
    PunchPattern.new([self[3...9], 0, 0, 0].join)
  end

  def down
    return nil unless self =~ /......000/
    PunchPattern.new([0, 0, 0, self[0...6]].join)
  end

  def turn
    PunchPattern.new([2, 5, 8, 1, 4, 7, 0, 3, 6].collect { |i| self[i].chr }.join)
  end

  def closure
    seen = Set.new([])
    frontier = Set.new([self])

    until frontier.empty?
      node = frontier.first
      frontier.delete(node)
      seen.add(node)

      %w{left right up down turn}.collect do |motion|
        node.send motion
      end.compact.each do |neighbor|
        frontier.add(neighbor) unless seen.include? neighbor
      end
    end

    seen
  end

  def representative
    self.class.representatives[self]
  end

  def self.representatives
    @@representatives
  end

end

(0...512).collect do |p|
  p = PunchPattern.new(p.to_s(2).rjust(9, '0'))
  [p.representative, p]
end.inject(Hash.new { |h, k| h[k] = [] }) do |h, pair|
  h[pair.first] << pair.last
  h
end.sort_by { |pair| pair.first }.each do |representative, patterns|
  puts patterns.collect { |p| p[0...3] + " " }.join
  puts patterns.collect { |p| p[3...6] + " " }.join
  puts patterns.collect { |p| p[6...9] + " " }.join
  puts
end

puts "#{PunchPattern.representatives.values.uniq.size} total congruence classes"

可生产

$ ./congruence.rb 
000 
000 
000 

000 000 000 000 000 000 001 010 100 
000 000 000 001 010 100 000 000 000 
001 010 100 000 000 000 000 000 000 

000 000 000 000 000 000 000 001 010 011 100 110 
000 000 001 010 011 100 110 001 010 000 100 000 
011 110 001 010 000 100 000 000 000 000 000 000 

000 000 001 010 100 101 
000 101 000 000 000 000 
101 000 001 010 100 000 

000 000 001 010 100 111 
000 111 001 010 100 000 
111 000 001 010 100 000 

000 000 000 000 001 010 010 100 
001 010 010 100 010 001 100 010 
010 001 100 010 000 000 000 000 

000 000 000 000 000 000 000 000 001 010 010 011 011 100 110 110 
001 010 010 011 011 100 110 110 011 011 110 001 010 110 010 100 
011 011 110 001 010 110 010 100 000 000 000 000 000 000 000 000 

000 001 010 100 
001 100 000 000 
100 000 001 010 

000 000 001 010 011 100 101 110 
001 101 101 000 000 000 100 000 
101 100 000 011 001 110 000 010 

000 000 001 010 010 011 100 100 
001 011 110 001 010 100 010 100 
110 100 000 001 001 000 010 010 

000 000 001 010 011 100 110 111 
001 111 111 010 001 100 010 100 
111 100 000 011 001 110 010 000 

000 000 001 010 010 010 100 101 
010 101 010 001 100 101 010 010 
101 010 001 010 010 000 100 000 

000 000 001 010 010 010 100 111 
010 111 011 011 110 111 110 010 
111 010 001 010 010 000 100 000 

000 000 011 110 
011 110 011 110 
011 110 000 000 

000 000 010 011 011 100 101 110 
011 101 001 010 101 010 110 100 
101 110 011 001 000 110 000 010 

000 010 011 100 
011 011 110 110 
110 001 000 010 

000 000 010 011 011 100 110 111 
011 111 011 011 111 110 110 110 
111 110 011 001 000 110 010 000 

000 001 010 100 
100 000 000 001 
001 010 100 000 

000 000 001 001 010 010 100 110 
100 110 001 010 010 100 011 001 
011 001 010 010 100 100 000 000 

000 000 001 010 011 100 101 110 
100 101 000 000 000 101 001 000 
101 001 011 110 010 000 000 100 

000 000 001 010 011 100 110 111 
100 111 001 010 010 111 100 001 
111 001 011 110 010 000 100 000 

000 000 001 010 011 101 110 110 
101 110 010 100 001 011 010 101 
011 101 011 110 010 000 100 000 

000 011 101 110 
101 000 101 000 
101 011 000 110 

000 000 011 011 101 110 110 111 
101 111 001 010 111 010 100 101 
111 101 011 011 000 110 110 000 

000 001 010 110 
110 011 110 011 
011 010 100 000 

000 000 001 010 011 110 110 111 
110 111 011 110 011 110 111 011 
111 011 011 110 010 100 000 000 

000 011 110 111 
111 011 110 111 
111 011 110 000 

001 100 
000 000 
100 001 

001 100 101 101 
000 000 000 000 
101 101 001 100 

001 011 100 100 
000 000 001 100 
110 100 001 001 

001 100 101 111 
000 100 001 000 
111 101 001 100 

001 001 100 110 
001 100 000 000 
100 100 011 001 

001 100 101 111 
001 000 100 000 
101 111 100 001 

001 011 100 110 
001 100 100 001 
110 100 011 001 

001 100 111 111 
001 100 001 100 
111 111 001 100 

001 100 
010 010 
100 001 

001 100 101 101 
010 010 010 010 
101 101 001 100 

001 011 100 100 
010 010 011 110 
110 100 001 001 

001 100 101 111 
010 110 011 010 
111 101 001 100 

001 001 100 110 
011 110 010 010 
100 100 011 001 

001 100 101 111 
011 010 110 010 
101 111 100 001 

001 011 100 110 
011 110 110 011 
110 100 011 001 

001 100 111 111 
011 110 011 110 
111 111 001 100 

001 010 100 101 
100 000 001 000 
001 101 100 010 

001 010 010 100 
100 001 100 001 
010 100 001 010 

001 010 101 110 
100 100 001 001 
011 101 010 100 

001 101 101 110 
100 000 001 000 
101 011 100 101 

001 011 100 110 
100 001 001 100 
110 100 011 001 

001 101 110 111 
100 001 100 001 
111 011 101 100 

001 010 100 111 
101 000 101 000 
001 111 100 010 

001 010 010 110 
101 100 101 001 
010 011 100 010 

001 010 110 111 
101 100 101 001 
011 111 100 010 

001 110 
101 000 
100 011 

001 101 110 111 
101 101 000 000 
101 100 111 011 

001 011 110 110 
101 101 001 100 
110 100 011 011 

001 110 111 111 
101 100 001 101 
111 111 011 100 

001 010 100 101 
110 010 011 010 
001 101 100 010 

001 010 010 100 
110 011 110 011 
010 100 001 010 

001 010 101 110 
110 110 011 011 
011 101 010 100 

001 101 101 110 
110 010 011 010 
101 011 100 101 

001 011 100 110 
110 011 011 110 
110 100 011 001 

001 101 110 111 
110 011 110 011 
111 011 101 100 

001 010 100 111 
111 010 111 010 
001 111 100 010 

001 010 010 110 
111 110 111 011 
010 011 100 010 

001 010 110 111 
111 110 111 011 
011 111 100 010 

001 110 
111 010 
100 011 

001 101 110 111 
111 111 010 010 
101 100 111 011 

001 011 110 110 
111 111 011 110 
110 100 011 011 

001 110 111 111 
111 110 011 111 
111 111 011 100 

010 011 100 101 
001 100 001 100 
101 001 110 010 

010 010 011 100 
001 101 100 101 
110 001 010 010 

010 011 100 111 
001 101 101 100 
111 001 110 010 

010 011 100 101 
011 110 011 110 
101 001 110 010 

010 010 011 100 
011 111 110 111 
110 001 010 010 

010 011 100 111 
011 111 111 110 
111 001 110 010 

010 
101 
010 

010 010 011 110 
101 101 101 101 
011 110 010 010 

010 011 101 110 
101 100 101 001 
101 011 010 110 

010 011 110 111 
101 101 101 101 
111 011 110 010 

010 
111 
010 

010 010 011 110 
111 111 111 111 
011 110 010 010 

010 011 101 110 
111 110 111 011 
101 011 010 110 

010 011 110 111 
111 111 111 111 
111 011 110 010 

011 100 101 101 
000 001 000 100 
101 101 110 001 

011 100 
000 101 
110 001 

011 100 101 111 
000 101 101 000 
111 101 001 110 

011 100 101 111 
001 001 100 100 
101 111 110 001 

011 011 100 110 
001 100 101 101 
110 110 011 001 

011 100 111 111 
001 101 100 101 
111 111 110 001 

011 100 101 101 
010 011 010 110 
101 101 110 001 

011 100 
010 111 
110 001 

011 100 101 111 
010 111 111 010 
111 101 001 110 

011 100 101 111 
011 011 110 110 
101 111 110 001 

011 011 100 110 
011 110 111 111 
110 110 011 001 

011 100 111 111 
011 111 110 111 
111 111 110 001 

011 101 101 110 
100 001 100 001 
101 110 011 101 

011 101 110 111 
100 101 101 001 
111 011 101 110 

011 101 110 111 
101 101 001 100 
101 110 111 011 

011 110 
101 101 
110 011 

011 110 111 111 
101 101 101 101 
111 111 011 110 

011 101 101 110 
110 011 110 011 
101 110 011 101 

011 101 110 111 
110 111 111 011 
111 011 101 110 

011 101 110 111 
111 111 011 110 
101 110 111 011 

011 110 
111 111 
110 011 

011 110 111 111 
111 111 111 111 
111 111 011 110 

101 
000 
101 

101 101 101 111 
000 001 100 000 
111 101 101 101 

101 101 111 111 
001 100 001 100 
111 111 101 101 

101 
010 
101 

101 101 101 111 
010 011 110 010 
111 101 101 101 

101 101 111 111 
011 110 011 110 
111 111 101 101 

101 111 
101 000 
101 111 

101 111 111 111 
101 001 100 101 
111 111 111 101 

101 111 
111 010 
101 111 

101 111 111 111 
111 011 110 111 
111 111 111 101 

111 
101 
111 

111 
111 
111 

117 total congruence classes

..为117个班级。

答案 6 :(得分:0)

我不明白这部分是关于轮换的,但对于这种情况:

有3x3 = 9个洞和10个案例,每次只能发生一个案例:

Case 1 = no holes
Case 2 = one hole
...
Case 10 = 9 holes

然后使用组合公式C(n,k):

  总计= C(9,0)+ C(9,1)+ ...... + C(9,9)

这是对n个元素的不同组合求和。

  

Totol = 1 + 9 + 36 + 84 + 126 + 126 + 84 + 36 + 9 + 1 = 512

我认为你是对的,512似乎是正确的,理论上无针的定义也是一个组合,如果你不想要它只是删除它得到511.

所有这些情况都是单独发生的,因此我们添加了不同的案例。

如果它们是同步发生的,例如计算3名雇员冲压纸张的组合或计算一个雇员3次标记纸张的组合,那么它会变得更复杂,应该是512 * 512 * 512 nxm规则。

简单显示中的m * n规则是:

我有4个口袋,两个= n手:

  

my_left * 4(口袋)= 4 my_right * 4(口袋)= 4

     

总= 4 + 4 = 4 * 2 = m * n

一次只有一只手可以进入口袋,或者只有一名雇员的一个组合只与另一名雇员的一个组合配对,这就是我们采用m * n规则的确切原因。

这就是我的想法,我不是数学家,也不是100%正确的说法,这正是我从概率课程中记得的。

我并不认为100%正确,这是我记得的概率课程。


至于模式重叠,检查模式是否已经找到等我根本不会打扰,因为伪代码中存在如此多的生成组合的算法。因为它们生成然后不需要检查重叠或任何东西。

毕竟这就是generat意味着它是先验设计找到所有结果,让它运行。

我曾经发现一种比简单组合更难得的算法,当我把它变成php时,它完美地完成了工作,无需担心重叠或任何事情。