Computer Counting Algorithms

by Donna Whisnant


M ^ N Counting

Often it's necessary to setup a computer program to try all combinations of certain characters.  For example. you might be doing brute-force password recovery and know that the password is 7 digits long and consists only of numbers.  Therefore, you'd want to try every combination of 0-9 that is 7 digits long:

 
0 0 0 0 0 0 0
0 0 0 0 0 0 1
0 0 0 0 0 0 2
0 0 0 0 0 0 3
0 0 0 0 0 0 4
0 0 0 0 0 0 5
0 0 0 0 0 0 6
0 0 0 0 0 0 7
0 0 0 0 0 0 8
0 0 0 0 0 0 9
0 0 0 0 0 1 0
0 0 0 0 0 1 1
0 0 0 0 0 1 2
0 0 0 0 0 1 3
0 0 0 0 0 1 4
  ...etc...
9 9 9 9 9 8 7
9 9 9 9 9 8 8
9 9 9 9 9 8 9
9 9 9 9 9 9 0
9 9 9 9 9 9 1
9 9 9 9 9 9 2
9 9 9 9 9 9 3
9 9 9 9 9 9 4
9 9 9 9 9 9 5
9 9 9 9 9 9 6
9 9 9 9 9 9 7
9 9 9 9 9 9 8
9 9 9 9 9 9 9
 

This is nothing more than simple counting and can easily be represented with code similar to the following:

 
#define M 10
#define N 7

int i;
int a[N];
int done;

done = 0;
while (!done) {

	// TODO : Add code here to use a[] for processing this iteration

	for (i=0; i<N; i++) {
		a[i]++;
		if (a[i]<M) break;
		a[i]=0;
	}
	if (i == N) done = 1;
}
 
This requires M ^ N iterations to complete or 10,000,000 for this example.  And as the character set increases and/or the number of characters increases, this number gets large very quickly.



N-Factorial (Permutation) Counting

But, suppose that you are able to narrow things down.  Suppose you know how many digits long it is and you know exactly what characters are used, but you just don't know the exact sequence of these characters.  Or, suppose you want to try arranging N objects into all of the possible ways of arranging them:

 
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
 

Mathematically, this yields N-Factorial combinations.  In the above example of 3 objects, that is 3! = 1 x 2 x 3 and such counting is fairly easy to do by hand on paper, but how do you effectiently make a computer count in this fashion?

One way is to use the previous M ^ N counting where M = N and add an additional check that compares each digit with the others and if all aren't unique to ignore that sequence:

 
1 1 1
1 1 2
1 1 3
1 2 1
1 2 2
1 2 3  << Keep
1 3 1
1 3 2  << Keep
1 3 3
2 1 1
2 1 2
2 1 3  << Keep
2 2 1
2 2 2
2 2 3
2 3 1  << Keep
2 3 2
2 3 3
3 1 1
3 1 2  << Keep
3 1 3
3 2 1  << Keep
3 2 2
3 2 3
3 3 1
3 3 2
3 3 3
 

But, as you can see this is extremely inefficient.  Out of 3 ^ 3 = 27 sequences, only 6 are used.  And this gets worse as the numbers get larger.  For our 7 character case, that means we are counting 7 ^ 7 = 823543 combinations to use only 7! = 5040 of them.  And that isn't even counting all of the extra comparisons the computer has to do to make sure each digit is unique!

There has to be a more efficient method, right?  Yes there is.  There are probably numerous ways of doing this, but here's one that I figured out one afternoon:

The way we've counted in our above example of 3 objects with 1 2 3 then 1 3 2 then 2 1 3, etc, is very difficult to represent programatically.  But what if we modified it slightly:

 
1 2 3
2 1 3
3 1 2
1 3 2
2 3 1
3 2 1
 

Here we increment the first position and then look through the sequence and find the position that holds the number we just incremented the first digit to and we give that position the old number from the first position:

 
1 2 3  << Start with this
2 2 3  << Increment first position
2 1 3  << Find the position that conflicts and give it the old one
 

Since each position has a unique value, we know that no other position held the same value from before our incrementation.  And we know that one and only one position conflicts with the new value.  So we simply play a swapping game.  And, we keep this up until the entire sequence is reversed -- that is, when 1 2 3 becomes 3 2 1.

