Generate random numbers with fix probability - php

I red a lot in the forum about this, but all answers were so specific to the the asked question. The nearest one I found to my need was:Probability Random Number Generator by Alon Gubkin.
The difference is that, Alon ask to give a one face (which is six) extra chance. In my case, I want to divide the chance for the six faces so that they add up to 100%. For example, face 1 has chance of 40%, face 2 has only 10%, face 3 has 25%, ... etc.
How can I do that?

The single probability check with linear probability can be easily done with:
function checkWithProbability($probability=0.1, $length=10000)
{
$test = mt_rand(1, $length);
return $test<=$probability*$length;
}
For example, this will produce:
for($i=0; $i<10; $i++)
{
var_dump(checkWithProbability(1/3));
}
Something like:
bool(false)
bool(true)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(true)
bool(false)
And you can use that principle to get your edges check with desired probability:
function checkWithSet(array $set, $length=10000)
{
$left = 0;
foreach($set as $num=>$right)
{
$set[$num] = $left + $right*$length;
$left = $set[$num];
}
$test = mt_rand(1, $length);
$left = 1;
foreach($set as $num=>$right)
{
if($test>=$left && $test<=$right)
{
return $num;
}
$left = $right;
}
return null;//debug, no event realized
}
The idea is to use geometry probability - i.e. split some line part into pieces with corresponding length and then check to which part our random number belongs.
0.75 0.9
| |
V V
*--------*--*-----*-*--*--* <-- (length)
^ ^ ^ ^ ^
| | | | |
0 0.4 0.5 0.8 1
Sample will be:
$set = [
1 => 0.4,
2 => 0.1,
3 => 0.25,
4 => 0.05,
5 => 0.1,
6 => 0.1
];
for($i=0; $i<10; $i++)
{
var_dump(checkWithSet($set));
}
With result like:
int(1)
int(2)
int(2)
int(6)
int(3)
int(1)
int(1)
int(6)
int(1)
int(1)
You can increase $length - in theory, this will increase "quality" of randomize check, but that's not too easy thing - because mt_rand() uses pseudo-random generator, Mersenne Twister (and in ideal case that's not true linear probability)

A quite simple approach would be to have an array with the length 100, writing your "faces" numbers in it, shuffle it and get the first element.
So for your example in that array are 40x 1, 10x 2, 25x 3.
Little code example (not tested):
$probabilities = array(
1 => 40,
2 => 10,
3 => 25,
4 => 5,
5 => 10,
6 => 10
);
$random = array();
foreach($probabilities as $key => $value) {
for($i = 0; $i < $value; $i++) {
$random[] = $key;
}
}
shuffle($random);
echo $random[0];

In your case you might generate random from 1 to 100 and then:
if random in 1:40 -> face 1
elseif random in 41:50 -> face 2
and so on.
Of course, real code would be a little more complex to get real ranges and not hardcoded ifs

I can think of a very simple solution. This one does not alter the random number generator's generation pattern but interprets the outcomes so as to suit your problem above. I'd ask the random number generator to generate numbers between 0 and 9. And then do the following mapping where I assign ranges of the generated number to values of my intrest based on the probability I am intered in assigning to that value:
If result <= 3, face=1
else if result <=5, face =2
else is result <=25 face =3
//and so on

I tried changing Alma's code a little.
The main goal was to make the code shorter and simple.
In this example, you will be inputting the probabilities as integers, and not decimals, therefore adding a probability of 7.5% will force you to multiply everything by 10.
// face 1 = 40%, face 2 = 10% etc...
$probabilities = [40, 10, 25, 25];
$results = ['face 1', 'face 2', 'face 3', 'face 4'];
echo checkWithSet($probabilities, $results);
function checkWithSet($probabilities, $results)
{
$total = array_sum($probabilities);
$random_num = mt_rand(1, $total);
$counter = 0;
foreach($probabilities as $index=>$value)
{
$counter += $value
if($counter > $random_num)
{
return $results[$index];
}
}
}

Related

Get the sum of all carried digits while performing addition on 2 or more integers

