I'm going to skip reviewing the OP's code, which has already been reviewed quite nicely by others. Instead I'm just going to show a much faster version and explain it.
What is the algorithm?
Basically the way the algorithm works is that a buffer is created that holds alphaLen^2
patterns, where alphaLen
is the length of the alphabet. A pattern is one combination, such as "aaaaa\n"
. The reason that alphaLen^2
patterns are used is because the buffer is prepopulated with the last 2 letters already set to all possible combinations. So for example, the buffer initially looks like (for length 5 patterns):
"aaaaa\naaaab\naaaac\n ... aaa99\n" (62*62 patterns, 23064 bytes in length)
On every iteration, the function uses write()
to output the buffer, and then increments the third to last letter. This involves writing that letter alphaLen^2
times (once per pattern). So the first iteration goes like:
"aabaa\naabab\naabac\n ... aab99\n"
^ ^ ^ ^
| | | |
+------+------+-----------+----- Characters written to buffer (62*62 changes)
Whenever the third to last character wraps around, then we also need to update the fourth to last letter. If that wraps around, the fifth to last letter is also updated, etc. This continues until the first letter wraps around, at which point we are done.
How fast is it?
For all testing, I outputted to /dev/null
so that my hard drive speed would not be the limiting factor. I tried the OP's program for 5 letter patterns but for me it took way too long (203 seconds). So I'll use Edward's estimate of 18 seconds for the OP's program instead. I also tested Edward's program on my machine, as well as a second program I wrote where I extended the algorithm to hardcode the last 3 characters instead of 2. I am using Cygwin gcc (32-bit) with -O4
on a Windows desktop. Here are the results:
Time (secs) Length Program
----------- ------ -------
18.0 5 syb0rg
9.6 5 Edward
0.4 5 JS1 (2 characters)
0.4 5 JS1 (3 characters)
665.0 6 Edward
26.0 6 JS1 (2 characters)
24.2 6 JS1 (3 characters)
1513.7 7 JS1 (3 characters)
As you can see, this algorithm is very fast.
The code
Both programs are available here on GitHub. I'll show the 2 character variation below:
static void generate(int maxlen)
{
int alphaLen = strlen(alphabet);
int len = 0;
char *buffer = malloc((maxlen + 1) * alphaLen * alphaLen);
int *letters = malloc(maxlen * sizeof(int));
if (buffer == NULL || letters == NULL) {
fprintf(stderr, "Not enough memory.\n");
exit(1);
}
// This for loop generates all 1 letter patterns, then 2 letters, etc,
// up to the given maxlen.
for (len=1;len<=maxlen;len++) {
// The stride is one larger than len because each line has a '\n'.
int i;
int stride = len+1;
int bufLen = stride * alphaLen * alphaLen;
if (len == 1) {
// Special case. The main algorithm hardcodes the last two
// letters, so this case needs to be handled separately.
int j = 0;
bufLen = (len + 1) * alphaLen;
for (i=0;i<alphaLen;i++) {
buffer[j++] = alphabet[i];
buffer[j++] = '\n';
}
write(STDOUT_FILENO, buffer, bufLen);
continue;
}
// Initialize buffer to contain all first letters.
memset(buffer, alphabet[0], bufLen);
// Now in buffer, write all the last 2 letters and newlines, which
// will after this not change during the main algorithm.
{
// Let0 is the 2nd to last letter. Let1 is the last letter.
int let0 = 0;
int let1 = 0;
for (i=len-2;i<bufLen;i+=stride) {
buffer[i] = alphabet[let0];
buffer[i+1] = alphabet[let1++];
buffer[i+2] = '\n';
if (let1 == alphaLen) {
let1 = 0;
let0++;
if (let0 == alphaLen)
let0 = 0;
}
}
}
// Write the first sequence out.
write(STDOUT_FILENO, buffer, bufLen);
// Special case for length 2, we're already done.
if (len == 2)
continue;
// Set all the letters to 0.
for (i=0;i<len;i++)
letters[i] = 0;
// Now on each iteration, increment the the third to last letter.
i = len-3;
do {
char c;
int j;
// Increment this letter.
letters[i]++;
// Handle wraparound.
if (letters[i] >= alphaLen)
letters[i] = 0;
// Set this letter in the proper places in the buffer.
c = alphabet[letters[i]];
for (j=i;j<bufLen;j+=stride)
buffer[j] = c;
if (letters[i] != 0) {
// No wraparound, so we finally finished incrementing.
// Write out this set. Reset i back to third to last letter.
write(STDOUT_FILENO, buffer, bufLen);
i = len - 3;
continue;
}
// The letter wrapped around ("carried"). Set up to increment
// the next letter on the left.
i--;
// If we carried past last letter, we're done with this
// whole length.
if (i < 0)
break;
} while(1);
}
// Clean up.
free(letters);
free(buffer);
}
Brute force
is not an algorithm,Brute force search
is (cf your reference) \$\endgroup\$