PHP:从添加到给定数量的数字列表中找到两个或更多数字

时间:2010-04-19 13:19:54

标签: php sum

我正在尝试创建一个可以让我的生活更轻松的小PHP脚本。 基本上,我将在一个页面上有21个文本字段,我将输入20个不同的数字。在最后一个字段中,我将输入一个数字,我们称之为TOTAL AMOUNT。我希望脚本所做的只是指出添加的20个字段中的哪些数字将达到总金额。

示例:

field1 = 25.23
field2 = 34.45
field3 = 56.67
field4 = 63.54
field5 = 87.54
....
field20 = 4.2

Total Amount = 81.90

输出:field1 + fields3 = 81.90

某些字段的值可能为0,因为有时我只需要输入5-15个字段,最大值为20。

如果有人可以帮我解决这个问题的PHP代码,将不胜感激。

7 个答案:

答案 0 :(得分:7)

如果你看一下oezis algorithm,就会立即明白一个缺点:它花了很多时间总结已知不起作用的数字。 (例如,如果1 + 2已经太大,尝试1 + 2 + 3,1 + 2 + 3 + 4,1 + 2 + 3 + 4 + 5,......也没有任何意义。)

因此我写了一个改进的版本。它不使用位神奇,它使一切手动。缺点是,它需要对输入值进行排序(使用rsort)。但那应该不是一个大问题;)

function array_sum_parts($vals, $sum){
    $solutions = array();
    $pos = array(0 => count($vals) - 1);
    $lastPosIndex = 0;
    $currentPos = $pos[0];
    $currentSum = 0;
    while (true) {
        $currentSum += $vals[$currentPos];

        if ($currentSum < $sum && $currentPos != 0) {
            $pos[++$lastPosIndex] = --$currentPos;
        } else {
            if ($currentSum == $sum) {
                $solutions[] = array_slice($pos, 0, $lastPosIndex + 1);
            }

            if ($lastPosIndex == 0) {
                break;
            }

            $currentSum -= $vals[$currentPos] + $vals[1 + $currentPos = --$pos[--$lastPosIndex]];
        }
    }

    return $solutions;
}

oezis测试程序的修改版本(见结束)输出:

possibilities: 540
took: 3.0897309780121

所以只执行 3.1秒,而oezis代码在我的机器上执行 65秒(是的,我的机器非常慢)。这比快20倍

此外,您可能会注意到,我的代码找到540而不是338种可能性。这是因为我调整了测试程序以使用整数而不是浮点数。 直接浮动点比较很少是正确的做法,这是一个很好的例子:你有时得到59.959999999999而不是59.96,因此不会计算匹配。所以,如果我用整数运行oezis代码,它也会找到540种可能性;)

测试程序:

// Inputs
$n = array();
$n[0]  = 6.56;
$n[1]  = 8.99;
$n[2]  = 1.45;
$n[3]  = 4.83;
$n[4]  = 8.16;
$n[5]  = 2.53;
$n[6]  = 0.28;
$n[7]  = 9.37;
$n[8]  = 0.34;
$n[9]  = 5.82;
$n[10] = 8.24;
$n[11] = 4.35;
$n[12] = 9.67;
$n[13] = 1.69;
$n[14] = 5.64;
$n[15] = 0.27;
$n[16] = 2.73;
$n[17] = 1.63;
$n[18] = 4.07;
$n[19] = 9.04;
$n[20] = 6.32;

// Convert to Integers
foreach ($n as &$num) {
    $num *= 100;
}
$sum = 57.96 * 100;

// Sort from High to Low
rsort($n);

// Measure time
$start = microtime(true);
echo 'possibilities: ', count($result = array_sum_parts($n, $sum)), '<br />';
echo 'took: ', microtime(true) - $start;

// Check that the result is correct
foreach ($result as $element) {
    $s = 0;
    foreach ($element as $i) {
        $s += $n[$i];
    }
    if ($s != $sum) echo '<br />FAIL!';
}

var_dump($result);

答案 1 :(得分:4)