Is that it?  Is that all there is to it?  Well, no not quite.  That's all that is required for 3 or less objects, but it will become apparent as we increase the number of objects that we are missing something.  Let's look at 4 objects and go through the entire counting sequence as we just described:

 
1 2 3 4
2 1 3 4
3 1 2 4
4 1 2 3
1 4 2 3
2 4 1 3
3 4 1 2
4 3 1 2
1 3 4 2
2 3 4 1
3 2 4 1
4 2 3 1
1 2 3 4
 

Oh No!  After only half of the sequence, the counting ended up right back where it started.  We should have gotten 4! = 24 unique sequences, but we only got 12.  So what went wrong??

Well, it turns out that everything is mirrored around the center.  For 2 objects, the first column is paired with the 2nd column.  For 3 objects, the first column is paired with the 3rd column and the 2nd with itself.  But when we reach 4 objects, the 1st column is paired with the 4th column and the 2nd is paired with the 3rd, and everything is mirrored around an invisible line down the center.

When we reach 5 objects, the first column will again be paired with the last column.  The second column will be paired with the next to last column and the center column will pair with itself.  Thus, 4 object and 5 object counting work the same, just like 2 object and 3 object counting works the same:

 
 +-------+
 | +---+ | Column Pairs
 v v   v v
     |
   1 | 2
   2 | 1
     |
     |
   1 2 3
   2 1 3
   3 1 2
   1 3 2
   2 3 1
   3 2 1
     |
     |
 1 2 | 3 4
 2 1 | 3 4
 3 1 | 2 4
 4 1 | 2 3
 1 4 | 2 3
 2 4 | 1 3
 etc | etc
     |
     |
 1 2 3 4 5
 2 1 3 4 5
 3 1 2 4 5
 4 1 2 3 5
 5 1 2 3 4
 1 5 2 3 4
 etc | etc
 

To explain how to fix our counting algorithm, let's assign a position or index number to each column.  For mathematical convenience, we'll start with 0 and call the first column "column number 0".  The next column will be "column number 1" and so forth, making the last column "column number N-1".

It turns out that whenever you increment a position and that position "rolls over" and then you do the value swap with the mirrored or paired column for the position, then you'll be reverting back to previously visited sequence.  In this example of 4 objects, when we reached 4 2 3 1 and our counting rolled from 4 back to 1 and then we swapped the 1 to a 4 on our paired column (in this case the last column), then we returned back to 1 2 3 4, a previously visited number.

If we carefully examine what is happening, we'll find that each position counts from (index+1) to (N-index).  And when a position rolls over in counting from (N-index) back to (index+1) and at the same time position "index" swaps with position "N-1-index" (that is to say its mirror column), then we "bump" to the next index and increment the next column.

Thus, in our example of 4 objects, when we reach 4 2 3 1, we increment to 1 2 3 4, but since position 0 (i.e. index=0) rolled over from 4 (N-index) back to 1 (index+1) and at the same time swapped with position 3 (i.e. N-1-index), we bump index to 1 to point to the next column and increment it:

 
0 1 2 3  << Indexes or Positions
- - - -
3 2 4 1
4 2 3 1
1 2 3 4  << Index 0 wrapped in value from N-index back to index+1 and swapped with its mirror position of N-1-index or 3
1 3 3 4  << Increment the next position or index
1 3 2 4  << And do the normal swap operation
 

Then, we go back to counting using index 0 as before:

 
0 1 2 3
- - - -
1 3 2 4
2 3 1 4
3 2 1 4
4 2 1 3
1 2 4 3
2 1 4 3
3 1 4 2
4 1 3 2
1 4 3 2
2 4 3 1
3 4 2 1
4 3 2 1
 

And again counting is complete when the sequence is reversed.  To test for counting completion, we could literally do a comparison to see if the sequence is physically reversed.  But that is a quite a bit of computational effort:


done = 1;
for (i=1; ((i<N) && (done)); i++) if (a[i] >= a[i-1]) done=0;
if (done) exit;

However, because of the mirrored nature of the columns, there is a much more efficient way to determine if we are done.  Once our index value passes the centerline of the mirror, we are done.  That is for 4 objects, once index hits 2 (or larger) we are done.  For 5 objects, since the center column is its own mirror, again once index hits 2 (or larger) we are done.  For 6 objects, it is 3, and so forth.  This can be expressed as:


if (index > ((N/2)-1)) exit;

