PHP数组:将每个子数组连接在一起(概率)

时间:2013-07-30 23:20:36

标签: php loops recursion multidimensional-array

我想找到更好的方法来做到这一点:

$array = array(
    array('a', 'b', 'c'),
    array('e', 'f', 'g'),
    array('h', 'i', 'j', 'k', 'l')
);

目标是打印这样的东西:

a e h
a e i
a e j
a e k
a e l

a f h
a f i
a f j
a f k
a f l

a g h
a g i
a g j
a g k
a g l

然后对bc执行相同操作。

目前,我正在使用此代码:

foreach ($array[0] as $val1) {
    foreach ($array[1] as $val2) {
        foreach ($array[2] as $val3) {
            echo "$val1  $val2  $val3 \n";
        }
        echo "--------\n";
    }
}

我还尝试动态创建上面的代码并使用eval执行它:

$eval         = '
     $data =array();
     ';
$eval_blocks  = '';
$eval_foreach = '';
$eval_data    = '
    $data[] = ';
$looplength   = count($array);

for ($i = 0; $i < $looplength; $i++) {
    $eval_foreach .= '
     foreach($array[' . $i . '] as $val' . ($i + 1) . '){
     ';
    if (($i + 1) == $looplength) {
        $eval_data .= ' $val' . ($i + 1) . ';';
    } else {
        $eval_data .= ' $val' . ($i + 1) . ' ." ".';
    }
    $eval_blocks .= '
     }
     ';
}
$eval = $eval . $eval_foreach . $eval_data . $eval_blocks;
eval($eval);
print_r($data);

但如果可能的话,我仍然希望找到更好的方法。

更新

注意:$array是动态的,可能包含两个子阵列或更多

6 个答案:

答案 0 :(得分:5)

我尝试了另一种方法,但最终以类似于Valentin CLEMENT的解决方案结束,尽管我的功能更加冗长。

尽管如此,原创性的是,这个函数可以为你提供一个组合树,根据你打算做什么,它可能(或可能不是)有用。

以下是代码:

function getCombinations( $arrayList, $index = 0  )
{
    $subCombinations = $combinations = '';

    if ( $index < count( $arrayList )-1 )
    {
        $subCombinations = getCombinations( $arrayList,  $index+1 );
    }

    foreach( $arrayList[$index] as $item )
    {
        $combinations[$item] = $subCombinations ;
    }
    return $combinations;
}

$combinations = getCombinations( $array );

print_r( $combinations );

使用示例数据:

$array = array(
    array('a', 'b', 'c'),
    array('e', 'f', 'g'),
    array('h', 'i', 'j', 'k', 'l')
);

将输出:

Array
(
    [a] => Array
        (
            [e] => Array
                (
                    [h] => 
                    [i] => 
                    [j] => 
                    [k] => 
                    [l] => 
                )

            [f] => Array
                (
                    [h] => 
                    [i] => 
                    [j] => 
                    [k] => 
                    [l] => 
                )

            [g] => Array
                (
                    [h] => 
                    [i] => 
                    [j] => 
                    [k] => 
                    [l] => 
                )

        )

    [b] => Array
        (
            [e] => Array
                (
                    [h] => 
                    [i] => 
                    [j] => 
                    [k] => 
                    [l] => 
                )

            [f] => Array
                (
                    [h] => 
                    [i] => 
                    [j] => 
                    [k] => 
                    [l] => 
                )

            [g] => Array
                (
                    [h] => 
                    [i] => 
                    [j] => 
                    [k] => 
                    [l] => 
                )

        )

    [c] => Array
        (
            [e] => Array
                (
                    [h] => 
                    [i] => 
                    [j] => 
                    [k] => 
                    [l] => 
                )

            [f] => Array
                (
                    [h] => 
                    [i] => 
                    [j] => 
                    [k] => 
                    [l] => 
                )

            [g] => Array
                (
                    [h] => 
                    [i] => 
                    [j] => 
                    [k] => 
                    [l] => 
                )

        )

)

然后需要额外的代码来绘制预期的结果:

function drawCombinations( $combinations, $line = array() )
{
    foreach( $combinations as $value => $children )
    {
        array_push( $line, $value );

        if ( is_array( $children ) ) 
        {
            drawCombinations( $children, $line );
        }
        else
        { 
            echo implode( " ", $line ) ." \n";
        }

        array_pop( $line );
    }

}


drawCombinations( $combinations );

生产:

a e h
a e i
a e j
a e k
a e l
a f h
a f i
a f j
a f k
a f l
a g h
a g i
a g j
a g k
a g l
b e h
b e i
b e j
b e k
b e l
b f h
b f i
b f j
b f k
b f l
b g h
b g i
b g j
b g k
b g l
c e h
c e i
c e j
c e k
c e l
c f h
c f i
c f j
c f k
c f l
c g h
c g i
c g j
c g k
c g l

正如我所说的那样,如果你不需要这个结果树(你的问题没有提及,我只是在搜索最佳方式时制作了这个),Valentin CLEMENT的方法可能会更好(如果你不使用太大的数据集,我会解释为什么之后)。

我用一种我认为更具可读性和可用性的方式重写了一下:

function expand( $array, $from = 0, $length = false ) 
{
    if ( $length === false )
    {
        $length = count( $array );
    }

    if ( $length == $from )
    {
        return array('');
    }
    else 
    {
        $result = array();

        foreach( $array[$from] as $x ) 
        {
            foreach( expand( $array, $from+1, $length ) as $tail )
            {
                $result[] = trim("$x $tail");
            }
        }
        return $result;
    }
}


$combinations = expand( $array );

print_r( $combinations );

返回以下数组:

Array
(
    [0] => a e h
    [1] => a e i
    [2] => a e j
    [3] => a e k
    [4] => a e l
    [5] => a f h
    [6] => a f i
    [7] => a f j
    [8] => a f k
    [9] => a f l
    [10] => a g h
    [11] => a g i
    [12] => a g j
    [13] => a g k
    [14] => a g l
    [15] => b e h
    [16] => b e i
    [17] => b e j
    [18] => b e k
    [19] => b e l
    [20] => b f h
    [21] => b f i
    [22] => b f j
    [23] => b f k
    [24] => b f l
    [25] => b g h
    [26] => b g i
    [27] => b g j
    [28] => b g k
    [29] => b g l
    [30] => c e h
    [31] => c e i
    [32] => c e j
    [33] => c e k
    [34] => c e l
    [35] => c f h
    [36] => c f i
    [37] => c f j
    [38] => c f k
    [39] => c f l
    [40] => c g h
    [41] => c g i
    [42] => c g j
    [43] => c g k
    [44] => c g l
)

然后很容易达到预期的结果:

echo implode( "\n", $combinations )."\n";

将输出:

a e h
a e i
a e j
a e k
a e l
a f h
a f i
a f j
a f k
a f l
a g h
a g i
a g j
a g k
a g l
b e h
b e i
b e j
b e k
b e l
b f h
b f i
b f j
b f k
b f l
b g h
b g i
b g j
b g k
b g l
c e h
c e i
c e j
c e k
c e l
c f h
c f i
c f j
c f k
c f l
c g h
c g i
c g j
c g k
c g l

起初,我认为我的解决方案比Valentin的解决方案占用更多内存,因为它使用数组,但是当我测试它时,我意识到它确实使用的内存略少。

根据这两种方法显示内存指标得出了这些结果:

drawCombinations(  getCombinations( $array ));

echo memory_get_usage()." ". memory_get_peak_usage()."\n";

// 238736 244896





echo implode( "\n", expand( $array ) )."\n";

echo memory_get_usage()." ". memory_get_peak_usage()."\n";

// 238912 252304

但是当使用更大的输入值时,它变得更加重要:

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

getCombinations给出:

drawCombinations(  getCombinations( $array ));

echo memory_get_usage()." ". memory_get_peak_usage()."\n";

// 242376 253008

展开给出:

echo implode( "\n", expand( $array ) )."\n";

echo memory_get_usage()." ". memory_get_peak_usage()."\n";

//242544 704520

如果我们查看每个函数生成的数组,原因很明显,因为第一个解决方案存储的重复值较少(我不确定PHP如何处理结束树的每个分支的重复数组)。

再一次,取决于你明白的目标,你会关心与否。

动态地“回显”每一行而不是创建一个大的结果数组会略微减少内存峰值问题,但随着数据集的增长,expand()仍会消耗更多的内存。

我希望它有所帮助,至少对我来说很有趣;)

答案 1 :(得分:4)

我首先选择了一些基于迭代(计数)的解决方案直接处理数组。事实证明,这只是一个有多个计数器的循环。然后我想,为什么要把它限制在阵列上?为什么不使用CartesianProductIterator来创建多个迭代器的笛卡尔积的迭代?