抱歉添加新答案,但这是一个全新的解决方案,可以解决生活,宇宙和所有问题......:

function array_sum_parts($n,$t,$all=false){
    $count_n = count($n); // how much fields are in that array?
    $count = pow(2,$count_n); // we need to do 2^fields calculations to test all possibilities

    # now i want to look at every number from 1 to $count, where the number is representing
    # the array and add up all array-elements which are at positions where my actual number
    # has a 1-bit
    # EXAMPLE:
    # $i = 1  in binary mode 1 = 01  i'll use ony the first array-element
    # $i = 10  in binary mode 10 = 1010  ill use the secont and the fourth array-element
    # and so on... the number of 1-bits is the amount of numbers used in that try

    for($i=1;$i<=$count;$i++){ // start calculating all possibilities
        $total=0; // sum of this try
        $anzahl=0; // counter for 1-bits in this try
        $k = $i; // store $i to another variable which can be changed during the loop
        for($j=0;$j<$count_n;$j++){ // loop trough array-elemnts
            $total+=($k%2)*$n[$j]; // add up if the corresponding bit of $i is 1
            $anzahl+=($k%2); // add up the number of 1-bits
            $k=$k>>1; //bit-shift to the left for looking at the next bit in the next loop
        }
        if($total==$t){
            $loesung[$i] = $anzahl; // if sum of this try is the sum we are looking for, save this to an array (whith the number of 1-bits for sorting)
            if(!$all){
                break; // if we're not looking for all solutions, make a break because the first one was found
            }
        }
    }

    asort($loesung); // sort all solutions by the amount of numbers used


    // formating the solutions to getting back the original array-keys (which shoud be the return-value)
    foreach($loesung as $val=>$anzahl){
        $bit = strrev(decbin($val));
        $total=0;
        $ret_this = array();
        for($j=0;$j<=strlen($bit);$j++){
            if($bit[$j]=='1'){
                $ret_this[] = $j;
            }   
        }
        $ret[]=$ret_this;
    }

    return $ret;
}

// Inputs
$n[0]=6.56;
$n[1]=8.99;
$n[2]=1.45;
$n[3]=4.83;
$n[4]=8.16;
$n[5]=2.53;
$n[6]=0.28;
$n[7]=9.37;
$n[8]=0.34;
$n[9]=5.82;
$n[10]=8.24;
$n[11]=4.35;
$n[12]=9.67;
$n[13]=1.69;
$n[14]=5.64;
$n[15]=0.27;
$n[16]=2.73;
$n[17]=1.63;
$n[18]=4.07;
$n[19]=9.04;
$n[20]=6.32;

// Output
$t=57.96;

var_dump(array_sum_parts($n,$t)); //returns one possible solution (fuc*** fast)

var_dump(array_sum_parts($n,$t,true)); // returns all possible solution (relatively fast when you think of all the needet calculations)

如果你不使用第三个参数,它会返回最佳(使用最少数量的数字)解决方案作为数组(输入数组的whith键) - 如果你将第三个参数设置为true ,返回所有解决方案(对于测试,我在他的帖子中使用与zaf相同的数字 - 在这种情况下有338个解决方案,在我的机器上找到~10秒)。

编辑: 如果你得到了所有,你得到的结果是“最好的” - 除此之外,你只得到第一个找到的解决方案(不一定是最好的)。

EDIT2: 为了满足一些解释的愿望,我评论了代码的基本部分。如果有人需要更多解释,请询问

答案 2 :(得分:3)

1. Check and eliminate fields values more than 21st field

2. Check highest of the remaining, Add smallest, 

3. if its greater than 21st eliminate highest (iterate this process)

   4. If lower: Highest + second Lowest, if equal show result.

   5. if higher go to step 7

   6. if lower go to step 4

7. if its lower than add second lowest, go to step 3.

8. if its equal show result

这样做效率很高,执行时间也会缩短。

答案 3 :(得分:1)

以下方法会给你一个答案...几乎所有的时间。根据您的喜好增加迭代变量。

<?php

