问题
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 d e f g h
I want to get all possible permutations or combinations (non repeating), but on a limited (variable) set of characters, meaning if I input the characters and the number 2
, the results should look like
ab ba ac ca ad da ae ea af fa ag ga ah ha
bc cb bd db be eb bf fb bg gb bh hb
cd dc ce ec cf fc cg gc ch hc
de ed df fd dg gd dh hd
ef fe eg ge eh he
fg gf fh hf
gh hg
I hope you understand where I'm going with this. I currently have an implementation that gives me the permutations of all characters, but I can't wrap my head around how to implement a limited space for those permutations:
public function getPermutations($letters) {
if (strlen($letters) < 2) {
return array($letters);
}
$permutations = array();
$tail = substr($letters, 1);
foreach ($this->getPermutations($tail) as $permutation) {
$length = strlen($permutation);
for ($i = 0; $i <= $length; $i++) {
$permutations[] = substr($permutation, 0, $i) . $letters[0] . substr($permutation, $i);
}
}
return $permutations;
}
回答1:
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>';
?>
回答2:
$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.
来源:https://stackoverflow.com/questions/13175257/create-fixed-length-non-repeating-permutation-of-larger-set