找到 3x3 打孔的所有组合

发布于 2024-12-08 07:38:42 字数 1273 浏览 2 评论 0原文

我参加了一个嘉年华,在每个地点,他们都会用特殊的打孔器标记您的节目。打孔器是一个 3x3 空间的网格。在每个空间中,要么有一根大头针刺破你的纸,要么没有。这让我想知道你可以用这个工具制作多少种不同的图案。我的第一个想法是:2^9 = 512,但是所有 9 个空格都是无针的并不是真正的一拳,所以真的:511。

然后复杂性让我震惊。特别是因为工作人员在打孔时并不那么小心,所以这些看起来都是一样的:

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

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


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

  • 二进制感觉像是这个方程的一个明显的部分。
  • 当找到一个独特的模式时,将其存储在内存中,以便可以针对它测试未来的模式
  • 。有 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 / /
. . . . . . .   . . . . . . .   . . . . / / /
. . . . . . .   . . . . . . .   . . . . / / /
  • 如果任一图案包含不在重叠区域中的引脚,则无需测试重叠。按位与可以在这里提供帮助。
  • 如果将 2 个模式中的每一个的每个位置都放入字符串中,则只需检查相等性
  • 即可 将前面的两个想法结合起来以提高效率吗?

I was at a carnival where at each location they mark your program with a special hole punch. The hole punch is a grid of 3x3 spaces. In each space, there's either a pin that punctures your paper or there isn't. This got me to wondering how many different patterns you could make with this tool. My first thought was: 2^9 = 512, but all 9 spaces being pinless isn't really a punch, so really: 511.

Then the complexity hit me. Especially since the workers aren't all that careful when they punch your paper, these would all look idential:

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

Question: How could a test be written to account for rotation and shifting?


Diligence and thoughts so far:

  • Binary feels like an obvious part of this equation
  • When a unique pattern is found, store it in memory so future patterns can be tested against it
  • There are 4 rotation possibilities.
    Edit: what I mean by "rotations" is that you can take any shape and turn it 90 degrees. Consider the pattern that is a dot in the upper left corner. You can turn/rotate it 90 degrees and get the dot in the upper right corner. Do this again and it's in the lower right. Again and it's in the lower left. Using the pure 2^9 calculation, these are 4 different combinations. For this problem however, these are exactly the kind of duplicates I'm trying to weed out.
  • For each rotation, there are 25 ways to make 3x3 grids overlap:

Overlaps:

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

1               2               25
/ / / . . . .   . / / / . . .   . . . . . . .
/ / / . . . .   . / / / . . .   . . . . . . .
/ / X \ \ . .   . / X X \ . .   . . \ \ \ . .
. . \ \ \ . .   . . \ \ \ . .   . . \ \ \ . .
. . \ \ \ . .   . . \ \ \ . .   . . \ \ X / /
. . . . . . .   . . . . . . .   . . . . / / /
. . . . . . .   . . . . . . .   . . . . / / /
  • An overlap doesn't need to be tested if either pattern contains a pin that isn't in the overlap area. Bitwise AND could help here.
  • If you make each position for each of the 2 patterns into strings, you can just check for equality
  • Can these previous two ideas be combined to increase efficiency?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

吻泪 2024-12-15 07:38:42

我们只需要考虑在第一行和第一列有冲孔的图案。如果第一行为空,则模式可以上移。如果第一列为空,则模式可以向左移动。无论哪种情况,我们都可以得出我们确实考虑的类似模式。

对于这些模式,我们需要检查旋转后的版本是否相同。为此,我们应用最多 3 个 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.023ms。

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

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

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

We need to only consider patterns that have punches in the first row and column. If the first row is empty, the pattern can be shifted up. If the first column is empty, the pattern can be shifted left. In either case, we can derive a similar pattern that we do consider.

For these patterns, we need to check if the rotated versions are identical. We do this by applying up to three 90 degree rotations, possibly shifting left to remove leading empty columns (the first row is never empty) and finding the pattern with the lowest numeric value.

We can then add this value to a hash set, which will only keep unique values.

The empty pattern is not included because all its rows are empty.

To implement this, we encode patterns as successive bits:

012
345
678

The operations we will need are mostly very simple:

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)

The trickiest part is the rotation, which is really just rearranging all the bits:

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

In 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;
}

This function returns 116. The time taken on my machine was 0.023ms.

EDIT: You can get an additional 7x improvement by using 4 observations:

  1. We can use a simple visited array instead of a hash set. If a pattern was seen before, we don't count it. This also eliminates the need to keep track of the 'lowest' pattern in the inner loop. If a pattern was visited, then its lowest rotated pattern was visited, too.
  2. If we don't have 180 degree rotation symmetry, then the 3rd rotation won't yield the original pattern. The 4th rotation will, always, so it is unnecessary.
  3. The rotation expression can be slightly simplified.

So, if we apply these observations and unroll the inner do loop, we get the following:

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

This runs in about 3μs on the same machine.

撩起发的微风 2024-12-15 07:38:42

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

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

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
  • 更多优化:59ms

