find3x3 holepunch的所有组合

我在嘉年华会,在每个地点他们用特别的打孔器来标记你的节目。 打孔器是一个3×3的格子。 在每个空间里,都有一个钉住你的纸或没有。 这让我想知道你可以使用这个工具有多less种不同的模式。 我的第一个想法是:2 ^ 9 = 512,但是所有9个空格都不是真正的一拳,所以真的:511。

那么复杂性打击了我。 特别是因为工作人员在打纸时不是很小心,所以这些都是理所当然的:

x.. .x. ... etc. .xx. .x. ... ... ..x 

问题: 如何编写testing来解释轮换和移位?


目前的努力和想法:

  • 二进制感觉是这个等式的一个显而易见的部分
  • 当find一个唯一的模式,将其存储在内存中,以便将来的模式可以被testing
  • 有4种旋转的可能性。
    编辑:我的意思是“旋转”,你可以采取任何形状,把它转过90度。 考虑左上angular的点阵模式。 你可以转动/旋转90度,并得到在右上angular的点。 再次这样做,它在右下angular。 再次,它在左下angular。 使用纯粹的2 ^ 9计算,这些是4种不同的组合。 但是,对于这个问题,这些正是我试图去除的重复。
  • 对于每次旋转,有25种方法可以使3×3网格重叠:

重叠:

 / = the spaces in the new one to test \ = the spaces in a verified unique one 1 2 25 / / / . . . . . / / / . . . . . . . . . . / / / . . . . . / / / . . . . . . . . . . / / X \ \ . . . / XX \ . . . . \ \ \ . . . . \ \ \ . . . . \ \ \ . . . . \ \ \ . . . . \ \ \ . . . . \ \ \ . . . . \ \ X / / . . . . . . . . . . . . . . . . . . / / / . . . . . . . . . . . . . . . . . . / / / 
  • 如果任一模式包含不在重叠区域的引脚,则不需要testing重叠。 按位与可以帮助这里。
  • 如果你将每个2个模式的每个位置变成string,你可以检查是否相等
  • 这两个想法可以结合起来提高效率吗?

我们只需要考虑在第一行和第一列中有冲突的模式。 如果第一行是空的,模式可以上移。 如果第一列是空的,模式可以左移。 在任何一种情况下,我们都可以推导出一个我们考虑过的类似的模式。

对于这些模式,我们需要检查旋转的版本是否相同。 我们通过应用三个90度的旋转来实现这一点,可能左移删除前导空列(第一行从不空),并find数值最小的模式。

然后,我们可以将这个值添加到一个散列集,它将只保留唯一的值。

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

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

 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.023ms。

编辑 :您可以通过使用4个观察获得额外的7倍改进:

  1. 我们可以使用一个简单的访问数组而不是一个哈希集。 如果以前看过一个模式,我们就不算数了。 这也消除了跟踪内循环中“最低”模式的需要。 如果访问了一个模式,那么访问它的最低旋转模式。
  2. 如果我们没有180度旋转对称,那么第3次旋转将不会产生原始模式。 第四次轮换总是这样,所以没有必要。
  3. 旋转expression式可以稍微简化。

所以,如果我们应用这些观察并展开内部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。

我的解决scheme:116独特的形状

当testing两个形状相等时,比较引脚数量可以节省很多时间。 但是我最大的突破是意识到所有这25个位置都可以被这个取代:为了检查两个3×3形状中的每一个形状是否相等,将这些行与两个零连接,然后修剪前导零和尾随零。 连续零是为了防止环绕。 例:

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

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


时间:
仅string:

  • 蛮力,每个旋转的所有25个位置:2018毫秒
  • … 00 … 00 …修剪:75ms
  • 更多优化:59ms

OOP和更好的caching:17毫秒

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

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

现在我们已经将问题简化为“球体表面有多less独特的打孔模式,直到旋转? 为了计算像这样对称的唯一对象,你不需要Burnside的引理 。 这本书是一本不错的入门书 。

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

 XOO XXO XOO 

是不一样的

 OOX XOX OOX 

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

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

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

我得到76.我可以很容易地错误,尤其是在4/5的情况下。

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

值得指出的是,如果你真的需要每一个形状来“看”独特的,不pipe如何旋转或移动,你有很less的select。 例如,无论在网格中的哪个位置,单击都将看起来相同。 此外,假设一个正方形网格和圆形引脚,并且假设小的间距差(√2)是微不足道的,那么连续的两个孔将看起来与两个相邻的引脚相同,因为所有观察者看到的都是两个相邻的孔。 同样,对angular线上的3将看起来就像直线上的3,这大大限制了你的select。

请注意, 形状可能是一个更好的词,因为我们不关心实际的组合是什么,只是在纸上得到的形状是什么。

我想我们可以假设不pipe形状如何,都可以旋转和移动左上angular的销钉(特别是如果你允许在45度上旋转),这使我们可以进一步缩小我们的search范围。 我们使用以下规则来完成此操作:

  1. 如果有任何angular落被打孔,请旋转网格直到打孔的angular落位于左上angular
  2. 否则,将模式尽可能远地移动,然后离开。
  3. 重复步骤1
  4. 如果我们得到这一点,那么我们知道只有顶部中间位置被打孔(因为我们知道两个angular都不是),在这种情况下,我们将图案旋转45度,使顶部中间位置成为左上angular。 QED。

我做了一个非常快速的笔和纸强制search可能的形状,似乎可行的选项列表是如此之小,你可以在几分钟内全部列出。

你不是要求统计直到翻译和旋转的独特模式的数量,而是要求一致性testing。

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

对于任何特定的表示forms,我们可以设法表示旋转和翻译的位移操作。 有些翻译对于某些模式是不合法的,因为我们希望避免“包装”。

正如旋转和翻译是可逆的,我们的行动也是如此。 如果两个运动将模式A映射到B,然后将B映射到C,那么我们当然可以将运动组合为A到C的转换。无所作为(身份转换)也是合法的,所以我们可以从A到达A.可达性因此通过变换是冲突模式的等价关系,所以等价模式划分空间。

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

给定一个模式,我们如何find最轻量级的代表? 通过检查,贪婪algorithm不能保证工作。 我们可以find很多启发式优化algorithm中的一种,或者我们可以注意到我们只是推动9位,彻底地search空间。 应该指出的是,同样的道理,这很好地被计算一次,并永远推到一个查找表中。

这是详尽的search。 注意适当的caching它仍然相当快(不到一秒钟)。

 #!/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 

..为117class。

我不清楚这一部分关于轮换,但是对于这种情况:

有3×3 = 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个元素的k个不同组合进行求和。

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

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

所有这些情况都是分开发生的,所以我们添加不同的情况

如果要同步发生,例如计算一次打孔纸张的三名员工的组合或一名员工计算三次打印纸张的组合,则其变得更复杂,并且应该是nxm规则的512 * 512 * 512。

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

我有4 = m口袋和2 = n手:

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

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

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

这就是我想的,我不是math家,也不是100%正确的,这正是我从概率过程中所记得的。

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


至于模式重叠,检查模式已经find等我不会打扰,因为在伪代码中有这么多的algorithm,产生组合。 由于他们产生,那么不需要检查重叠或任何东西。

毕竟这是什么generat意味着它被devise先验find所有的结果,只是让它运行。

我发现一次比简单的组合更罕见的algorithm,当我把它转换为PHP它完美的工作,没有必要担心重叠或任何事情。