To summarize this counting algorithm, lets look again at 7 objects.  This time we have 3 paired columns: 0 and 6, 1 and 5, and 2 and 4.  column 3 pairs with itself.  Our counting on column or index 0 goes from 1 to 7.  Counting on column 1 goes from 2 to 6.  And counting on column 2 goes from 3 to 5.  And technically, our self-mirrored column goes from 4 to 4, which means we never increment it.  Let's step through the counting and watch in detail how our column incrementation works:

 
0 1 2 3 4 5 6  Column Indexes
- - - - - - -
1 2 3 4 5 6 7
2 1 3 4 5 6 7
3 1 2 4 5 6 7
4 1 2 3 5 6 7
5 1 2 3 4 6 7
6 1 2 3 4 5 7
7 1 2 3 4 5 6
1 7 2 3 4 5 6
2 7 1 3 4 5 6
3 7 1 2 4 5 6
4 7 1 2 3 5 6
5 7 1 2 3 4 6
6 7 1 2 3 4 5
7 6 1 2 3 4 5
  ...etc...
5 2 3 4 6 7 1
6 2 3 4 5 7 1
7 2 3 4 5 6 1
1 2 3 4 5 6 7 << Column 0 rolled over and swapped with mirror column 6 (toss this one)
1 3 2 4 5 6 7 << Bump next index, do normal swap operation. This is the next sequence
2 3 1 4 5 6 7 << Continue counting with index 0
3 2 1 4 5 6 7
4 2 1 3 5 6 7
5 2 1 3 4 6 7
  ...etc...
5 3 2 4 6 7 1
6 3 2 4 5 7 1
7 3 2 4 5 6 1
1 3 2 4 5 6 7 << Column 0 rolled over and swapped with mirror column 6 (toss this one)
1 4 2 3 5 6 7 << Bump next index, do normal swap operation. This is the next sequence
2 4 1 3 5 6 7 << Continue counting with index 0
3 4 1 2 5 6 7
  ...etc...
4 5 2 3 6 7 1
5 4 2 3 6 7 1
6 4 2 3 5 7 1
7 4 2 3 5 6 1
1 4 2 3 5 6 7 << Column 0 rolled over and swapped with mirror column 6 (toss this one)
1 5 2 3 4 6 7 << Bump next index, do normal swap operation. This is the next sequence
2 5 1 3 4 6 7 << Continue counting with index 0
3 5 1 2 4 6 7
4 5 1 2 3 6 7
  ...etc...
4 6 2 3 5 7 1
5 6 2 3 4 7 1
6 5 2 3 4 7 1
7 5 2 3 4 6 1
1 5 2 3 4 6 7 << Column 0 rolled over and swapped with mirror column 6 (toss this one)
1 6 2 3 4 5 7 << Bump next index, do normal swap operation. This is the next sequence
2 6 1 3 4 5 7 << Continue counting with index 0
3 6 1 2 4 5 7
4 6 1 2 3 5 7
  ...etc...
4 7 2 3 5 6 1
5 7 2 3 4 6 1
6 7 2 3 4 5 1
7 6 2 3 4 5 1
1 6 2 3 4 5 7 << Column 0 rolled over and swapped with mirror column 6 (toss this one)
1 2 6 3 4 5 7 << Bump next index, do normal swap operation. Remember counting is from index+1 to N-index. This is the next sequence
2 1 6 3 4 5 7 << Continue counting with index 0
3 1 6 2 4 5 7
4 1 6 2 3 5 7
  ...etc...
4 2 7 3 5 6 1
5 2 7 3 4 6 1
6 2 7 3 4 5 1
7 2 6 3 4 5 1
1 2 6 3 4 5 7 << Column 0 rolled over and swapped with mirror column 6 (toss this one)
1 3 6 2 4 5 7 << Bump next index, do normal swap operation. This is the next sequence
2 3 6 1 4 5 7 << Continue counting with index 0
3 2 6 1 4 5 7
4 2 6 1 3 5 7
  ...etc...
4 3 7 2 5 6 1
5 3 7 2 4 6 1
6 3 7 2 4 5 1
7 3 6 2 4 5 1
1 3 6 2 4 5 7 << Column 0 rolled over and swapped with mirror column 6 (toss this one)
1 4 6 2 3 5 7 << Bump next index, do normal swap operation. This is the next sequence
2 4 6 1 3 5 7 << Continue counting with index 0
3 4 6 1 2 5 7
4 3 6 1 2 5 7
  ...etc...
4 5 7 2 3 6 1
5 4 7 2 3 6 1
6 4 7 2 3 5 1
7 4 6 2 3 5 1
1 4 6 2 3 5 7 << Column 0 rolled over and swapped with mirror column 6 (toss this one)
1 5 6 2 3 4 7 << Bump next index, do normal swap operation. This is the next sequence
2 5 6 1 3 4 7 << Continue counting with index 0
3 5 6 1 2 4 7
4 5 6 1 2 3 7
  ...etc...