I've got this task, which I honestly don't understand what exactly to do.
It my be because of my English level, or mathmatics level, but this is really something I can not make sense of. Could you help be at least to understand the task ?
My php knowledge is very well, at least I thought so...
The task is this :
"Carry" is a term of an elementary arithmetic. It's a digit that you transfer to column with higher significant digits when adding numbers.
This task is about getting the sum of all carried digits.
You will receive an array of two numbers, like in the example. The function should return the sum of all carried digits.
function carry($arr) {
// ...
}
carry([123, 456]); // 0
carry([555, 555]); // 3 (carry 1 from ones column, carry 1 from tens column, carry 1 from hundreds column)
carry([123, 594]); // 1 (carry 1 from tens column)
Support of arbitrary number of operands will be a plus:
carry([123, 123, 804]); // 2 (carry 1 from ones column, carry 1, carry 1 from hundreds column)
Background information on "carry": https://en.m.wikipedia.org/wiki/Carry_(arithmetic)
For this task, we don't actually need the numbers written under the equals line, just the numbers which are carried. Importantly, the carried numbers need to be used when calculating subsequent columns.
Before looping each column of integers, reverse the order of the columns so that looping from left-to-right also iterates the lowest unit column and progresses to higher unit columns (ones, then tens, then hundreds, etc).
For flexibility, my snippet is designed to handle numbers of dynamic length. If processing potential float numbers, you could merely multiply all number by a power of 10 to convert all values to integers. My snippet is not designed to handled signed integers.
Code: (Demo)
function sumCarries(array $array) {
$columns = ['carries' => []];
// prepare matrix of 1-digit integers in columns -- ones, tens, hundreds, etc
foreach ($array as $integer) {
$columns[] = str_split(strrev($integer));
}
// sum column values in ascending order and populate carry values
// subsequent column sums need to include carried value
for ($i = 0, $len = strlen(max($array)); $i < $len; ++$i) {
$columns['carries'][$i + 1] = (int)(array_sum(array_column($columns, $i)) / 10);
}
// sum all populated carry values
return array_sum($columns['carries']);
}
$tests = [
[123, 456], // no carries in any column
[555, 555], // 1 ones, 1 tens, 1 hundreds
[123, 594], // 1 tens
[123, 123, 804], // 1 ones, 1 hundreds
[99, 9, 99, 99, 99], // 4 ones, 4 hundreds
[9,9,9,9,9,9,9,9,9,9,9,9], // 10 ones
];
var_export(array_map('sumCarries', $tests));
Output:
array (
0 => 0,
1 => 3,
2 => 1,
3 => 2,
4 => 8,
5 => 10,
)
Since it's homework, I'm not going to fully answer the question, but explain the pieces you seem confused about so that you can put them together.
1 11 111 111 <- these are the carry digits
555 555 555 555 555
+ 555 -> + 555 -> + 555 -> + 555 -> + 555
----- ----- ----- ----- -----
0 10 110 1110
For a better example of two digits, let's use 6+6. To get the carry digit you can use the modulus operator where 12 % 10 == 2. So, (12 - (12 % 10)) / 10 == 1.
Thank you again. #Sammitch
I got it to make it work. Actually the problem was my English Math Level. The term "Carry digits" had no meaning at all for me. I was completely focusing on something else.
Here is my code : It may be far from perfect, but it does the job :)
function carry($arr) {
$sum_ones = 0;
$sum_tens = 0;
$sum_hunds = 0;
$arrCount = count($arr);
foreach($arr as $key){
$stri = (string)$key;
$foo[] = array(
"hunds" => $stri[0],
"tens" => $stri[1],
"ones" => $stri[2]
);
}
$fooCount = count($foo);
for($i=0; $i<$fooCount; $i++) {
$sum_ones+= $foo[$i]["ones"];
$sum_tens+= $foo[$i]["tens"];
$sum_hunds+= $foo[$i]["hunds"];
}
$sum1 = ($sum_ones - ($sum_ones % 10)) / 10;
$sum10 = ($sum_tens - ($sum_tens % 10)) / 10;
$sum100 = ($sum_hunds - ($sum_hunds % 10)) / 10;
return ($sum1 + $sum10 + $sum100);
}
$arr = array(555, 515, 111);
echo carry($arr);

Generate a random number from a given set of numbers and chances

