我想找到更好的方法来做到这一点:
$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
然后对b
和c
执行相同操作。
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
是动态的,可能包含两个子阵列或更多
答案 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
来创建多个迭代器的笛卡尔积的迭代?
它类似于AppendIterator
和MultipleIterator
(请参阅: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;
}