4 6 7 2 3 5 1
5 6 7 2 3 4 1
6 5 7 2 3 4 1
7 5 6 2 3 4 1
1 5 6 2 3 4 7 << Column 0 rolled over and swapped with mirror column 6 (toss this one)
1 6 5 2 3 4 7 << Bump next index, do normal swap operation. This is the next sequence
2 6 5 1 3 4 7 << Continue counting with index 0
3 6 5 1 2 4 7
4 6 5 1 2 3 7
  ...etc...
4 7 6 2 3 5 1
5 7 6 2 3 4 1
6 7 5 2 3 4 1
7 6 5 2 3 4 1
1 6 5 2 3 4 7 << Column 0 rolled over and swapped with mirror column 6 (toss this one)
1 2 5 6 3 4 7 << Bump next index, do normal swap operation. Remember counting is from index+1 to N-index. This is the next sequence
2 1 5 6 3 4 7 << Continue counting with index 0
3 1 5 6 2 4 7
4 1 5 6 2 3 7
  ...etc...
  ...etc...
2 7 4 5 6 3 1
3 7 4 5 6 2 1
4 7 3 5 6 2 1
5 7 3 4 6 2 1
6 7 3 4 5 2 1
7 6 3 4 5 2 1
1 6 3 4 5 2 7 << Column 0 rolled over and swapped with mirror column 6 (toss this one)
1 2 3 4 5 6 7 << Column 1 rolled over (Remember counting is from index+1 to N-index) and swapped with mirror column 5 (toss this one)
1 2 4 3 5 6 7 << Bump next index, do normal swap operation. This is the next sequence
2 1 4 3 5 6 7 << Continue counting with index 0
3 1 4 2 5 6 7
4 1 3 2 5 6 7
5 1 3 2 4 6 7
6 1 3 2 4 5 7
  ...etc...
  ...etc...
  ...etc...
2 7 5 4 6 3 1
3 7 5 4 6 2 1
4 7 5 3 6 2 1
5 7 4 3 6 2 1
6 7 4 3 5 2 1
7 6 4 3 5 2 1
1 6 4 3 5 2 7 << Column 0 rolled over and swapped with mirror column 6 (toss this one)
1 2 4 3 5 6 7 << Column 1 rolled over (Remember counting is from index+1 to N-index) and swapped with mirror column 5 (toss this one)
1 2 5 3 4 6 7 << Bump next index, do normal swap operation. This is the next sequence
2 1 5 3 4 6 7 << Continue counting with index 0
3 1 5 2 4 6 7
4 1 5 2 3 6 7
  ...etc...
  ...etc...
  ...etc...
2 1 7 6 5 4 3
3 1 7 6 5 4 2
4 1 7 6 5 3 2
5 1 7 6 4 3 2
6 1 7 5 4 3 2
7 1 6 5 4 3 2
1 7 6 5 4 3 2
2 7 6 5 4 3 1
3 7 6 5 4 2 1
4 7 6 5 3 2 1
5 7 6 4 3 2 1
6 7 5 4 3 2 1
7 6 5 4 3 2 1 << If we check for sequence reversal, we would stop here
1 6 5 4 3 2 7 << Column 0 rolled over and swapped with mirror column 6 (toss this one)
1 2 5 4 3 6 7 << Column 1 rolled over (Remember counting is from index+1 to N-index) and swapped with mirror column 5 (toss this one)
1 2 3 4 5 6 7 << Column 2 rolled over (Remember counting is from index+1 to N-index) and swapped with mirror column 4 (toss this one)
1 2 3 4 5 6 7 << Index increments to 3, but 3 > ((N/2) - 1) so we are done.
 

And that's all there is to it.  Click here if you'd like to download the N-Factorial source code implementation in C.  To use this in a useful application simply replace the printf loop where the current sequence is being printed and replace it with code that performs whatever work you desire to do on the current sequence.

And what if you want to use letters or something else for each entry?  Simple.  All you have to do is use these calculated sequence numbers as indexes into an array of characters or whatever kind of object you wish.




Algorithm Usage

Feel free to use these algorithms freely wherever you see fit.  And please send me any comments that you might have or ideas for additional improvements.  Also, if you have additional counting algorithms that might be useful to others, please send them and I'll post them with this one.  Enjoy!