I know this topic is much discussed but I can\'t seem to find any implementation that fits my needs.
I have the following set of characters:
a b c
$strings = get_perm( range('a', 'h'), 4 );
function get_perm( $a, $c, $step = 0, $ch = array(), $result = array() ){
if( $c == 1 ){ //if we have last symbol in chain
for( $k = 0; $k < count( $a ); $k++ ){
if( @in_array( $k, $ch ) ) continue; // if $k exist in array we already have such symbol in string
$tmp = '';
foreach( $ch as $c ) $tmp .= $a[$c]; // concat chain of previous symbols
$result[] = $tmp . $a[$k]; // and adding current + saving to our array to return
}
}else{
for( $i = 0; $i < count( $a ); $i++ ){
if( @in_array( $i, $ch ) ) continue;
$ch[$step] = $i; // saving current symbol for 2 things: check if that this symbol don't duplicate later and to know what symbols and in what order need to be saved
get_perm( $a, $c-1, $step+1, $ch, &$result );
// recursion,
// decrementing amount of symbols left to create string,
// incrementing step to correctly save array or already used symbols,
// $ch - array of already used symbols,
// &$result - pointer to result array
}
}
return $result;
}
NOTICE
a-h with 6 symbols = 20k values in array
a-z with 4 symbols = 358799 values in array
So a-z with 10 symbols will die for sure =) It will require too much memory.
You need to try to save output to file or database if you would need big amount of values. Or extend memory limit to php but not sure if this is best way.
If you only need one element at a time, you can save on memory by generating each element individually.
If we wanted to generate a random string in your set of expected outputs, we could use this algorithm:
Given a set of characters S, and a desired output length K:
While the output has less than K characters:
Pick a random number P between 1 and |S|.
Append the P'th character to the output.
Remove the P'th character from S.
where |S|
is the current number of elements in S.
We can actually encode this sequence of choices into an integer. One way to do that is to change the algorithm as such:
Given a set of characters S, and a desired output length K:
Let I = 0.
While the output has less than K characters:
I = I * (|S| + 1).
Pick a random number P between 1 and the number of elements in S.
I = I + P.
Append the P'th character to the output.
Remove the P'th character from S.
After running this algorithm, the value I
will uniquely encode this particular sequence of choices. It basically encodes this as a mixed-radix number; one digit uses base N, the next uses N-1, and so on until the last digit which is base N-K+1 (N being the number of letters in the input).
Naturally, we can also decode this again, and in PHP, that would be something like this:
// Returns the total number of $count-length strings generatable from $letters.
function getPermCount($letters, $count)
{
$result = 1;
// k characters from a set of n has n!/(n-k)! possible combinations
for($i = strlen($letters) - $count + 1; $i <= strlen($letters); $i++) {
$result *= $i;
}
return $result;
}
// Decodes $index to a $count-length string from $letters, no repeat chars.
function getPerm($letters, $count, $index)
{
$result = '';
for($i = 0; $i < $count; $i++)
{
$pos = $index % strlen($letters);
$result .= $letters[$pos];
$index = ($index-$pos)/strlen($letters);
$letters = substr($letters, 0, $pos) . substr($letters, $pos+1);
}
return $result;
}
(Note that for simplicity, this particular decoding algorithm does not correspond exactly to the encoding algorithm I previously described, but maintains the desirable property of a given $index
mapping to a unique result.)
To use this code, you would do something like this:
$letters = 'abcd';
echo '2 letters from 4:<br>';
for($i = 0; $i < getPermCount($letters, 2); $i++)
echo getPerm($letters, 2, $i).'<br>';
echo '<br>3 letters from 4:<br>';
for($i = 0; $i < getPermCount($letters, 3); $i++)
echo getPerm($letters, 3, $i).'<br>';
?>