【问题标题】:How can I return all combinations of a given string? (ex. 'foo bar' = bar, bar_foo, foo)如何返回给定字符串的所有组合? (例如,'foo bar' = bar、bar_foo、foo)
【发布时间】:2014-07-31 21:02:53
【问题描述】:

此问题与上述建议的问题不同。标题可能听起来相似,但其答案绝不会导致下面的问题。


我很难递归地遍历一个未知长度的数组来创建唯一的字符串组合。你能帮忙吗?

我们的目标是获取像foo bar 这样的字符串,并从该字符串创建唯一的组合:

foo
bar
bar_foo (alphabetized to make unique combinations, not permutations)

另一个例子:

car bar add 应该返回:

add
add_bar
add_car
add_bar_car
bar
bar_car
car

这是我的进步:

function string_builder($length) {
    $arrWords = array('add','bar','car','dew','eat','fat','gym','hey','ink','jet','key','log','mad','nap','odd','pal','qat','ram','saw','tan','urn','vet','wed','xis','yap','zoo');
    $arr = array();
    for ($i=0; $i < $length; $i++) { 
        $arr[] = $arrWords[$i];
    }
    return implode(' ', $arr);
}
function get_combinations($string) {
    $combinations = array(); // put all combinations here
    $arr = explode(' ',$string);
    $arr = array_unique($arr); // only unique words are important
    sort($arr); // alphabetize to make unique combinations easier (not permutations)
    $arr = array_values($arr); // reset keys
    for ($i=0; $i < count($arr); $i++) {
        // this is where I'm stuck
        // how do I loop recursively through all possible combinations of an array?
    }
    return $combinations;
}
// Test it!
for ($i=1; $i < 26; $i++) { 
    $string = string_builder($i);
    $combinations = get_combinations($string);
    echo $i . " words\t" . count($combinations) . " combinations\t" . $string . "\n";
    // print_r($combinations);
}

另一个尝试:

function getCombinations2($str, $min_length = 2) {
    $words = explode(' ', $str);
    $combinations = array();
    $len = count($words);
    for ($a = $min_length; $a <= $min_length; $a++) {
        for ($pos = 0; $pos < $len; $pos ++) {
            if(($pos + $a -1) < $len) {
                $tmp = array_slice($words, $pos, $a);
                sort($tmp);
                $tmp = implode('_',$tmp);
                $combinations[] = $tmp;
            }
        }
    }
    $combinations = array_unique($combinations);
    return $combinations;
}

当您打印出组合并寻找应该存在的几个组合(例如,“fat_zoo”、“car_tan”)时,您就可以知道您成功了。我的两次尝试都会显示其中的几个,但不会显示全部。

【问题讨论】:

  • 你到底想要什么?你想要所有音节的所有可能组合吗?你希望每个单词有多少个音节?
  • @MCEmperor,只是文字。 explode(' ',$str) 很好。
  • 我认为排列关心顺序?我不想要bar_foobar_barfoo_barfoo_foo。我只想要其中一个(bar_foo,因为它更容易按字母顺序排列)。
  • 组合不考虑顺序,因此 ABC 和 CBA 是相同的。排列关心顺序,因此将两者视为不同的。当订单很重要时,不同的订单是不同的子集,而不是相反
  • 谢谢安东尼。我想同样对待ABCCBA。因此,按字母顺序对结果进行排序以确保 CBA 在包含 ABC 时永远不会出现。

标签: php arrays recursion combinations


【解决方案1】:

您正在搜索的内容很容易使用二进制数构建(和解释)。

二进制 word 中的每个位置都应指示是否附加了数组中的某个单词。

假设,您有一个由两个单词组成的数组:

$words = ["foo","bar"];

你现在期待这些组合

foo
bar
bar_foo

在二进制中可以表示为

1 0
0 1
1 1

三个字$words = ["foo","bar", "baz"];就是组合

foo
bar
baz
foo_bar
foo_baz
bar_baz
foo_bar_baz

可以理解为

1 0 0
0 1 0
0 0 1
1 1 0
1 0 1
0 1 1 
1 1 1

(现在忽略字母排序)

让我们将这些二进制数移动到一个具体的顺序并查看它们的十进制值:

0 0 1 // dec 1
0 1 0 // dec 2
0 1 1 // dec 3
1 0 0 // dec 4
1 0 1 // dec 5
1 1 0 // dec 6
1 1 1 // dec 7

注意:您要生成的元素数是(2^n)-1,其中n是您的单词数。

这就是你需要做的所有事情:

  • 1 迭代到(2^n)-1
  • 将该十进制数的二进制版本作为“数组索引”。
  • 追加索引为“1”的元素。

php:

print_r(get_combinations("car bar add"));

function get_combinations($str) {
    $words = explode(' ',$str);
    $elements = pow(2, count($words))-1;

    $result = array();

    for ($i = 1; $i<=$elements; $i++){
        $bin = decbin($i);
        $padded_bin = str_pad($bin, count($words), "0", STR_PAD_LEFT);

        $res = array();
        for ($k=0; $k<count($words); $k++){
            //append element, if binary position says "1";
            if ($padded_bin[$k]==1){
                $res[] = $words[$k];
            }
        }

        sort($res);
        $result[] = implode("_", $res);
    }
    sort($result);
    return $result;
}

结果:

Array
(
    [0] => add
    [1] => bar
    [2] => bar_add
    [3] => car
    [4] => car_add
    [5] => car_bar
    [6] => car_bar_add
)

您可以在对数组$res 进行内爆之前按字母顺序对其进行排序。


长度有限3:

print_r(get_combinations("car bar add"));

