A Project Euler Puzzler (specifically in PHP) - php

There is another recent Project Euler question but I think this is a bit more specific (I'm only really interested in PHP based solutions) so I'm asking anyway.
Question #5 tasks you with: "What is the smallest number that is evenly divisible by all of the numbers from 1 to 20?"
Now, I have solved it twice. Once very inefficiently and once much more efficiently but I am still far away from an especially sophisticated answer (and I am not especially solid in math hence my brute force solution). I can see a couple of areas where I could improve this but I am wondering if any of you could demonstrate a more efficient solution to this problem.
*spoiler: Here is my less than optimal (7 seconds to run) but still tolerable solution (not sure what to do about the double $... just pretend you only see 1...
function euler5(){
$x = 20;
for ($y = 1; $y < 20; $y++) {
if (!($x%$y)) {
} else {
$x+=20;
$y = 1;
}
}echo $x;
};

Collect prime factors for all numbers between 1 and 20. Counting the maximal exponents of each prime factor, we have 16 = 2**4, 9 = 3**2, as well as 5, 7, 11, 13, 17, 19 (each appearing only once). Multiply the lot, and you have your answer.

in php it will look like this:
<?php
function gcd($a,$b) {
while($a>0 && $b>0) {
if($a>$b) $a=$a-$b; else $b=$b-$a;
}
if($a==0) return $b;
return $a;
}
function euler5($i=20) {
$euler=$x=1;
while($x++<$i) {
$euler*=$x/gcd($euler,$x);
}
return $euler;
}
?>
Its at least twice as fast than what you posted.

Chris Jester-Young is right.
In general if you wanted the smallest number that is evenly divisible by all of the numbers from 1 to N, you would want to find all the prime numbers from 2 to N, and for each one, find the greatest number of times it divides any number in the range. This can be calculated by finding the greatest power of the prime that's not greater than N.
In the case of 20, as Chris pointed out, 2^4 is the greatest power of 2 not greater than 20, and 3^2 is the greatest power of 3 not greater than 20, and for all other primes, only the first power is not greater than 20.

You can remove some numbers that are divided with, for example 1 is unnecessary, all natural numbers are divisible by 1.you don’t need 2 either, and therefore, all numbers are divisible by multiples of 2 (4, 8, 16, etc) are divisible by 2, also. So the relevant numbers will be 11, 12, 13, 14, 15, 16, 17, 18, and 19.
So:
<?
function eulerPuzzle()
{
$integers = array( 11,12,13,14,15,16,17,18,19 );
for ($n = 20; 1; $n += 20 ) {
foreach ($integers as $int) {
if ( $n % $int ) {
break;
}
if ( $int == 19 ) {
die ("Result:" . $n);
}
}
}
}
eulerPuzzle();
?>

<?php
$i=20;
while ($i+=20) {
for ($j=19;$j!==10;--$j){
if ($i%$j) continue 2;
}
die ("result: $i\n");
}
Is the fastest and shortest php solution so far. About 1.4x faster than Czimi's on my comp. But check out the python solution, thats a nice algo.

Some people really over-think this...
In Ruby:
puts 5*7*9*11*13*16*17*19

#People doing simple math; I'm not sure if that is the goal of the exercise. You are to learn new languages and new ways to perform stuff. Just doing it by a calculator isn't the right way going about things.
And I know this is a post in an old old thread but it still comes up in google results :)
Doing it in code (PHP that is) I found this to be the fastest solution:
function eulerPuzzle() {
$integers = array (11, 12, 13, 14, 15, 16, 17, 18, 19 );
for($n = 2520; 1; $n += 2520) {
foreach ( $integers as $int ) {
if ($n % $int) {
break;
}
if ($int == 19) {
die ( "Result:" . $n );
}
}
}
}
eulerPuzzle ();
Yes, it's a modified piece from CMS. The main reason it is faster is because when you read the question, they already state that the lowest possible number for the first 10 integers is 2520. therefor, you can just increment by 2520 instead of 20. resulting in 126 times less loops

I know you said PHP, but here's my rough draft in Python.
#!/usr/bin/env python
from operator import mul
def factor(n):
factors = {}
i = 2
while i < n and n != 1:
while n % i == 0:
try:
factors[i] += 1
except KeyError:
factors[i] = 1
n = n / i
i += 1
if n != 1:
factors[n] = 1
return factors
base = {}
for i in range(2, 2000):
for f, n in factor(i).items():
try:
base[f] = max(base[f], n)
except KeyError:
base[f] = n
print reduce(mul, [f**n for f, n in base.items()], 1)
It's not as elegant as I could have made it, but it calculates the least common multiple of the numbers from 2 to 2000 in .15s. If your iterative solution could process a billion candidates per second, it would take 10^849 years to finish.
In other words, don't bother optimizing the wrong algorithm.

Related

Highest multiplication with multiple and max number set

Not quite sure what to set this title as, or what to even search for. So I'll just ask the question and hope I don't get too many downvotes.
I'm trying to find the easiest way to find the highest possible number based on two fixed numbers.
For example:
The most I can multiply by is, say, 18 (first number). But not going over the resulted number, say 100 (second number).
2 x 18 = 36
5 x 18 = 90
But if the first number is a higher number, the second number would need to be less than 18, like so:
11 x 9 = 99
16 x 6 = 96
Here I would go with 11, because even though the second number is only 9, the outcome is the highest. The second number could be anything as long as it's 18 or lower. The first number can be anything, as long as the answer remains below 100. Get what I mean?
So my question is, how would write this in php without having to use switches, if/then statements, or a bunch of loops? Is there some math operator I don't know about that handles this sort of thing?
Thanks.
Edit:
The code that I use now is:
function doMath($cost, $max, $multiplier) {
do {
$temp = $cost * $multiplier;
if ($temp > $max) { --$multiplier; }
} while ($temp > $max);
return array($cost, $temp, $multiplier);
}
If we look at the 11 * 9 = 99 example,
$result = doMath(11, 100, 18);
Would return,
$cost = 11, $temp = 99, $multiplier = 9
Was hoping there was an easier way so that I wouldn't need to use a loop, being as how there are a lot of numbers I need to check.
If I understood you right, you are looking for the floor function, combining it with the min function.
Both a bigger number c and a smaller number a are part of the problem, and you want to find a number b in the range [0, m] such that a * b is maximal while staying smaller (strictly) than c.
In your example, 100/18 = 5.55555, so that means that 18*5 is smaller than 100, and 18*6 is bigger than 100.
Since floor gets you the integral part of a floating point number, $b = floor($c/$a) does what you want. When a divides c (that is, c/a is an integer already), you get a * b == c.
Now b may be outside of [0,m] so we want to take the smallest of b and m :
if b is bigger than m, we are limited by m,
and if m is bigger than b, we are limited by a * b <= c.
So in the end, your function should be :
function doMath($cost, $max, $multiplier)
{
$div = min($multiplier, floor($max/$cost));
return array($cost, $div * $cost, $div);
}

optimal algorithm for particular divisors

Not a duplicate of-
optimal algorithm for finding unique divisors
I came across this problem. I am not able to find an optimal algorithm.
The problem is :
Given a list L of natural numbers(number can be really large) and a number N, what's the optimal algorithm to determine the number of divisors of N which doesn't not divide any of the numbers present in the list L. Numbers in the list can be repetitive ie, one number can occur more than once.
Observation:
Divisors of some divisor d of N are also divisors of N.
MY approach was :
Find the divisors of N.
Sort L in reverse order(largest element being 1st element).
foreach divisor d of N, I check whether it divides any element in the list or not.(stop when you come to check for an element less than d in the list, as the list is sorted)
If d divides some number in the list L, then I don't check for any divisor of d, that is, I skip this checking.
Ultimately, left divisors which were neither divided any number in the list nor skipped are counted. This count is the final answer.
But this algorithm is not optimal for this problem.
Any ideas for a better algorithm?
What you need to look into is : co-primes (or relatively primes)
In number theory, a branch of mathematics, two integers a and b are
said to be coprime (also spelled co-prime) or relatively prime if the
only positive integer that evenly divides both of them is 1.
So to "transcode" your problem :
You basically want to find the Number of coprimes of N from the L list.
When a and b are co-primes?
If two numbers are relatively prime then their greatest common divisor (GCD)
is 1
Example code (for GCD) in PHP :
<?php
$gcd = gmp_gcd("12", "21");
echo gmp_strval($gcd) . "\n";
?>
Simply put :
$count = 0
Foreach element e in list L : calculate the GCD(e,N)
Is their GCD=1? If yes, they are coprime (so N and e have no common divisors). Count it up. $count++
And that's all there is to it.
First, factorize n and represent it in the following way: p1:k1, p2:k2,..., pm:km such that p1,p2,... are all primes and n=p1^k1 * p2^k2 ....
Now, iterate over r1, r2, r3,..., rm such that r1<=k1, r2<=k2, ..., rm<=km and check if p1^r1*p2^r2...*pm^rm divides any number in L. If not increment count by 1.
Optimization: Pick a value for r1. See if p1^r1 divides any number in L. If yes, then pick a number for r2 and so on. If p1^r1 does not divide any number in L, then increment count by (k2+1)(k3+1)..*(km+1).
Example N=72, L=[4, 5, 9, 12, 15, 20]:
Writing N as a primal product: 2:3, 3:2 (2^3*3*2 = 72).
p1=2, p2=3, k1=3, k2=2
count=0
r1=0:
r2=0:
Divides 4
r1=0:
r2=1:
Divides 9
r1=0:
r2=2:
Divides 9
r1=1:
r2=0:
Divides 4
r1=1:
r2=1:
Divides 12
r1=1:
r2=2:
L not divisible by 18. Count+=1 = 1
r1=2:
r2=0:
Divides 4
r1=2:
r2=1:
Divides 12
r1=2:
r2=2:
L not divisible by 36. Count+=1 = 2
r1=3:
r2=0:
L not divisible by 8. Count+=(k2+1) +=(2+1) = 5
<?php
class Divisors {
public $factor = array();
public function __construct($num) {
$this->num = $num;
}
// count number of divisors of a number
public function countDivisors() {
if ($this->num == 1) return 1;
$this->_primefactors();
$array_primes = array_count_values($this->factor);
$divisors = 1;
foreach($array_primes as $power) {
$divisors *= ++$power;
}
return $divisors;
}
// prime factors decomposer
private function _primefactors() {
$this->factor = array();
$run = true;
while($run && #$this->factor[0] != $this->num) {
$run = $this->_getFactors();
}
}
// get all factors of the number
private function _getFactors() {
if($this->num == 1) {
return ;
}
$root = ceil(sqrt($this->num)) + 1;
$i = 2;
while($i <= $root) {
if($this->num % $i == 0) {
$this->factor[] = $i;
$this->num = $this->num / $i;
return true;
}
$i++;
}
$this->factor[] = $this->num;
return false;
}
} // our class ends here
$example = new Divisors(4567893421);
print $example->countDivisors();
?>

Generating a random number but with a twist

I need to generate a random number.
But the twist is that the % of lower numbers should be greater than the higher.
For example.
rand > 1 to 100
13,
15,
12,
16,
87,
15,
27,
12,
1,
12,
98,
12,
53,
12,
14....
The bold integers will be the ones return from the 1-100 range.
The math should be like so rand = a number lower than max/2
Hope you guys can help.
Ps, How would a matrix come into this ? im not superior at maths :(
The abs answer seems to be the one.
$up = $down = 0;
while(true)
{
if(abs((rand()%150)-50) < 50)
{
$up++;
}else
{
$down++;
}
if( ($up + $down) == 500){ break;}
}
echo $up . '/' . $down;
how about
n = abs((rand()%150)-50)
$x = rand(0,1) ? rand(1,100) : rand(1,50);
Simple method: the first rand(0,1) selects between the two cases. Either 1..50 or 1..100 as random range. Since 1,100 already encompases 1,50, the latter range is selected 100% of the time, the former case only in 1 of 2 runs.
If you want a distribution where the highest numer 99 gets selected almost never, but the lower numbers 1..20 pretty frequent, then a simple rand(1,rand(1,100)) would do.
$rand = (rand() * rand()) / (getrandmax() * getrandmax());
This will give you a random number between 0 and 1 with a high probability of falling into the lower end of the range and the probability of a larger number decreasing exponentially. You can then scale this result to any range that you want.

PHP: find two or more numbers from a list of numbers that add up towards a given amount

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.
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 too 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 faster!
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. Direct floating point comparison is rarely the right thing to do, 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);
sorry for adding a new answer, but this is a complete new solution to solve all problems of life, universe and everything...:
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
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.
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
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
)
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)
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:
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.

