如何将数组拆分为所有可能的组合

时间:2018-09-03 01:38:00

标签: php

如何遍历一个数组,将其拆分为两个数组,并为每种可能的组合运行一个函数?顺序没关系。

// Original array
$array = array('a','b','c','d','e');

// Result 1
array('a');
array('b','c','d','e');

// Result 2
array('a', 'b');
array('c','d','e');

// Result 3
array('a', 'c');
array('b','d','e');

依此类推...

2 个答案:

答案 0 :(得分:2)

这是我能做的最好的事情

class Combos
{

    /**
     * getPossible then getDivide
     * 
     * @param array $input
     * @return array
     */
    public function getPossibleAndDivided( array $input )
    {
        return $this->getMultiShiftAndDivided( $this->getPossible( $input ) );
    }

    /**
     * return all possible combinations of input
     * 
     * @param array $input
     * @return array
     */
    public function getPossible( array $inputs )
    {
        $result = [];

        if ( count( $inputs ) <= 1 ) {
            $result = $inputs;
        } else {
            $result = array();
            foreach($inputs as $input){

                //make it an array
                $first = [ $input ];  

                //get all inputs not in first
                $remaining = array_diff( $inputs, $first );

                //send the remaining stuff but to ourself
                $combos = $this->getPossible( $remaining );//recursive

                //iterate over the above results (from ourself)
                foreach( $combos as $combo ) {
                    $last = $combo;
                    //convert to array if it's not
                    if( !is_array( $last ) ) $last = [ $last ];
                    //merge them
                    $result[] = array_merge( $first, $last );
                }
            }
        }
        return $result;
    }

    /**
     * shift and divide a multi level array
     *
     * @param array $array
     * @return array
     */
    public function getMultiShiftAndDivided( array $mArray )
    {
        $divided = [];
        foreach ( $mArray as $array ) {
            $divided = array_merge($divided, $this->getShiftAndDivided( $array ));
        }
        return $divided;
    }

    /**
     * shift and divide a single level array
     * 
     * @param array $array
     * @return array
     */
    public function getShiftAndDivided( array $array )
    {        
        $divided = [];
        $array1 = [];
        while( count( $array ) ){
            $array1[] = array_shift( $array );
            $divided[] = [ $array, $array1 ];
        }

        return $divided;
    }
}

工作方式

顶层,我不想深入了解所有细节。这基本上是一个两步过程,或者至少更容易以这种方式解决。我在一个类中构建它,以使所有内容保持整洁。它还可以提供更好的单元测试和可重用性。

这需要2次操作,或者至少对我而言,它更容易以2次而不是1次进行。

public function getPossibleAndDivided( array $input )
{
   return $this->getMultiShiftAndDivided( $this->getPossible( $input ) );
}

这是我使它成为一门课的主要原因,以使所有内容都很好地打包在一起。我将其称为包装器方法。

第一步