function get_combinations($str) {
    $words = explode(' ',$str);
    $elements = pow(2, count($words))-1;

    $result = array();

    for ($i = 1; $i<=$elements; $i++){
        $bin = decbin($i);
        $padded_bin = str_pad($bin, count($words), "0", STR_PAD_LEFT);

        $res = array();
        for ($k=0; $k<count($words); $k++){
           //break, if maximum length is reached.
           if (count($res) == 3){
             break;
           }           

           //append element, if binary position says "1";
            if ($padded_bin[$k]==1){
                $res[] = $words[$k];
            }
        }

        sort($res);

        //check result array if combination already exists before inserting.
        $res_string =implode("_", $res);
        if (!in_array($res_string, $result)){ 
          $result[] = $res_string;
        } 
    }
    sort($result);
    return $result;
}

【讨论】:

  • 这行得通!但哇,这是一个记忆猪。它最多可以加载 10 个单词,但是当我遍历更大的字符串时,它开始真正变慢。有什么技巧可以防止它耗尽过多的内存?
  • 我认为这是您可以获得的最佳解决方案。对于“X”元素的所需输出,您将迭代“X”次 - 可能对重复访问 count() 进行一些调整 - 但这就是您提高性能所能做的所有事情。
  • 这个怎么样...如何限制返回结果的长度?例如,包括所有少于3 字长的组合?所以在一个 26 字长的字符串上运行它仍然会返回 xis_yap_zoo。试图节省内存。我尝试限制 for 循环,但它不起作用。我不明白在哪里可以停止循环if &gt; max
  • @Ryan:使用 break; 将数组 $res 的最大长度限制为 3 - 但这会产生重复 - 所以在插入之前检查数组 $result
  • 嗯,但这仍然将所有内容存储在内存中。对我来说,即使限制为 25 个单词中的 3 个也超时。
【解决方案2】:

可能有更优雅的解决方案,但我用 2 个函数做到了。

getCombosOfLength 函数给出数组中的每个 $intLength 组合。 GetCombos 函数只是为您想要的每个长度运行 GetCombosOfLength。这非常适合生成 1-5 个项目的所有组合。如果你对所有 25 项组合运行它,它就会出现一些问题。

$a = array("c", "b", "f", "v", "g", "e", "h", "i", "j", "k", "l", "m", "n", "p", "o", "r", "a", "q", "s", "t", "u", "w", "x", "y", "z");
$b = getCombos($a, 5);

print "<pre>\n";
print_r($b);

function getCombos($arrInput, $intMax = null) {
    sort($arrInput);
    if (is_null($intMax)) $intMax = count($arrInput);
    $arrOutput = array();
    for ($i = $intMax; $i > 0; $i--) {
        $arrReturn = getCombosOfLength($arrInput, $i);
        for ($j = 0; $j < count($arrReturn); $j++)  $arrOutput[] = $arrReturn[$j];
    }
    return $arrOutput;
}

function getCombosOfLength($arrInput, $intLength) {
    $arrOutput = array();
    if ($intLength == 1) {
        for ($i = 0; $i < count($arrInput); $i++) $arrOutput[] = array($arrInput[$i]);
        return $arrOutput;
    }
    $arrShift = $arrInput;
    while (count($arrShift)) {
        $x = array_shift($arrShift);
        $arrReturn = getCombosOfLength($arrShift, $intLength - 1);
        for ($i = 0; $i < count($arrReturn); $i++) {
            array_unshift($arrReturn[$i], $x);
            $arrOutput[] = $arrReturn[$i];
        }
    }
    return $arrOutput;
}

【讨论】:

    【解决方案3】:

    如果您使用的是 PHP 5.5,您可以使用生成器来消除内存问题。它还稍微降低了整体执行时间。

    Generators
    15: 256kb, 0.48s
    16: 256kb, 0.90s
    19: 256kb, 7.76s
    
    Original
    15:  5.75mb, 0.49s
    16: 17.25mb, 0.99s
    19: 86.24mb, 8.58s
    

    基于 dognose 的功能,您可以将 $result[] 赋值替换为 yield $res。该函数不是遍历整个循环并返回一个巨大的数组,而是一个接一个地遍历它,每次都返回(产生)单个元素。

    function combo_gen($str) 
    {
        $words = explode(' ',$str);
        $elements = pow(2, count($words))-1;
    
        $result = array();
    
        for ($i = 1; $i<=$elements; $i++)
        {
            $bin = decbin($i);
            $padded_bin = str_pad($bin, count($words), "0", STR_PAD_LEFT);
    
            $res = array();
            for ($k=0; $k<count($words); $k++){
                //append element, if binary position says "1";
                if ($padded_bin[$k]==1){
                    $res[] = $words[$k];
                }
            }
    
            sort($res);
    
            $res = implode("_", $res);
    
            yield $res;
        }
    }
    
    foreach(combo_gen('one two three') as $item) {
      //stuff
    }}
    

    【讨论】:

    • 如果我没有使用 PHP 5.5 的能力怎么办? (PHP 5.4)
    • 内存方面的改进非常好! - 但我认为对于 25 个单词的期望输入 - 不是记忆是主要问题 - 而是它所花费的时间。 (但是,到处提高资源消耗永远不会错)
    • @dognose 是的,不幸的是这是真的。虽然它仍然是展示生成器如何让事情变得更简单的一个很好的例子。
    猜你喜欢
    • 1970-01-01
    • 2017-06-23
    • 1970-01-01
    • 2022-11-04
    • 1970-01-01
    • 2014-03-20
    • 1970-01-01
    • 2019-08-30
    • 2017-07-25
    相关资源
    最近更新 更多