// Inputs
$n[1]=8.99;
$n[2]=1.45;
$n[3]=4.83;
$n[4]=8.16;
$n[5]=2.53;
$n[6]=0.28;
$n[7]=9.37;
$n[8]=0.34;
$n[9]=5.82;
$n[10]=8.24;
$n[11]=4.35;
$n[12]=9.67;
$n[13]=1.69;
$n[14]=5.64;
$n[15]=0.27;
$n[16]=2.73;
$n[17]=1.63;
$n[18]=4.07;
$n[19]=9.04;
$n[20]=6.32;

// Output
$t=57.96;

// Let's try to do this a million times randomly
// Relax, thats less than a blink
$iterations=1000000;
while($iterations-->0){
    $z=array_rand($n, mt_rand(2,20));
    $total=0;
    foreach($z as $x) $total+=$n[$x];
    if($total==$t)break;
}

// If we did less than a million times we have an answer
if($iterations>0){
    $total=0;
    foreach($z as $x){
        $total+=$n[$x];
        print("[$x] + ". $n[$x] . " = $total<br/>");
    }
}

?>

一个解决方案:

[1] + 8.99 = 8.99
[4] + 8.16 = 17.15
[5] + 2.53 = 19.68
[6] + 0.28 = 19.96
[8] + 0.34 = 20.3
[10] + 8.24 = 28.54
[11] + 4.35 = 32.89
[13] + 1.69 = 34.58
[14] + 5.64 = 40.22
[15] + 0.27 = 40.49
[16] + 2.73 = 43.22
[17] + 1.63 = 44.85
[18] + 4.07 = 48.92
[19] + 9.04 = 57.96

答案 4 :(得分:1)

带回溯的可能效率低下但简单的解决方案

function subset_sums($a, $val, $i = 0) {
    $r = array();
    while($i < count($a)) {
        $v = $a[$i];
        if($v == $val)
            $r[] = $v;
        if($v < $val)
            foreach(subset_sums($a, $val - $v, $i + 1) as $s)
                $r[] = "$v $s";
        $i++;
    }
    return $r;
}

例如

$ns = array(1, 2, 6, 7, 11, 5, 8, 9, 3);
print_r(subset_sums($ns, 11));

结果

Array
(
    [0] => 1 2 5 3
    [1] => 1 2 8
    [2] => 1 7 3
    [3] => 2 6 3
    [4] => 2 9
    [5] => 6 5
    [6] => 11
    [7] => 8 3
)

答案 5 :(得分:0)

在不知道这是否是家庭作业的情况下,我可以给你一些伪代码作为可能的解决方案的提示,注意解决方案效率不高,更多的是演示。

提示:

将每个字段值与所有字段值进行比较,并在每次迭代时检查它们的总和是否等于TOTAL_AMOUNT

伪代码:

for i through field  1-20
 for j through field 1-20
   if value of i + value of j == total_amount
        return i and j

更新

您似乎拥有的是Subset sum problem,在Wiki链接中给出的是算法的伪代码,可能有助于指向正确的方向。

答案 6 :(得分:0)

我认为答案并不像nik提到的那么容易。让我们你有以下数字:

1 2 3 6 8

寻找10

的金额

niks解决方案会这样做(如果我理解的话):

1*8 = 9 = too low
adding next lowest (2) = 11 = too high

现在他将删除高位数并再次开始获取新的最高

1*6 = 7 = too low
adding next lowest (2) = 9 = too low
adding next lowest (3) = 12 = too high

......依此类推,完美答案就是这样 是8+2 = 10 ...我认为唯一的解决方案是尝试每种可能的组合 数字并停止,如果找到你正在寻找的amaunt(或重新计算所有,如果有不同的解决方案,并保存哪个使用最少的数字)。

编辑:真正计算21个数字的所有可能的组合将真正地结束,真的,真的很多计算 - 所以必须有任何“智能”的解决方案,以特殊的顺序添加数字(喜欢那个在niks post - with一些改进,也许会带给我们一个可靠的解决方案)