tags:

views:

256

answers:

8

Hi Guys,

I am trying to create a little php script that can make my life a bit easier. Basically, I am going to have 21 text fields on a page where I am going to input 20 different numbers. In the last field I will enter a number let's call it the TOTAL AMOUNT. All I want the script to do is to point out which numbers from the 20 fields added up will come up to TOTAL AMOUNT.

Example:

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

Total Amount = 81.90

Output: field1 + fields3 = 81.90

Some of the fields might have 0 as value because sometimes I only need to enter 5-15 fields and the maximum will be 20.

If someone can help me out with the php code for this, will be greatly appreciated.

A: 

Without knowing if this is a homework assignment or not, I can give you some pseudo code as a hint for a possible solution, note the solution is not very efficient, more of a demonstration.

Hint:

Compare each field value to all field value and at each iteration check if their sum is equal to TOTAL_AMOUNT.

Pseudo code:

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

Update:

As stereofrog as pointed out, what you seem to be having is the Subset sum problem, given within the Wiki link is pseudo code for the algorithm which might help point you in the right direction.

Anthony Forloney
it could be more than 2 fields
nik
Not a very efficient solution - but workable for the small dataset being used - a better solution would be to use the values as index keys then search for (total_amount-i) but this would have problems with duplicate values.
symcbean
@nik,@symcbean: Yeah it's not a very efficient solution, but I wanted to demonstrate a possible comparison for the OP. Now seeing that another requirement is for more than 2 fields, I will try my best, while at work, to update my answer
Anthony Forloney
Thx for your reply guys. Just to make it clear this is not a school project or homework. I was trying to set up a way to find from of list of invoices which ones add up to a summ of money that has been tranfered into the company account. The accounting software that we have in place right now is not the brightest one. I'll give it a try tonight after work see if I can figure something out.
Splash
+2  A: 
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

This is efficient and will take less execution time.

nik
maybe it't efficient, but i don't think it will get the right result. see my post for explanation - and correct me if i understand something wrong, please.
oezi
have added 4th 5th 6th step, hope the problem is solved
nik
A: 

i don't think the answer isn't as easy as nik mentioned. let's ay you have the following numbers:

1 2 3 6 8

looking for an amount of 10

niks solution would do this (if i understand it right):

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

now he would delete the high number and start again taking the new highest

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

... and so on, where the perfect answer would simply be 8+2 = 10... i think the only solution is trying every possible combination of numbers and stop if the amaunt you are looking for is found (or realy calculate all, if there are different solutions and save which one has used least numbers).

EDIT: realy calculating all possible combiations of 21 numbers will end up in realy, realy, realy much calculations - so there must be any "intelligent" solution for adding numbers in a special order (lik that one in niks post - with some improvements, maybe that will bring us to a reliable solution)

oezi
hey oezi, u were rite. I have done some changes, please check.I hope this will work bettter for more values like u have taken 1 2 3 6 8, wat if there are 15 more values grater than 10, this algo will cancel them out in one go saving exponential checks, hence less execution time
nik
hm... i don't think it's correct, yet (but i'm not sure about this). i'm sitting i a lecture at the moment, so i have a lot of time trying out an idea your answer has given me - i'll download mamp now and try something out.
oezi
+1  A: 

Following method will give you an answer... almost all of the time. Increase the iterations variable to your taste.

<?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/>");
    }
}

?>

One solution:

[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
zaf
+1 for a solution would work now and again, but it isn't 100% reliable - which is realy kind of stupid.
oezi
The logic behind this method of computation is far from stupid.
zaf
but it's a bad habit that (only in some rarely moments) this script will end up with no solution whereas there is one wich simply wasn't found - so you have to do more iterations to be more reliable (and also in that case it _could_ be the script doesn't find anything)... with the numbers you have taken for testing there are 338 possible solutions, what if there is only one? how many times doesn't this script find that solution - and how long will it take?
oezi
It's one of many methods to find solutions, not a bad habit. Of course, in the extreme case, if there was only one particular solution then the code would not find a solution most of the time. This is a NP-complete problem. The more methods you know the better you'll handle this class of problems. Anyway, I'm not capable of answering your specific questions (I'm not a mathematician), maybe you can ask over at http://mathoverflow.net/ I've just seen your latest answer - let me check it out.
zaf
i've just commented the code in my post, maybe you can take a look at that again - and thanks for the hint to mathoverflow... at a first glace it looks like a place full of absolute freaks o.0
oezi
+1 I think this is a very good method if you need only one match.
nikic
+2  A: 

sorry for adding a new answer, but this is a complete new solution which solves all problems of life, universe and everything... simply use this function i wrote:

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)

if you don't use the third parameter, it returns the best (whith the least amount numbers used) solution as array (whith keys of the input-array) - if you set the third parameter to true, ALL solutions are returned (for testing, i used the same numbers as zaf in his post - there are 338 solutions in this case, found in ~10sec on my machine).

EDIT: if you get all, you get the results ordered by which is "best" - whithout this, you only get the first found solution (which isn't necessarily the best).

EDIT2: to forfil the desire of some explanation, i commented the essential parts of the code . if anyone needs more explanation, please ask

oezi
Thx oezi, I'll give it a try tonight. Will post the outcome. Cristian.
Splash
Before I +1 you, could you please give some explanation to this algorithm?
zaf
i commented the code above, hoping this will help you understand - if not, please ask again.
oezi
For the total calculations is it not the factorial of 20 (20!) ? What a second, I see what you are doing. OK.
zaf
nice to see how many german users there are on stackoverflow :D I see many code snippets with varnames likes `$loesung` or `$anzahl`. @oezi: You should change your <= comparisons to < comparisons. This will remove the notices and it will save you one code run (the latter doesn't matter, but still there's no need to do it.) Though I wonder what makes this algorithm *so* slow. I would understand that my implementation is two or three times faster, because it drops some checks, but I didn't expect it to be approximately 20 times faster.
nikic
A: 

A probably inefficient but simple solution with backtracking

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;
}

example

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

result

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
)
stereofrog
+2  A: 

Hey at all,

i've just checked the funktion which was posted by oezi. I think, it's the best and fastest way to do it. I have to post this as Answer becuase i'm not allowed to write comments yet. ;)

ph3x_m0nk3y
thanks for testing ;)
oezi
+1  A: 

If you look at oezis algorithm one drawback is immediately clear: It spends very much time summing up numbers which are already known not to work. (For example if 1 + 2 is already to big, it doesn't make any sense to try 1 + 2 + 3, 1 + 2 + 3 + 4, 1 + 2 + 3 + 4 + 5, ..., too.)

Thus I have written an improved version. It does not use bit magic, it makes everything manual. A drawback is, that it requires the input values to be sorted (use rsort). But that shouldn't be a big problem ;)

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;
}

A modified version of oezis testing program (see end) outputs:

possibilities: 540
took: 3.0897309780121

So it took only 3.1 seconds to execute, whereas oezis code executed 65 seconds on my machine (yes, my machine is very slow). That's more than 20 times slower!

Furthermore you may notice, that my code found 540 instead of 338 possibilities. This is because I adjusted the testing program to use integers instead of floats. Floats comparison shall never be used, this is a great example why: You sometimes get 59.959999999999 instead of 59.96 and thus the match will not be counted. So, if I run oezis code with integers it finds 540 possibilities, too ;)

Testing program:

// 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);
nikic
+1 realy great! i also knew this problem in my code and wanted to twak it, but never found time and space to do that... and splash wasn't online since my post, so he won't even recognize it. one question more that will never be officially answered (and this one _is_ the answer)
oezi
@oezi: That's always the risk when answering old questions. But hey, writing algorithms is fun even if you can't expect to get reputation for them ;)
nikic
absolutely - and i wrote my function during a business economics lecture that bored to tears - questions like this are great for those situations
oezi