序言
这个问题不即将学习PHP,也不是我计划在高效环境中使用的代码。我只是想看到并学习更好的方法来完成这项工作,正如我在我的方法中所做的那样。因此,请仅纠正我的代码或向我展示更好,更快或更短的解决方案。问题本身已经解决了。谢谢!
问题:
前几天用户asked a question在这里上了SO。他的问题引起了我的注意,因为我想找到一种方法来解决他的需求。
他希望获得PHP array
的所有可能的键组合,其中值的总和 100,或者尽可能接近可能为100 。他给了我们一个示例数组,我将用于我的例子:
$array = array(25, 30, 50, 15, 20, 30);
例如,一个结果应为[2, 4, 5]
,因为50 + 20 + 30
为100
。
$sum = $array[2] + $array[4] + $array[5]; // = 100
我认为基本的想法应该是明确的。现在让我们来看看我的工作......
我的方法:
所以这个问题引起了我作为开发人员的注意。起初,我认为这很简单。只需做一些添加并检查结果。但后来我注意到有一点需要牢记......
有很多组合需要测试。对于示例数组,最多可存在720(6! = 1*2*3*4*5*6 = 720
)个排列。为了获得所有可能的组合,我想首先得到阵列的所有可能的排列。
但那只是半真半假。因为数组中可能有双值(,如示例中的30
),我们无法获得数组值的所有可能排列,我们必须得到所有可能的排列。而是数组键。
所以我使用了php cookbook的pc_permut
功能并根据我的需要对其进行了修改。它将返回一组键中所有可能的排列。
/**
* gets all possible permutations of $array
* @param array $array
* @param array $permutations
* @return array
*/
function permutations($array, $permutations = array()) {
if( !empty($array) ) {
$result = array();
for( $i = count($array) - 1; $i >= 0; --$i ) {
$newItems = $array;
$newPerms = $permutations;
list($values) = array_splice($newItems, $i, 1);
array_unshift($newPerms, $values);
$result = array_merge($result, permutations($newItems, $newPerms));
}
}
else {
$result = array($permutations);
}
return $result;
}
此函数的结果是一个多维数组,包含有序键数组中的所有排列。
Array (
[0] => Array (
[0] => 0
[1] => 1
[2] => 2
[3] => 3
[4] => 4
[5] => 5
)
[1] => Array (
[0] => 1
[1] => 0
[2] => 2
[3] => 3
[4] => 4
[5] => 5
)
[...
)
所以,现在我得到了所有的排列。计算可能的组合并不是那么难。我只是循环遍历排列,增加总和直到它们达到100
或更高并返回组合键。
但我发现我错过了一件事。当我得到所有可能的排列时,甚至有一些结果在列表中加倍。为了解释,这两个结果基本相同:
[2, 4, 5]; // 50 + 20 + 30 = 100
[4, 5, 2]; // 20 + 30 + 50 = 100
我在计算后最终对键进行了排序,并将它们用作结果数组中的索引。所以可以肯定的是,每个组合只在结果中存在一次。这是我的combinations
函数:
/**
* gets all possible key combinations of $array with a sum below or equal $maxSum
* @param array $array
* @param integer $maxSum
* @return array
*/
function combinations($array, $maxSum) {
// get all permutations of the array keys
$permutations = permutations(array_keys($array));
$combinations = array();
// loop all permutations
foreach( $permutations as $keys ) {
// create a container for each permutation to store calculation
$current = array(
"sum" => 0,
"keys" => array()
);
// now loop through the permutation keys
foreach( $keys as $key ) {
// if the addition is still between or equal $maxSum
if( $current["sum"] + $array[$key] <= $maxSum ) {
// increment the sum and add key to result
$current["sum"] += $array[$key];
$current["keys"][] = $key;
}
}
// to be sure each combination only exists once in the result
// order the keys and use them as array index
sort($current["keys"]);
$combinations[join("", $current["keys"])] = $current;
}
// remove the created key-index from array when finished
return array_values($combinations);
}
执行很简单:
$array = array(25, 30, 50, 15, 20, 30);
print_r(combinations($array, 100));
结果是一个包含所有组合的数组。对于我们的示例数组,有11种可能的组合。结果如下:
Array (
[0] => Array (
[sum] => 90
[keys] => Array (
[0] => 0
[1] => 1
[2] => 3
[3] => 4
)
)
[1] => Array (
[sum] => 90
[keys] => Array (
[0] => 0
[1] => 2
[2] => 3
)
)
[...
由于我已将此脚本编写为answer of the original question,我会问自己,是否还有其他甚至更好的方法来完成这项工作。也许有一种没有排列的方法,或者从计算或结果数组中排除相同组合的方法。我知道我也可以直接在permutations
函数中执行计算,但这基本上是相同的工作流程。
我真的希望得到一些建议,提示或改进。我认为这里有一些改进脚本的潜力,但实际上我不知道怎么做。但我确信它可以更简单直接地完成......
感谢您的时间! :)
答案 0 :(得分:0)
对数组进行排序会带来一些可能性:这是我想到的那个:
我表示(selectedIndexes)由selectedIndexes的所有元素组成的元素,例如 a({25,30,30})=(25,30,30)
P(n)是索引1到n的所有组合的集合,为清楚起见,我的数组从索引1开始(因此P(2)= {1,2,(1,2)})
我正在使用下面的伪代码中解释的2个中断条件。第一个是aSorted =允许总和的第一个元素。 第二个是与aSorted第一个元素相比总和太小
selectedIndexes = {}
sum = 100
aSorted = {15, 20, 25, 30, 30, 50} //starting values for the example
//to clarify the following function
aSum = {15, 35, 60, 90}
function n(aSorted, sum, selectedIndexes){
compute aSum //precisely search in aSum for the index at which
//the elements are bigger than sum, and cut
answer = (P(count(aSum))) X a(selectedIndexes) // with X being the cartesian product
for (i=count(aSum)+1; i<=count(aSorted); i++){
newASorted = splice(aSorted, count(aSum))
// 1st break condition
if(newASorted is empty) return answer
// 2nd break condition the new sum < the first element of aSorted
if (aSorted(i)<sum && sum-aSorted(i)>=aSorted(1)){
answer += n(newASorted, sum-aSorted(i), push(selectedIndexes,
i))
}
}
return answer
}
这个算法的复杂性感觉是二次的(在快速检查之后它更像是n ^ log2(n)的顺序)关于数组中元素的数量
为了使它不那么抽象,让我们开发一个例子(警告我比伪代码更信任这个例子,尽管我自己在伪代码中没有看到不准确):
n({15,20,25,30,30,50},100,{})= P(4)+ n({15,20,25,30,30},50,{6}) + n({15,20,25,30},70,{5})
首先开发方程右侧的第n个函数
n({15,20,25,30,30},50,{5})=(P(2)X {6})+ n({15,20,25,30},20,{ 5,6}} + n({15,20,25},20,{4,6})+ n({15,20},25,{3,6}}
n({15,20,25,30},20,{5,6}} =(P(1)X {(5,6)})// + n({15},0,{ 2,5,6}}(但0 <15)断裂条件2
n({15,20,25},20,{4,6})= P(1)X {(4,6)} //和休息条件2
n({15,20},25,{3,6}} = P(1)X {(3,6)} // + n({15},5,{2,3,6} )(但5 <15)断裂条件2
现在开发方程右侧的第二个n函数
n({15,20,25,30},70,{5})=(P(3)X {5})+ n({15,20,25},40,{4,5} )
n({15,20,25},40,{4,5}} =(P(2)X {(4,5)})+ n({15,20},15,{3, 4,5}}
n({15,20},15,{3,4,5}} = P(1)x {(3,4,5)} // + n({},0,{1,3 ,4,5})新的休息条件aSum为空