它类似于AppendIteratorMultipleIterator(请参阅:Iterating over Multiple Iterators at Once (Apr 2012; by hakre)),可以很容易地与您的数组一起使用:

$array = [
    ['a', 'b', 'c'],
    ['e', 'f', 'g'],
    ['h', 'i', 'j', 'k', 'l'],
];

$it = new CartesianProductIterator();

foreach($array as $sub)
{
    $it->append(new ArrayIterator($sub));
}

foreach ($it as $tuple)
{
    echo implode(',', $tuple), "\n";
}

输出符合预期:

a,e,h
a,e,i
a,e,j
...
c,g,j
c,g,k
c,g,l

这种迭代器的一个好处是它可以更灵活地接受输入。

此外,这些产品可能非常耗尽内存,迭代器是通过迭代解决问题来减少内存需求的好工具。

另一个好处是Iterator已经跟踪计算,因此产品每个维度的计数问题已经解决。

只要考虑内存消耗,而不是为您实现计数的迭代器,您可以使用循环对产品中的所有元组进行迭代。这个变种btw。实际上应该处理键,所以应该使用不同的输入(example code减少阅读):

...

// working loop
$valid = TRUE;
while ($valid)
{
    // create segment
    foreach ($array as $key => $sub)
    {
        echo $sub[$keys[$key][$counters[$key]]];
    }
    echo "\n";

    // count up
    foreach ($order as $key)
    {
        if (++$counters[$key] == $lengths[$key])
        {
            $counters[$key] = 0;
            continue;
        }
        continue 2;
    }
    $valid = FALSE;
};

如本例所示,循环中的每次迭代都会输出一个产品元组,因此内存需求也很低。如果您使用echo语句替换yield,则可以从中创建generator

由于CartesianProdcutIterator是一个对象所以它可以比循环或生成器做更多的事情,因此它有一些更多的功能:你可以指定迭代或计数或排序模式:首先移动最后一个迭代器(默认),或第一个:

$it = new CartesianProductIterator(CartesianProductIterator::ORDER_FIRST_FIRST);

这将进行以下迭代:

a,e,h
b,e,h
c,e,h
...
a,g,l
b,g,l
c,g,l

但不仅如此,通过在追加时指定$countOrder参数,甚至可以更加控制它。它指定了按订单模式订购的实际排序键:

$array = [
    0 => ['a', 'b', 'c'],
    2 => ['e', 'f', 'g'],
    1 => ['h', 'i', 'j', 'k', 'l'],
];

$it = new CartesianProductIterator();

foreach($array as $countOrder => $sub)
{
    $it->append(new ArrayIterator($sub), $countOrder);
}

foreach ($it as $tuple)
{
    echo implode(',', $tuple), "\n";
}

这(作为默认模式是last-first)指定首先在中间(e-g)迭代,然后在结束(h-1),然后在第一个(a-c)上迭代:

a,e,h
a,f,h
a,g,h
a,e,i
...
c,g,k
c,e,l
c,f,l
c,g,l

希望这有用并且有资格作为“更好的方式”。

答案 2 :(得分:3)

这可能有助于您尝试这样做

    class Test {
    // holds the combinations


     var $combinations= array(); 



    function getCombinations($batch, $elements, $i)  {
        if ($i >= count($elements)) {
            $this->combinations[] = $batch;
        } else {     
            foreach ($elements[$i] as $element) {
                $this->getCombinations(array_merge($batch, $element), $elements, $i + 1);
            }                       
        }
    }
}  

//测试它!

$traits = array (
    array(
        array('Happy'),
        array('Sad'),
        array('Angry'),
        array('Hopeful')
    ),
    array(
        array('Outgoing'),
        array('Introverted')
    ),
    array(
        array('Tall'),
        array('Short'),
        array('Medium')
    ),
    array(
        array('Violent'),
        array('Quiet'),
        array('Psychotic')
    ),
    array(
        array('Handsome'),
        array('Plain'),
        array('Ugly')
    )
);

$test = new Test();
$start = array();
$test->getCombinations($start, $traits, 0);   
print_r($test->combinations);   

答案 3 :(得分:1)

这应该有效:

