如何在几个数组中生成元素的组合?

时间:2010-08-15 01:03:50

标签: php arrays

这是我的第一个问题:)

我有一个包含多个数组子节点的数组,每个子节点都有唯一的值,并希望获得这些值的所有可能的唯一组合。

数组的数量是已知的,但可能会随着时间而改变。

例如,

array(
  [0] => array([0]=>'blue',[1]=>'red'),
  [1] => array([0]=>'sunny',[1]=>'cloudy'),
  [2] => array([0]=>'sweet',[1]=>'acid');

我该怎么办才能获得:

array(
  [0] => array([0]=>'blue',[1]=>'sunny',[2]=>'sweet'),
  [1] => array([0]=>'blue',[1]=>'sunny',[2]=>'acid'),
  [2] => array([0]=>'blue',[1]=>'cloudy',[2]=>'sweet'),
  [3] => array([0]=>'blue',[1]=>'cloudy',[2]=>'acid'),
  [4] => array([0]=>'red',[1]=>'sunny',[2]=>'sweet'),
  [5] => array([0]=>'red',[1]=>'sunny',[2]=>'acid'),
  [6] => array([0]=>'red',[1]=>'cloudy',[2]=>'sweet'),
  [7] => array([0]=>'red',[1]=>'cloudy',[2]=>'acid'));

我尝试过使用嵌套循环,但我的逻辑不太强。

非常感谢,如果有人可以放弃一些光线

5 个答案:

答案 0 :(得分:8)

注意:需要稍微修改才能在PHP中使用< 5.3)

执行此操作(example在线翻译):

$f = function () { return func_get_args(); };
$res = array_outer($f,
    array("blue", "red"),
    array("sunny", "cloudy"),
    array("sweet", "acid"));

在Mathematica的Outer中受到启发的函数array_outer就是:

/**
 * A generalization of the outer product, forming all the possible
 * combinations of the elements of any number of arrays and feeding
 * them to $f.
 * The keys are disregarded
 **/
function array_outer($f, array $array1) {
    $res = array();
    $arrays = func_get_args();
    array_shift($arrays);
    foreach ($arrays as $a) {
        if (empty($a))
            return $res;
    }

    $num_arrays = count($arrays);
    $pos = array_fill(0, $num_arrays, 0);
    while (true) {
        $cur = array();
        for ($i = 0; $i < $num_arrays; $i++) {
            $cur[] = $arrays[$i][$pos[$i]];
        }
        $res[] = call_user_func_array($f, $cur);
        for ($i = $num_arrays-1; $i >= 0; $i--) {
            if ($pos[$i] < count($arrays[$i]) - 1) {
                $pos[$i]++;
                break;
            } else {
                if ($i == 0)
                    break 2;
                $pos[$i] = 0;
            }
        }
    }
    return $res;
}

答案 1 :(得分:4)

这是一种递归方法:

$arr =  array(
            0 => array(0 =>'blue', 1 =>'red'),
            1 => array(0 =>'sunny', 1 =>'cloudy'),
            2 => array(0 =>'sweet', 1 =>'acid')
        );

$combinations = array();
getArrayCombinations($arr, $combinations);
echo '<pre>';print_r($combinations);

/**
 * Creates an array with all possible combinations
 * @param array main_array - Array to find all the possible combinations of
 * @param array combinations - Array to store the resulting array in
 * @param array batch
 * @param int index
 */
function getArrayCombinations($main_array, &$combinations, $batch=array(), $index=0)
{
    if ($index >= count($main_array))
        array_push($combinations, $batch);
    else
        foreach ($main_array[$index] as $element)
        {
            $temp_array = $batch; array_push($temp_array, $element);
            getArrayCombinations($main_array, $combinations, $temp_array, $index+1);
        }
}

答案 2 :(得分:2)

你真正想要的是一种迭代序列的方法:

000
001
010
011
100
101
110
111

如果我们不必假设每个输入数组的大小相同,那也会很好。因此,如果我们将第二个数组的大小减小1:

array(
  [0] => array([0]=>'blue',[1]=>'red'),
  [1] => array([0]=>'sunny'),
  [2] => array([0]=>'sweet',[1]=>'acid');

...我们希望该列的最大值减少1:

000
001
100
101

这种抽象使问题更容易思考。你会如何重复这个序列?在每次迭代时,将最右边的列增加1.如果这样做会将其增加到超出其最大值,则将其重置为0并向左移动一列。现在,您重复刚才在最后一栏中所做的事情。如果您也无法增加此列,请将其重置为0,向左移动,冲洗并重复。如果你一直移动并且在没有超出其最大值的情况下无法增加任何列,那么你就完成了。

我们可以在PHP迭代器中包含上述逻辑:

class Sequence implements Iterator {

    private $input;

    private $hasNext;
    private $positions;

    public function __construct(array $input) {
        $this->input = $input;
    }

    public function rewind() {
        $this->hasNext = true;
        $this->positions = array();
        for ($i = 0; $i < count($this->input); $i++) {
            $this->positions[$i] = 0;
        }
    }

    public function valid() {
        return $this->hasNext;
    }

    public function current() {
        $current = array();
        for ($i = 0; $i < count($this->positions); $i++) {
            $current[] = $this->input[$i][$this->positions[$i]];
        }
        return $current;
    }

    public function key() {}

    public function next() {
        for ($i = count($this->positions) - 1; $i >= 0; $i--) {
            if ($this->positions[$i] < count($this->input[$i]) - 1) {
                $this->positions[$i]++;
                break;
            } else {
                $this->positions[$i] = 0;
                $this->hasNext = $i !== 0;
            }
        }
    }

}

next()是上述逻辑的实现。 reset()只需将每列设置回0,current()使用当前序列作为输入的索引,以返回当前值。

此处处于行动状态(删除“阴天”以显示解决方案的一般性):

$input = array(
    array('blue', 'red'),
    array('sunny'),
    array('sweet', 'acid')
);

$lst = new Sequence($input);
foreach ($lst as $elt) {
    print(implode(', ', $elt) . "\n");
}

及其输出:

blue, sunny, sweet
blue, sunny, acid
red, sunny, sweet
red, sunny, acid

答案 3 :(得分:2)

非常简单的解决方案:

$arr = array(
    array('a', 'b', 'c'),
    array('x', 'y', 'z'),
    array('1', '2')
);

$result = array();
foreach ($arr as $a) {
    if (empty($result)) {
        $result = $a;
        continue;
    }

    $res = array();
    foreach ($result as $r) {
        foreach ($a as $v) {
            $res[] = array_merge((array)$r, (array)$v);
        }
    }

    $result = $res;
}

var_dump($result);

答案 4 :(得分:0)

latenight伪代码:

result = []
counter = 0
for i in length(array[0]):
  for j in length(array[1]):
    for k in length(array[2]):      
      result[counter] = aray(0: array[0][i], 1: array[1][j], 2: array[2][k])
      counter+=1

虽然如果数组的数量会变大或动态变化,可能会对递归方法提出强烈的要求