$ Combos-> getPossible(array $ input)

  1. 如果仅剩一项,则返回$inputs
    • 这是它可以拥有的所有组合(毕竟只是一个元素)。
  2. 其他使用foreach将$inputs迭代为$input
    1. $input包装为名为$first的数组
      • 这是来自$inputs
      • 的单个元素
    2. 以无损方式获取$inputs$remaining中的其余元素
      • 使用array_diff我们需要将两个元素都作为数组(见上文)
    3. 递归调用$this->getPossible($remaining)(请参阅#1)并返回为$combos
    4. $combos的foreach迭代为$combo
      1. $combo被分配给$last并变成一个数组(如果不是)
        • 有时combo是一个包含多个元素的数组
        • 有时是一个元素。这取决于递归调用
      2. 我们在结果集中添加了$first$last的合并
        • 我们都需要两个数组作为数组,以便我们可以合并而不会导致嵌套
  3. 结束。返回$result中的所有内容。

这基本上旋转了数组的所有组合,并以如下形式返回它们:

Array
(
    [0] => Array
        (
            [0] => a
            [1] => b
            [2] => c
            [3] => d
            [4] => e
        )

    [1] => Array
        (
            [0] => a
            [1] => b
            [2] => c
            [3] => e
            [4] => d
        )

    [2] => Array
        (
            [0] => a
            [1] => b
            [2] => d
            [3] => c
            [4] => e
        )
 ...
    [117] => Array
        (
            [0] => e
            [1] => d
            [2] => b
            [3] => c
            [4] => a
        )

    [118] => Array
        (
            [0] => e
            [1] => d
            [2] => c
            [3] => a
            [4] => b
        )

    [119] => Array
        (
            [0] => e
            [1] => d
            [2] => c
            [3] => b
            [4] => a
        )

)

是的,它返回119个结果,不,我不会全部包含它们。

第二步

别忘了上面的输出是一个多维数组(这在下面很重要)。

$ Combos-> getMultiShiftAndDivided(array $ mArray)

该方法旨在用于多维数组(因此得名)。我们从“步骤1”中获得此信息。它基本上是 $ Combos-> getShiftAndDivided($ array)

的包装
  1. $mArray的foreach迭代为$array
  2. 它调用$this->getShiftAndDivided($array)返回并与$divided合并。
    • 不需要存储结果,因此我没有在上面浪费任何变量。

输出示例:

$input = array(array('a','b','c','d','e'));
print_r($combos->getMultiShiftAndDivided($input));

Array
(
    [0] => Array
        (
            [0] => Array
                (
                    [0] => b
                    [1] => c
                    [2] => d
                    [3] => e
                )

            [1] => Array
                (
                    [0] => a
                )

        )
    ....
    [4] => Array
        (
            [0] => Array
                (
                )

            [1] => Array
                (
                    [0] => a
                    [1] => b
                    [2] => c
                    [3] => d
                    [4] => e
                )

        )

)

$ Combos-> getShiftAndDivided(array $ array)

此方法旨在用于单级数组。

  1. 只要$array的计数大于0,就循环,然后循环
  2. $array1从添加的$array中获取第一个元素,并从$array中删除该元素(破坏性地)
  3. 我们在结果$array中存储$array1$divided
    • 这会记录当时的当前“状态”
  4. $array中没有其他项目时,我们返回结果

输出示例:

$input = array('a','b','c','d','e');
print_r($combos->getShiftAndDivided($input));

Array
(
    [0] => Array
        (
            [0] => Array
                (
                    [0] => b
                    [1] => c
                    [2] => d
                    [3] => e
                )

            [1] => Array
                (
                    [0] => a
                )

        )
....
[4] => Array
    (
        [0] => Array
            (
            )

        [1] => Array
            (
                [0] => a
                [1] => b
                [2] => c
                [3] => d
                [4] => e
            )

    )

)

基本上,这会将单个数组的元素转换为两个结果数组,并在每次转换时记录其状态。我将其设置为2个函数,以便可以更轻松地对其进行测试和重新使用。

另一个问题是很难检查多维数组。我知道该怎么做,但是我不喜欢它,因为它有点丑陋,还有更好的方法。我之所以这样说,是因为可以在getMultiShiftAndDivided中使用一个一级数组,并且它不会给您您所期望的。可能会收到如下错误:

//I say probably, but I actually test it ... lol
Warning:  array_shift() expects parameter 1 to be array

这可能会引起混淆,但人们可能会认为代码是错误的。因此,通过将第二种方法调用的类型设置为getShiftAndDivided( array $array )。当wrapping方法尝试用字符串调用它时,它会被炸掉,但是会更好:

Catchable fatal error:  Argument 1 passed to Combos::getShiftAndDivided() must be of the type array, string given

希望这是有道理的,在这种情况下,我总是尝试这样做。从长远来看,这只会使生活更轻松。这两个函数都以相同的格式返回数据,这很方便(不客气)。

摘要

因此,这样做的总和是找到我们输入的所有组合,然后将其取用并将每个输入分解为移位和分割的数组。出于这个理由,我们无论如何都将所有可能的组合分成2个数组。因为那几乎就是我所说的。

现在我不是100%做到这一点,您可以根据需要检查它们,最后返回599个元素。因此,我很幸运,我建议您仅测试 $ combos-> getPossible($ input)的结果。如果它具有应有的所有组合,那么它将具有所需的全部。我不确定它是否返回重复项,我认为没有指定。但是我并没有真正检查它。

您可以这样调用main方法:

$input = array('a','b','c','d','e');  
print_r((new Combos)->getPossibleAndDivided($input));

Test It!

P.S。我把刹车当刹车,但我可以这样写代码,图...

答案 1 :(得分:2)

这是我的看法:

<?php
$ar = ['a','b','c','d','e'];

function permuteThrough($ar, $callback, $allowMirroredResults = true, $mode = 'entry', $desiredLeftCount = null, $left = [], $right = [])
{
    switch($mode)
    {
        case 'entry':
            // Logic:
            // Determine how many elements we're gonna put into left array
            $len = $allowMirroredResults ? count($ar) : floor(count($ar)/2);

            for($i=0; $i <= $len; $i++)
            {
                    call_user_func(__FUNCTION__, $ar, $callback, $allowMirroredResults, 'permute',$i);
            }
            break;
        case 'permute':
            // We have nothing left to sort, let's tell our callback
            if( count($ar) === 0 )
            {
                $callback($left,$right);
                break;
            }

            if( count($left) < $desiredLeftCount )
            {
                // Note: PHP assigns arrays as clones (unlike objects)
                $ar1 = $ar;
                $left1 = $left;
                $left1[] = current(array_splice($ar1,0,1));
                call_user_func(__FUNCTION__, $ar1, $callback, $allowMirroredResults, 'permute', $desiredLeftCount, $left1, $right);
            }

            // This check is needed so we don't generate permutations which don't fulfill the desired left count
            $originalLength = count($ar) + count($left)+count($right);
            if( count($right) < $originalLength - $desiredLeftCount )
            {
                $ar2 = $ar;
                $right1 = $right;
                $right1[] = current(array_splice($ar2,0,1));
                call_user_func(__FUNCTION__, $ar2, $callback, $allowMirroredResults, 'permute', $desiredLeftCount, $left, $right1);
            }
            break;
    }
}

function printArrays($a,$b)
{
    echo '['.implode(',',$a).'],['.implode(',',$b)."]\n";
}

permuteThrough($ar, 'printArrays', true); // allows mirrored results
/*
[],[a,b,c,d,e]
[a],[b,c,d,e]
[b],[a,c,d,e]
[c],[a,b,d,e]
[d],[a,b,c,e]
[e],[a,b,c,d]
[a,b],[c,d,e]
[a,c],[b,d,e]
[a,d],[b,c,e]
[a,e],[b,c,d]
[b,c],[a,d,e]
[b,d],[a,c,e]
[b,e],[a,c,d]
[c,d],[a,b,e]
[c,e],[a,b,d]
[d,e],[a,b,c]
[a,b,c],[d,e]
[a,b,d],[c,e]
[a,b,e],[c,d]
[a,c,d],[b,e]
[a,c,e],[b,d]
[a,d,e],[b,c]
[b,c,d],[a,e]
[b,c,e],[a,d]
[b,d,e],[a,c]
[c,d,e],[a,b]
[a,b,c,d],[e]
[a,b,c,e],[d]
[a,b,d,e],[c]
[a,c,d,e],[b]
[b,c,d,e],[a]
[a,b,c,d,e],[]

*/
echo "==============\n"; // output separator
permuteThrough($ar, 'printArrays', false); // doesn't allow mirrored results
/*
[],[a,b,c,d,e]
[a],[b,c,d,e]
[b],[a,c,d,e]
[c],[a,b,d,e]
[d],[a,b,c,e]
[e],[a,b,c,d]
[a,b],[c,d,e]
[a,c],[b,d,e]
[a,d],[b,c,e]
[a,e],[b,c,d]
[b,c],[a,d,e]
[b,d],[a,c,e]
[b,e],[a,c,d]
[c,d],[a,b,e]
[c,e],[a,b,d]
[d,e],[a,b,c]
*/

我的permuteThrough函数带有三个参数。
数组,回调和一个可选的布尔值,指示是否要允许镜像结果。
逻辑很简单:
首先决定要在左侧数组中放入多少个元素。
然后像这样递归调用该函数:
我们的工作数组将其余元素进行排序。
移开一个元素并将其放入左侧数组。结果将发送到另一层递归。
移开元素并将其放入正确的数组。结果将发送到另一层递归。
如果没有剩余要偏移的元素,请使用产生的左右数组调用回调。
最后,确保在开始时没有超过由for循环确定的所需左数组元素的大小,并确保正确的大小不会变得太大以至于无法满足所需的左大小。通常,这将由两个单独的功能完成。一个决定应将多少个元素放入左数组。和一个用于递归。但是,相反,我将递归函数的另一个参数扔掉了,以消除对单独函数的需要,这样就可以由同一个递归函数来全部处理。