function expand($arr){
    function recexpand($arr, $from, $len) {
        if ($from == $len) {
            yield "\n";
        } else {
            foreach($arr[$from] as $x) {
                foreach(expand($arr, $from+1, $len) as $tail) {
                    yield "$x $tail";
                }
            }
        }
    }
    return recexpand($arr, 0, count($arr);
}
$array = array(
    array('a', 'b', 'c'),
    array('e', 'f', 'g'),
    array('h', 'i', 'j', 'k', 'l')
);
foreach(expand($array) as $row) {
    echo $row;
}

回波:

a e h
a e i
a e j
a e k
a e l
a f h
a f i
a f j
a f k
a f l
a g h
a g i
a g j
a g k
a g l
b e h
b e i
b e j
b e k
b e l
b f h
b f i
b f j
b f k
b f l
b g h
b g i
b g j
b g k
b g l
c e h
c e i
c e j
c e k
c e l
c f h
c f i
c f j
c f k
c f l
c g h
c g i
c g j
c g k
c g l

我不是一个PHP人,因此可能有一种更惯用的方式来编写它,但它适用于任何长度的数组。

对于PHP&lt; 5(或任何没有'yield'语句的版本)

function expand($arr) {
    function recexpand($arr, $from, $len) {
        if ($from == $len) {
            return array("\n");
        } else {
            $result = array();
            foreach($arr[$from] as $x) {
                foreach(expand($arr, $from+1, $len) as $tail) {
                    $result[] = "$x " . $tail;
                }
            }
            return $result;
        }
    }
    return recexpand($arr, 0, count($arr));
}
$arr = array(
    array('a', 'b', 'c'),
    array('e', 'f', 'g'),
    array('h', 'i', 'j', 'k', 'l')
);
foreach(expand($arr) as $row) {
    echo "$row";
}

答案 4 :(得分:1)

我对你们提出的优秀算法印象深刻。 但是我认为PHP中的正确方法是使用Iterator:http://php.net/manual/fr/class.iterator.php

我实施了一个关于你可以为你的问题做些什么的例子。

class CombinationIterator implements Iterator {
    private $position = 0;
    private $array = array();

    public function __construct($array) {
        $this->array = $array;
        $this->position = array_fill(0,sizeof($this->array),0);
    }

    function rewind() {
        $this->position = array_fill(0,sizeof($this->array),0);
    }

    function current() {
        $word = array();
        foreach ($this->position as $i=>$pos) {
            $word[]=$this->array[$i][$pos];
        }
        return implode(" ",$word);
    }

    function key() {
        return $this->position;
    }
    function next() {
        foreach (array_reverse($this->position,true) as $i=>$pos) {
            # if position in this array has reached end, set it to 0 and increse next one
            if ($pos == sizeof($this->array[$i])-1) {
                $this->position[$i] = 0;
                if (array_key_exists($i-1,$this->position)) {
                    continue;
                } else {
                    $this->rewind();
                }
                break;
            } else {
                $this->position[$i]++;
                break;
            }

        }
    }
    function valid() {
        $valid = false;
        foreach ($this->position as $i=>$pos) {
            if ($pos < sizeof($this->array[$i])-1) { return true; }
        }
        return $valid;
    }

}

以下是如何使用它来显示您的文字:

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


$c = new CombinationIterator($array);
while ($c->valid()) {
    echo $c->current()."\n";
    $c->next();
}

我没有编写previous()方法,但你可以从next()方法轻松创建它。

此外,内存使用量非常低,因为您只能存储位置。

我希望它可以帮助您完成项目。

再见

答案 5 :(得分:1)

我的非递归方式:

<?php
$array = array(
    array('a', 'b', 'c'),
    array('e', 'f', 'g'),
    array('h', 'i', 'j', 'k', 'l')
);

$upperBounds = array();
foreach ($array as $arr) {
    $upperBounds[] = count($arr);
}

$counterArray = array_pad(array(), count($array), 0);

do {
    foreach ($counterArray as $key => $c) {
        echo $array[$key][$c];
    }
    echo PHP_EOL;
} while (incrementCounter($counterArray, $upperBounds));

function incrementCounter(&$counterArray, $upperBounds)
{
    if (count($counterArray) == 0 || count($counterArray) != count($upperBounds)) {
        return false;
    }
    $counterArray[0]++;
    foreach ($counterArray as $key => $value) {
        if ($counterArray[$key] >= $upperBounds[$key]) {
            $counterArray[$key] = 0;
            if (isset($counterArray[$key+1])) {
                $counterArray[$key+1]++;
            }
            else {
                $counterArray[$key+1] = 1;
                return false;
            }
        }
        else {
            break;
        }
    }
    return true;
}