【问题标题】:Create fixed length non-repeating permutation of larger set创建较大集合的固定长度非重复排列
【发布时间】:2012-11-07 23:30:20
【问题描述】:

我知道这个话题被讨论了很多,但我似乎找不到任何适合我需要的实现。

我有以下一组字符:

a b c d e f g h

我想获得所有可能的排列或组合(不重复),但在有限(可变)字符集上,这意味着如果我输入字符和数字2,结果应该是这样的

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

我希望你明白我的意思。我目前有一个实现,它为我提供了 所有 字符的排列,但我不知道如何为这些排列实现 limited space

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;
}

【问题讨论】:

    标签: php permutation


    【解决方案1】:

    如果您一次只需要一个元素,您可以通过单独生成每个元素来节省内存。

    如果我们想在您的预期输出集中生成一个随机字符串,我们可以使用这个算法:

    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.
    

    其中|S| 是 S 中的当前元素数。

    我们实际上可以将这个选择序列编码为一个整数。一种方法是改变算法:

    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.
    

    运行此算法后,值I 将对这个特定的选择序列进行唯一编码。它基本上将其编码为mixed-radix 数字;一个数字使用基数 N,下一个数字使用 N-1,依此类推,直到最后一个数字是基数 N-K+1(N 是输入中的字母数)。

    当然,我们也可以再次解码,在 PHP 中,会是这样的:

    // 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;
    }
    

    (请注意,为简单起见,此特定解码算法与我之前描述的编码算法并不完全对应,但保持了给定 $index 映射到唯一结果的理想属性。)

    要使用此代码,您可以执行以下操作:

    $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>';
    ?>
    

    【讨论】:

    • 他将如何节省内存?他需要“所有可能的排列或组合”。算法的差异不会使结果数组变小。
    • @UAWDT:当您处理排列时,您通常只需要遍历它们并单独处理它们。您无需将它们全部存储在一个大数组中即可 - 您可以根据需要生成每个元素,并在完成后将其丢弃。换句话说,就是$perms = getPerms(); foreach ($perms as $perm)for ($i=0;$i&lt;getPermCount(...);$i++) $perm = getPerm($i);的区别。
    • 两天后我找到了完美的答案
    【解决方案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;
    }
    

    通知

    a-h 有 6 个符号 = 数组中有 20k 个值
    具有 4 个符号的 a-z = 数组中的 358799 个值
    所以带有 10 个符号的 a-z 肯定会死掉 =) 它需要太多的内存。
    如果需要大量值,则需要尝试将输出保存到文件或数据库。或者将内存限制扩展到 php 但不确定这是否是最好的方法。

    【讨论】:

    • 这是一个想法,但您可以扩展它以达到您的目标 =) 如果您需要更多帮助,请询问 =)
    • 我想念您有 ba、ca 等值。为此只需将 for( $i = $step 更改为 for( $i = 0
    • 对于 2 个字符似乎可以正常工作,但是当我设置 $c = 3 时,我会交替使用 3 个和 2 个字母组合,例如abc, ab, acd, ac, ade, ad,...
    • 请再次检查我的代码。它被编辑了,是的,有这样的问题
    • 我在 4 上看到了。等几分钟。需要思考=)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-02
    • 2011-12-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多