OOP 和更好的缓存:17ms

My solution: 116 unique shapes

When testing 2 shapes for equality, comparing the number of pins saves a lot of time. But my biggest breakthrough was realizing that all of those 25 positions can be replaced by this: for each of the two 3x3 shapes to be checked for equality, concatenate the lines with two zeros then trim leading and trailing zeros. The concat zeros are to prevent wrap-around. Example:

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

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

Then just test the results for equality. That's 4 easy iterations (1 for each rotation) instead of 100 (25 positions * 4 rotations) more complex ones.


Time:
strings only:

  • brute force, all 25 positions for each rotation: 2018ms
  • ...00...00... trimmed: 75ms
  • more optimization: 59ms

OOP and better caching: 17ms

甜中书 2024-12-15 07:38:42

您并不是要求计算平移和旋转之前的独特模式的数量,而是要求进行一致性测试。

选择 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 个类。

You aren't asking for a count of the number of unique patterns up to translation and rotation, but for a congruence test.

Choose a bit string representation of the 3x3 grid. I'll choose row-by-row, top down. By setting a bit where its corresponding hole is punched, we can now map 9-bit integers to punch patterns (and vice-versa.)

For any particular representation, we can contrive bit-fiddling operations representing rotations and translations. Some translations are illegal to perform on some patterns, as we wish to avoid "wrapping around".

Just as rotations and translations are reversible, so will our operations be. If two motions map pattern A to B, and then B to C, we can certainly compose the motions to make a transformation taking A to C. Doing nothing (the identity transform) is also legal, so we can reach A from A. Reachability by transformation is therefore an equivalence relation on punch patterns, and so equivalent patterns partition the space.

Having a means of assigning a positive integer score to each punch pattern, we can invoke the well-ordered principle: the equivalence class containing the pattern will contain a unique pattern (including translations and rotations) of least score. We'll choose this least pattern to be a representative of the equivalence class. If two patterns have the same equivalence class representative, they are necessarily congruent. If they do not, they are necessarily not congruent.

Given a pattern, how do we find its least-weight representative? By inspection, greedy algorithms aren't guaranteed to work. We can reach for one of umpteen heuristic optimization algorithms, or we can note we're only pushing 9 bits around and exhaustively search the space. It should be noted that by the same token this lends itself nicely to being computed once, and shoved in a lookup table forever after.

Here's the exhaustive search. Note with proper caching it is still quite fast (less than a second.)

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

Produces

$ ./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

..for 117 classes.

岁月苍老的讽刺 2024-12-15 07:38:42

首先,我们可以将除了平移之外的等效的两个拳击视为彼此的旋转。想象一下冲孔图案位于球体的表面上:我们可以通过沿水平轴和垂直轴旋转球体(因为它握在我们手中)来“平移”它。

两个冲孔相当于旋转(例如 90 度旋转 ) -度转动)也可以通过我们沿第三个剩余轴旋转球体来捕获。

现在我们将问题简化为“球体表面有多少种独特的打孔图案,直至旋转?”为了像这样计算对称性的唯一对象,您需要 非 Burnside 引理这本书是一本很好的入门书。

First, we can view two punches that are equivalent, except for a translation, as rotations of each other. Imagine the punch pattern is on the surface of a sphere: we can 'translate' it by rotating the sphere along the horizontal and vertical axes (as it is held in our hand.)

Two punches that are equivalent up to rotation (like a 90-degree turn) are also captured here by us rotating our sphere along the third, remaining axis.

Now we've reduced the problem to "How many unique punch patterns are there on the surface of a sphere, up to rotation?" For counting unique objects up to symmetry like this, you want not-Burnside's Lemma. This book is a good primer.

原来是傀儡 2024-12-15 07:38:42

我不认为这就像球体情况,因为你不能绕边缘旋转? 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 的情况下。

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

I don't think that this is like the sphere case, since you can't rotate around the edges? IE:

XOO
XXO
XOO

is not the same as

OOX
XOX
OOX

I tried counting by hand on paper to see what I got. Consider the 2x2 case - you have 1 with 0 dots, 1 with 1 dot, 2 with 2 dots (adjacent, or diagonal), 1 with 3 dots and 1 with 4; for a total of 5 (or 4 if you neglect the empty case). Note that the enumeration is symmetric, since it is the same to count empty spaces as full ones. For the 3x3 case I got this:

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

and then by symmetry, 21, 10, 5, 1, 1

I get 76. I could very easily have miscounted, especially in the 4/5 case.

The only way I can think of enumerating these automatically would involve shifting and rotating the patterns to see if they match a previously-enumerated one. Shifting is tricky, since you can only shift until you "bump" against an edge.

悸初 2024-12-15 07:38:42

值得指出的是,如果您确实需要每个形状“看起来”独特,那么无论它如何旋转或移动,您都没有什么可供选择的。例如,一拳,无论它在网格中的哪个位置,看起来总是一样的。此外,假设正方形网格和圆形引脚,并假设微小的间距差异 (√2) 不显着,那么对角线上的 2 个孔将看起来与两个相邻的引脚相同,因为观察者看到的只是靠近在一起的 2 个孔。同样,对角线中的 3 个看起来就像直线中的 3 个,这极大地限制了您的选择。