Detecting if integer can be written as sum of given integers

Supposing I'm having the constants 3,5,6,9,10. How can I detect how to write $n, which is the input, as a sum of these constants with the least number of terms?
Examples
$n=10, S=10
$n=18, S=9+9
$n=24, S=9+9+6
$n=27, S=9+9+9
$n=28, S=10+9+9
Thanks
This is another Python solution, but hopefully it's easy for you to convert to PHP (I would do it myself, but I'm no PHP expert - I'm sure you could do a better job of it). I've tried not to use any advanced Python funcitons, so that it is easier for non-Python readers to understand, but if some Python syntax is not clear, please just ask.
allowed = [3, 5, 6, 9, 10]
n = 28
solutions = [ None ] * (n + 1)
solutions[0] = []
for i in range(n + 1):
if solutions[i] is None: continue
for a in allowed:
if i + a > n: continue
if solutions[i + a] is None or len(solutions[i]) + 1 < len(solutions[i + a]):
solutions[i + a] = solutions[i] + [a]
print solutions[28]
It works by starting from 0 and building up to the desired number, keeping a cache of the shortest solution seen so far for each possible total. It has a running time of O(n * a), where a is the number of different allowed values.
By the way, your answer to n=28 is wrong. It should be [9, 9, 10].
Update: here's my attempt at a PHP solution:
<?php
$allowed = array(3, 5, 6, 9, 10);
$n = 28;
$solutions = array();
$solutions[0] = array();
foreach (range(0, $n) as $i) {
if (is_null($solutions[$i])) continue;
foreach ($allowed as $a) {
if ($i + $a > $n) continue;
if (is_null($solutions[$i + $a]) ||
sizeof($solutions[$i]) + 1 < sizeof($solutions[$i + $a])) {
$solutions[$i + $a] = array_merge($solutions[$i], array($a));
}
}
}
var_dump($solutions[$n]);
?>
It gives the right answer, but please be aware that I'm not a professional PHP coder - I just looked up the equivalent functions in the PHP documentation.
This is Mark Byers' algorithm, rewritten using loop structures that are more familiar to PHP developers, and constructs that won't generate PHP notices. $C is your set of integers, $S the solutions.
$n = 28;
$C = array(3, 5, 6, 9, 10);
$S = array(array());
// if your set isn't sorted already, you have to call sort()
//sort($C);
for ($i = 0; $i <= $n; ++$i)
{
if (!isset($S[$i]))
{
continue;
}
foreach ($C as $v)
{
if ($i + $v > $n)
{
break;
}
if (!isset($S[$i + $v])
|| count($S[$i + $v]) > 1 + count($S[$i]))
{
$S[$i + $v] = $S[$i];
$S[$i + $v][] = $v;
}
}
}
print_r($S[$n]);
Two obvious approaches suggest themselves:
Write a series of linear equations,
and solve to find various solutions.
Choose one with the least number of
terms.
Trial and error, starting
with the largest terms first.
Find all possible solutions for "S=3A+5B+6C+9D+10E" then choose the one with the most 0 values for A,B,C,D,E
a rough sketch of an unscalable but correct solution (sorry, so far its only python ..):
#!/usr/bin/env python
import itertools, sys
pool = [3, 5, 6, 9, 10]
repeat, found, solutions = 1, False, set()
try: x = int(sys.argv[1])
except: x = 42
while not found:
for n in itertools.product(pool, repeat=repeat):
s = sum(n)
if s == x:
solutions.add(n)
found = True
break
repeat = repeat + 1
print solutions
would yield:
$ python 1850629.py 11
set([(5, 6)])
$ python 1850629.py 19
set([(9, 10)])
$ python 1850629.py 21
set([(3, 9, 9)])
$ python 1850629.py 42
set([(3, 9, 10, 10, 10)])
In addition to the excellent general answers already provided, bear in mind that if your set of values has certain properties, much more optimal solutions exist.
Specifically, if your solution is 'minimal' - that is, a single best solution exists for any value - then you can find the smallest number of elements using a 'greedy' algorithm: Simply add the largest value until the remainder is smaller than it, repeat with the next largest value, and so forth.
As an example, the denominations used for money in many countries are .01, .02, .05, .10, .20, .50, 1, 2, 5, .... This set is minimal, so you can just repeatedly add the largest valid denomination.
NP-complete problem
Subset sum problem

Categories