开发者

Find all combinations of 3x3 holepunch

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?


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.


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


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.


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.


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.


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.


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