请注意,对于我们所追求的内容,形状可能比组合更好,因为我们不关心实际的组合是什么,只关心最终的形状是什么纸。

我认为我们可以假设,无论形状如何,它都可以旋转和移动,从而使左上角的销钉被冲孔(特别是如果您允许 45 度旋转),这使我们能够进一步缩小搜索范围。我们使用以下规则来完成此操作:

  1. 如果任何角被打孔,则旋转网格直到打孔的角位于左上角,
  2. 否则将图案尽可能向上和向左移动。
  3. 重复步骤 1
  4. 如果我们走到这一步,那么我们知道只有顶部中间位置被打孔(因为我们知道两个角都没有),在这种情况下,我们将图案旋转 45 度,使顶部中间位置现在成为左上角。量子ED。

我用笔和纸快速地强力搜索了可能的形状,看起来可行的选项列表非常小,您可以在几分钟内将它们全部列出。

It's worth pointing out that if you truly need each shape to "look" unique, no matter how it's rotated or shifted, you have very few to choose from. For example, a single punch, no matter WHERE it is in the grid, will always look the same. Furthermore, assuming a square grid and round pins, and assuming that minor spacing differences (√2) are insignificant, then 2 holes diagonal in a row will look the same as two adjacent pins, since all the viewer sees is 2 holes close together. Likewise, 3 in a diagonal will look just like 3 in a straight line, which dramatically limits your options.

Note that shape is probably a better word for what we're after than combination, since we don't care what the actual combination was, just what the resultant shape is on paper.

I think we can posit that no matter what the shape, it can be rotated and shifted such that the top-left pin is punched (specifically if you allow rotation on the 45-degree), which allows us to narrow our search even further. We accomplish this using the following rules:

  1. If any corner is punched, rotate the grid until the punched corner is in the top-left
  2. Otherwise shift the pattern as far up and left as it will go.
  3. Repeat step 1
  4. If we get this far, then we know that only the top middle position is punched (since we know that neither corner is), in which case we rotate the pattern 45 degrees, making the top-middle now the top-left. QED.

I did a really quick pen-and-paper brute-force search for possible shapes and it appears that the list of viable options is so small that you could list them all out in just a few minutes.

旧情勿念 2024-12-15 07:38:42

我不太清楚关于旋转的这一部分,但对于这种情况:

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

总计 = 1+9+36+84+126+126+84+36+9+1 = 512

我认为你是对的,512似乎是正确的,理论上无针也被定义为一个组合,如果你不想的话它只是将其删除以获得 511。

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

如果它们同时发生,例如计算 3 名员工在纸上打孔一次的组合或计算一名员工在纸上标记 3 次的组合,那么它会变得更复杂,根据 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 规则的确切原因。

这是我的想法,我不是数学家,也不声称100%正确,这只是我在概率课程中记得的。

我并不声称100%正确,这只是我记得的概率课程。


至于模式重叠、检查是否已经找到模式等,我根本不会打扰,因为伪代码中有很多算法可以生成组合。由于它们生成,因此无需检查重叠或任何内容。

毕竟,这就是生成的意思,它被设计为先验地找到所有结果,只需让它运行即可。

我曾经发现过一种比简单组合更罕见的算法,当我将其转换为 php 时,它完美地完成了工作,无需担心重叠或其他任何问题。

I don't understand clearly this part about rotations but for this scenario:

There are 3x3=9 holes and 10 cases and every time only one case can take place:

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

Then it would go like this with the combination formula C(n,k):

Total = C(9,0)+C(9,1)+....+C(9,9)

this is to sum k different combinations of n elements.

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

I think you are right, 512 seems to be correct and theoritically pinless is defined as a combination too, if you don't want it just remove it to get 511.

All these cases happen separately so we add the different cases.

If they were to happen synchronously, for example calculating the combinations for 3 employees punching the paper once or calculating the combinations for marking the paper 3 times by one employee then it gets more complicated and should be 512 * 512 * 512 by the nxm rule.

The m*n rule in a simple display is:

I have 4=m pockets and two=n hands:

my_left * 4(pockets)=4 my_right * 4(pockets)=4

total= 4+4=4*2=m*n

Only one hand can enter the pocket at a time or only one combination of one employe is paired with only one combination of the other employee and this is the exact reason we take the m*n rule.

This is what I think, I am not a mathematician and do not claim to be correct 100%, it's just what I remember from the probabilities course.

I don't claim to be 100% correct, is jus what I remember for the probability course.


As for patterns overlapping, checking if pattern already found etc I wouldn't bother at all since there are so many algorithms in pseudocode that generate combinations. Since they generate then no need to check overlaps or anything.

After all this is what generat means it is designed a priori to find all results, just let it run.

I had found once a even more rare algorithm than simple combinations and when I turned it to php it did the job perfectly, no need to worry for overlaps or anything.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文