I have a list of numbers like
$list = array(1,5,19,23,59,51,24)
in actual code this is generated from database, so this array will hold up to 500 numbers that are different from each other.
each of these numbers in the database has a probability of occurring recorded. So i have a data from previous executions to generate random numbers from 1 to 500 and recorded the probabilities of each number generated for like 1000 times.
Now having list of numbers and probabilities for each number i want to write a function that will generate a random number from these 500 numbers based on their probabilities.
For example:
number 1 has a chance of: 0.00123 //0.123%
number 6 has a chance of: 0.0421 //4.21%
number 11 has a chance of: 0.0133 //1.33%
so variable $finallist will look something like this:
$finallist[1] = 0.00123;
$finallist[6] = 0.0421;
$finallist[11] = 0.0133;
Now if i run my function and pass in $finallist as a parameter i want to retrieve a random number between 1 and 6 but number 6 will have higher possibility of coming out than 1 and 11 will have higher possibility to come out than 1.
I have some functions written that deal with returning the random number based on its chance but it only takes 1 value as a parameter.
private function randomWithProbability($chance, $num, $range = false)
{
/* first generate a number 0 and 1 and see if that number is in the range of chance */
$rand = $this->getRandomFloatValue(0, 1);
if ($rand <= $chance)
{
/* the number should be returned */
return $num;
}
else
{
/* otherwise return a random number */
if ($range !== false)
{
/* make sure that this number is not same as the number for which we specified the chance */
$rand = mt_rand(1, $range);
while ($rand == $num)
{
$rand = mt_rand(1, $range);
}
return $rand;
}
}
}
if anyone knows a solution/algorithm to do this or if there is anything built in to PHP would be a big help. Thank you so much.
The basic algorithm you're looking for:
add all the probabilities together and determine the maximum
pick a random number between 0 and 1 and multiply it by the max
find the entry that corresponds with that value
Example code:
<?php
// create some weighted sample data (id => weight)
$samples = array(
'a' => 0.001,
'b' => 0.004,
'c' => 0.006,
'd' => 0.05,
'e' => 0.01,
'f' => 0.015,
'g' => 0.1
);
class Accumulator {
function __construct($samples) {
// accumulate all samples into a cumulative amount (a running total)
$this->acc = array();
$this->ids = array();
$this->max = 0;
foreach($samples as $k=>$v) {
$this->max += $v;
array_push($this->acc, $this->max);
array_push($this->ids, $k);
}
}
function pick() {
// selects a random number between 0 and 1, increasing the multiple here increases the granularity
// and randomness; it should probably at least match the precision of the sample data (in this case 3 decimal digits)
$random = mt_rand(0,1000)/1000 * $this->max;
for($i=0; $i < count($this->acc); $i++) {
// looks through the values until we find our random number, this is our seletion
if( $this->acc[$i] >= $random ) {
return $this->ids[$i];
}
}
throw new Exception('this is mathematically impossible?');
}
private $max; // the highest accumulated number
private $acc; // the accumulated totals for random selection
private $ids; // a list of the associated ids
}
$acc = new Accumulator($samples);
// create a results object to test our random generator
$results = array_fill_keys(array_keys($samples), 0);
// now select some data and test the results
print "picking 10000 random numbers...\n";
for($i=0; $i < 10000; $i++) {
$results[ $acc->pick() ]++;
}
// now show what we found out
foreach($results as $k=>$v) {
print "$k picked $v times\n";
}
The results:
> php.exe rand.php
picking 10000 random numbers...
a picked 52 times
b picked 198 times
c picked 378 times
d picked 2655 times
e picked 543 times
f picked 761 times
g picked 5413 times
Running the same code with this sample:
// samples with even weight
$samples = array(
'a' => 0.1,
'b' => 0.1,
'c' => 0.1,
'd' => 0.1
);
Produces these results:
> php.exe rand.php
picking 10000 random numbers...
a picked 2520 times
b picked 2585 times
c picked 2511 times
d picked 2384 times

php abbreviating numbers with K/M

The function below finds the number, rounds it off and puts a K or M on the end of it
IE: 25000 becomes 25K
function abbr_no($number) {
$number = preg_replace('/[^0-9]/', '', $number);
$numbers = array('', 'K', 'M');
if ($number == 0) {
return('n/a');
} else {
return (round($number/pow(10000, ($i = floor(log($number, 10000)))), 0) . $numbers[$i]);
}
}
and so it basically does what I want, but as is for a number like 389345 it rounds it off and spits out 39K (as it should) however I would like the result to be 398K (3 digit number)
Now in the last return line:
0) . $numbers[$i]);
if I change the 0 to a 1 the result becomes 39.8K but again I don't want the decimal point so I am currently stuck and have hit a brickwall
As always all help is appreciated and thank you in advance.
This allows you to convert on all sorts of scales,
function humanize($val, $postfix)
{
foreach ($postfix as $p=>$div) {
$t=round($val/$div) . $p;
if (strlen($t)<(3+strlen($p))) {
break;
}
}
return trim($t);
}
e.g.
$postfix=array(''=>1, 'K'=>1000,'M'=>1000000,'B'=>1000000000);
To answer the original question,
$postfix=array('walnut' => 0.16, 'chicken egg'=>0.35,
'grapefruit' => 1, 'bulgarian airbag' => 1.1,
'bulgarian funbag'=>3.27, 'football' => 11.07,
'olympic swim pool' => 4780114, 'known universe'=>1.17456E4933);
for the Vulture Central Weights and Measures Soviet volume standard.
without editing much of your code you can add additional if statement
test if the modular division by 1
yournumber%1
($i = floor(log($number, 10000)))), 0) % 1
gives you 0
if it doesnt then
multiply your number by 10 (you can make it a while statement so it multiplies it by 10 until there are no spaces after decimal point)
then return that number
so if you use your_number like 39.8 as an example it would look like this
while(your_number%1 != 0){
your_number*=10;
}
return your_number;
This a quick fix but its definitely not the best way to code this

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.

A Project Euler Puzzler (specifically in 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.

Categories