Create fixed length non-repeating permutation of larger set

后端 未结 2 757
走了就别回头了
走了就别回头了 2021-01-22 16:44

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

2条回答
  •  礼貌的吻别
    2021-01-22 17:41

    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:
    '; for($i = 0; $i < getPermCount($letters, 2); $i++) echo getPerm($letters, 2, $i).'
    '; echo '
    3 letters from 4:
    '; for($i = 0; $i < getPermCount($letters, 3); $i++) echo getPerm($letters, 3, $i).'
    '; ?>

提交回复
热议问题