抽象一下需求:
在构建某个对象时,它拥有多个属性,每个属性拥有多个可选的值,需要穷举出每个属性不同的选择组合构建出的不同对象,比如:
输入参数:
$arr = [
'Name' => ['Jack', 'Lily', 'Martin'],
'Age' => ['18', '12'],
'Gender' => ['male', 'female'],
'Address' => ['Alexander', 'Huston', 'NewYork'],
];
从每个属性中取一个值,根据排列组合计算知道可以得到 36 个不同的人。
期望结果:
篇幅原因,中间省略掉。
array (
0 =>
array (
'Name' => 'Jack',
'Age' => '18',
'Gender' => 'male',
'Address' => 'Alexander',
),
1 =>
array (
'Name' => 'Jack',
'Age' => '18',
'Gender' => 'male',
'Address' => 'Huston',
),
...
34 =>
array (
'Name' => 'Martin',
'Age' => '12',
'Gender' => 'female',
'Address' => 'Huston',
),
35 =>
array (
'Name' => 'Martin',
'Age' => '12',
'Gender' => 'female',
'Address' => 'NewYork',
),
)
实现代码:
/**
* 排列组合
* @param $arr
* @return array
*/
function pac($arr): array
{
if (empty($arr)) {
return [];
}
$n = count($arr); // 维度数量
$count = 1; // 结果总数
$size = []; // 每个属性的可选值个数
$visit = []; // 每个属性当前访问到的位置(下标)
$keyIndex = []; // 保存每个属性的key
$res = []; // 保存结果
// 初始化
$i = 0;
foreach ($arr as $key => $values) {
$size[$i] = count($values);
$visit[$i] = 0;
$count *= count($values);
$keyIndex[$i] = $key;
$i ++;
}
$m = 0;
$temp = [];
while (true) {
for ( $i = 0; $i < $n; $i++ ) {
// 取当前位置的下一个位置记录到最终的数组中
$temp[$m][$i] = $visit[$i] + 1;
}
$m ++;
for ( $i = $n - 1; $i >= 0; $i-- ) {
// 取的是可选值的最后一个值时,重置位置记录,下一个组合从0位置开始
if ($visit[$i] == $size[$i] - 1) {
$visit[$i] = 0;
} else {
// 当前属性未取完
break;
}
}
// 当所有属性的值都取完了,经过最后一次$i--, $i 值变为 -1,循环完成
if ($i < 0) {
break;
}
// 位置后移
$visit[$i] ++;
}
for ($i = 0; $i < $count; $i ++) {
for ($j = 0; $j < $n; $j ++) {
$res[$i][$keyIndex[$j]] = $arr[$keyIndex[$j]][$temp[$i][$j]-1];
}
}
return $res;
}
如果需求是想要从多个元素中取n个出来,得到多种不同的结果,如果可选元素集合为 ['a', 'b','c']
,n为2,这样传值:
$arr = [
['a', 'b','c'],
['a', 'b','c'],
];
得到的结果(部分):
array (
0 =>
array (
0 => 'a',
1 => 'a',
),
1 =>
array (
0 => 'a',
1 => 'b',
),
...
7 =>
array (
0 => 'c',
1 => 'b',
),
8 =>
array (
0 => 'c',
1 => 'c',
),
)