I am trying to find the smallest positive number that is evenly divisible by all of the numbers from 1 to 20 and here is the code:
$num = 2520;
$x = 1;
while($x < 21){
if($num % $x == 0){
$x++;
}else{
$num += 20;
$x = 1;
}
}
echo $num;
it gives a proper output in less than 1 minute. Is this execution time bad in the professional world? any way to optimize this?
P.S. I started from 2520 because it is the smallest number that can be divided by each of the numbers from 1 to 10 without any remainder.
Suggestion: find the primes of all integers in [1,20].
Eg, we have the primes {2,3,5,7,11,13,17,19}. So, if the solution is divisible
by all integers in [1,20], then surely it is divisible by each element in this
list of primes. So, at a minimum, our solution is >= 2*3*5*7*11*13*17*19, right?
Now the problem is how clever we can be about constructing candidate solutions
larger than that number. Well, let's first see how much of the solution is done...
Is 2*3*5*7*11*13*17*19 divisible by 4? No. So, let's multiple by 2 to get
2*2*3*5*7*11*13*17*19, which surely is divisible by 2*2...
Is 2*2*3*5*7*11*13*17*19 divisible by 6? Yes.
Is 2*2*3*5*7*11*13*17*19 divisible by 8? ....
You get the picture. While I am not sure, I believe this approach will
result in the right answer -- ie, the smallest integer divisible by each
integer in [1,20].
use modified Sieve of Eratosthenes for massive speed up
http://en.wikipedia.org/wiki/Sieve_of_Eratosthenes
but do not use array instead iterate just indexes
something like
1.int ix[20]={1,2,3,4,...20};
2.now write a loop
where you will increment all ix[i]+=i+1; where i=<0;18>
while ix[i]>=ix[19]
if at the end all ix[i] are the same then its contens is the result so stop/return whatever
3.if not then increment also ix[19]+=20;
and continue with the loop on bullet 2
[notes]
can be easily changed for searing for divisible by any numbers not just divisible by 1..20
This sounds like Project Euler's Problem 5.
What we are finding here is the least common multiple of numbers 1 to 20. You can find this with help form the greatest common divisor:
function gcd($a, $b) {
if ($a == 0) return $b;
return gcd($b % $a, $a);
}
function lcm($a, $b) {
return $a * $b / gcd($a, $b);
}
function smallestDiv($n) {
$div = 1;
for ($i = 1; $i <= $n; $i++) {
$div = lcm($div, $i);
}
return $div;
}
echo smallestDiv(20);
Pardon my PHP. It's been a while since I have written any.
Note that we can also find the answer via prime factorization of each of the numbers and looking for the largest exponents as described here and in Kode Charlie's answer:
2 = 2
3 = 3
4 = 2²
5 = 5
6 = 2 × 3
7 = 7
8 = 2³
9 = 3²
10 = 2 × 5
11 = 11
12 = 2² × 3
13 = 13
14 = 2 × 7
15 = 3 × 5
16 = 2⁴
17 = 17
18 = 2 × 3²
19 = 19
20 = 2² × 5
lcm(1,2,…20) = 2⁴ × 3² × 5 × 7 × 11 × 13 × 17 × 19 = 232,792,560
Related
How can I generate a sequence of numbers like
1 2 4 8 11 12 14 18 ...
(plus 10 every 4 numbers) with the following additional requirements:
using only one loop
output should stop when a value in the sequence is greater than a specified input
Examples
$input = 24;
1 2 4 8 11 12 14 18 21 22 24
$input = 20;
1 2 4 8 11 12 14 18
Here's what I tried so far:
<?php
// sample user input
$input = 20;
$number = 1;
$counter = 0;
$array = array();
//conditions
while ($counter < 4) {
$counter++;
array_push($array, $number);
$number += $number;
}
//outputs
for ($x = 0; $x < count($array); $x++) {
echo $array[$x];
echo " ";
}
Code: (Demo)
function arrayBuilder($max,$num=1){
$array=[];
for($i=0; ($val=($num<<$i%4)+10*floor($i/4))<=$max; ++$i){
$array[]=$val;
}
return $array;
}
echo implode(',',arrayBuilder(28)),"\n"; // 1,2,4,8,11,12,14,18,21,22,24,28
echo implode(',',arrayBuilder(28,2)),"\n"; // 2,4,8,16,12,14,18,26,22,24,28
echo implode(',',arrayBuilder(20)),"\n"; // 1,2,4,8,11,12,14,18
echo implode(',',arrayBuilder(24)),"\n"; // 1,2,4,8,11,12,14,18,21,22,24
This method is very similar to localheinz's answer, but uses a technique introduced to me by beetlejuice which is faster and php version safe. I only read localheinz's answer just before posting; this is a matter of nearly identical intellectual convergence. I am merely satisfying the brief with the best methods that I can think of.
How/Why does this work without a lookup array or if statements?
When you call arrayBuilder(), you must send a $max value (representing the highest possible value in the returned array) and optionally, you can nominate $num (the first number in the returned array) otherwise the default value is 1.
Inside arrayBuilder(), $array is declared as an empty array. This is important if the user's input value(s) do not permit a single iteration in the for loop. This line of code is essential for good coding practices to ensure that under no circumstances should a Notice/Warning/Error occur.
A for loop is the most complex loop in php (so says the manual), and its three expressions are the perfect way to package the techniques that I use.
The first expression $i=0; is something that php developers see all of the time. It is a one-time declaration of $i equalling 0 which only occurs before the first iteration.
The second expression is the only tricky/magical aspect of my entire code block. This expression is called before every iteration. I'll try to break it down: (parentheses are vital to this expression to avoid unintended results due to operator precedence
( open parenthesis to contain leftside of comparison operator
$val= declare $val for use inside loop on each iteration
($num<<$i%4) because of precedence this is the same as $num<<($i%4) meaning: "find the remainder of $i divided by 4 then use the bitwise "shift left" operator to "multiply $num by 2 for every "remainder". This is a very fast way of achieving the 4-number pattern of [don't double],[double once],[double twice],[double three times] to create: 1,2,4,8, 2,4,8,16, and so on. bitwise operators are always more efficient than arithmetic operators.The use of the arithmetic operator modulo ensure that the intended core number pattern repeats every four iterations.
+ add (not concatenation in case there is any confusion)
10*floor($i/4) round down $i divided by 4 then multiply by 10 so that the first four iterations get a bonus of 0, the next four get 10, the next four get 20, and so on.
) closing parenthesis to contain leftside of comparison operator
<=$max allow iteration until the $max value is exceeded.
++$i is pre-incrementing $i at the end of every iteration.
Complex solution using while loop:
$input = 33;
$result = [1]; // result array
$k = 0; // coeficient
$n = 1;
while ($n < $input) {
$size = count($result); // current array size
if ($size < 4) { // filling 1st 4 values (i.e. 1, 2, 4, 8)
$n += $n;
$result[] = $n;
}
if ($size % 4 == 0) { // determining each 4-values sequence
$multiplier = 10 * ++$k;
}
if ($size >= 4) {
$n = $multiplier + $result[$size - (4 * $k)];
if ($n >= $input) {
break;
}
$result[] = $n;
}
}
print_r($result);
The output:
Array
(
[0] => 1
[1] => 2
[2] => 4
[3] => 8
[4] => 11
[5] => 12
[6] => 14
[7] => 18
[8] => 21
[9] => 22
[10] => 24
[11] => 28
[12] => 31
[13] => 32
)
On closer inspection, each value in the sequence of values you desire can be calculated by adding the corresponding values of two sequences.
Sequence A
0 0 0 0 10 10 10 10 20 20 20 20
Sequence B
1 2 4 8 1 2 4 8 1 2 4 8
Total
1 2 4 8 11 12 14 18 21 22 24 28
Solution
Prerequisite
The index of the sequences start with 0. Alternatively, they could start with 1, but then we would have to deduct 1, so to keep things simple, we start with 0.
Sequence A
$a = 10 * floor($n / 4);
The function floor() accepts a numeric value, and will cut off the fraction.
Also see https://en.wikipedia.org/wiki/Floor_and_ceiling_functions.
Sequence B
$b = 2 ** ($n % 4);
The operator ** combines a base with the exponent and calculates the result of raising base to the power of exponent.
In PHP versions prior to PHP 5.6 you will have to resort to using pow(), see http://php.net/manual/en/function.pow.php.
The operator % combines two values and calculates the remainder of dividing the former by the latter.
Total
$value = $a + $b;
Putting it together
$input = 20;
// sequence a
$a = function ($n) {
return 10 * floor($n / 4);
};
// sequence b
$b = function ($n) {
return 2 ** ($n % 4);
};
// collect values in an array
$values = [];
// use a for loop, stop looping when value is greater than input
for ($n = 0; $input >= $value = $a($n) + $b($n) ; ++$n) {
$values[] = $value;
}
echo implode(' ', $values);
For reference, see:
http://php.net/manual/en/control-structures.for.php
http://php.net/manual/en/function.floor.php
http://php.net/manual/en/language.operators.arithmetic.php
http://php.net/manual/en/function.implode.php
For an example, see:
https://3v4l.org/pp9Ci
How to reduce a number to single digit by adding its individual digits recursively :
Example 1 : $n = 99999 >> 45 >> 9
Example 2 : $n = 444444 >> 24 >> 6
Example 3 : $n = 8888888888888885 >> 125 >> 8;
then get equal to at last we want to get single digit.
You can use array_sum and str_split into a while loop until the final value of $n has the length equal to 1.
$n = 4444;
while (strlen($n) > 1) {
$n = array_sum(str_split($n));
}
var_dump($n);
Without array_sum and str_split you can use something like:
$n = '4444';
while (strlen($n) > 1) {
$s = 0;
for ($i = 0; $i < strlen($n); $i++) {
$s += $n[$i];
}
$n = (string) $s;
}
var_dump($n);
You can calculate this in a much more simple and elegant way, let me try to explain. For example if you have the number 53, you can divide it by 9 and it’s remainder will be it’s reduced number. Don’t ask me how I figured this out, I was just tinkering with numbers. So what you can do is use modulus, (53 % 9 = 8!) (517 % 9 = 4!). Perfect right? Almost, if the number is a multiple of 9 like 45 for example if you “modulus” it by 9 you will receive it’s remaineder which is 0 and we would expect 9 because 45 reduced to a single digit is 9. So you can just make a quick and easy else if statement checking for an output of 0, and if it’s 0 just return 9. Done! Whatever number you out in from 1 to infinty it will reduce it perfectly. Hope this helps :)
Writing some code connected with factorials (counting sum of numbers of factorial) I noticed that 13! modulus 10 equals 4.
function fact($n)
{
if ($n == 0) return 1;
return $n * fact($n-1);
}
function sum_num($n)
{
$sum = 0;
while ($n > 0)
{
$sum = $sum + ($n % 10);
$n = floor($n/10);
}
return $sum;
}
$n = 13;
$buff = fact($n);
echo $n."! = ".$buff;
echo "<br>";
echo "Summ ".$n."! = ".sum_num($buff);
Output is:
13! = 6227020800
Summ 13! = 31
But Summ should be 27. I begun search and found that on the first step I am getting 4 instead 0.
6227020800 % 10 = 4
And I don't understand why?
6227020800 requires 33 bits. The most siginficant bit gets truncated and you get you the number 1932053504 (modulo 10 of which is 4).
For arbitrary precision math use bc* functions, e.g. bcmod(6227020800, 10).
May be you should use bcmath php module instead for this operations.
http://php.net/manual/en/book.bc.php
Just take care that this module uses strings as inputs.
I'm pretty sure, you use a 32-bit version. Here the max value is 4.294.967.296.
I cannot ask for help on their forums, but i've been at this for 3 hours now. Spoilers Below I don't understand what i'm doing wrong. The question is:
If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.
Find the sum of all the multiples of 3 or 5 below 1000.
Here's my equation I made.
for($total = 0, $f = 5, $t = 3; $t < 1000; $t+=3){
if($f < 1000)
{
$total += $f + $t;
echo "Five: $f, Three: $t = $total<br />";
$f += 5;
}
else
{
$total += $t;
echo "Five: $f, Three: $t = $total<br />";
}
}
The answer is:233168. Where's my error?
You are counting numbers that are divisible both by 3 and 5 twice.
Suppose S(3) denotes sum of numbers divisible by 3 and S(5) denotes sum of numbers divisible by 5 till a given number n, then sum of numbers divisible by 3 or 5 is given by
S(3 U 5) = S(3) + S(5) - S(3 ∩ 5)
where S(3 ∩ 5) denotes sum of those numbers divisible by both 3 and 5.
In your case, you are calculating S(3 U 5) = S(3) + S(5) and hence getting wrong answer.
I have below a function (from a previous question that went unanswered) that creates an array with n amount of values. The sum of the array is equal to $max.
function randomDistinctPartition($n, $max) {
$partition= array();
for ($i = 1; $i < $n; $i++) {
$maxSingleNumber = $max - $n;
$partition[] = $number = rand(1, $maxSingleNumber);
$max -= $number;
}
$partition[] = $max;
return $partition;
}
For example: If I set $n = 4 and $max = 30. Then I should get the following.
array(5, 7, 10, 8);
However, this function does not take into account duplicates and 0s. What I would like - and have been trying to accomplish - is to generate an array with unique numbers that add up to my predetermined variable $max. No Duplicate numbers and No 0 and/or negative integers.
Ok, this problem actually revolves around linear sequences. With a minimum value of 1 consider the sequence:
f(n) = 1 + 2 + ... + n - 1 + n
The sum of such a sequence is equal to:
f(n) = n * (n + 1) / 2
so for n = 4, as an example, the sum is 10. That means if you're selecting 4 different numbers the minimum total with no zeroes and no negatives is 10. Now go in reverse: if you have a total of 10 and 4 numbers then there is only one combination of (1,2,3,4).
So first you need to check if your total is at least as high as this lower bound. If it is less there is no combination. If it is equal, there is precisely one combination. If it is higher it gets more complicated.
Now imagine your constraints are a total of 12 with 4 numbers. We've established that f(4) = 10. But what if the first (lowest) number is 2?
2 + 3 + 4 + 5 = 14
So the first number can't be higher than 1. You know your first number. Now you generate a sequence of 3 numbers with a total of 11 (being 12 - 1).
1 + 2 + 3 = 6
2 + 3 + 4 = 9
3 + 4 + 5 = 12
The second number has to be 2 because it can't be one. It can't be 3 because the minimum sum of three numbers starting with 3 is 12 and we have to add to 11.
Now we find two numbers that add up to 9 (12 - 1 - 2) with 3 being the lowest possible.
3 + 4 = 7
4 + 5 = 9
The third number can be 3 or 4. With the third number found the last is fixed. The two possible combinations are:
1, 2, 3, 6
1, 2, 4, 5
You can turn this into a general algorithm. Consider this recursive implementation:
$all = all_sequences(14, 4);
echo "\nAll sequences:\n\n";
foreach ($all as $arr) {
echo implode(', ', $arr) . "\n";
}
function all_sequences($total, $num, $start = 1) {
if ($num == 1) {
return array($total);
}
$max = lowest_maximum($start, $num);
$limit = (int)(($total - $max) / $num) + $start;
$ret = array();
if ($num == 2) {
for ($i = $start; $i <= $limit; $i++) {
$ret[] = array($i, $total - $i);
}
} else {
for ($i = $start; $i <= $limit; $i++) {
$sub = all_sequences($total - $i, $num - 1, $i + 1);
foreach ($sub as $arr) {
array_unshift($arr, $i);
$ret[] = $arr;
}
}
}
return $ret;
}
function lowest_maximum($start, $num) {
return sum_linear($num) + ($start - 1) * $num;
}
function sum_linear($num) {
return ($num + 1) * $num / 2;
}
Output:
All sequences:
1, 2, 3, 8
1, 2, 4, 7
1, 2, 5, 6
1, 3, 4, 6
2, 3, 4, 5
One implementation of this would be to get all the sequences and select one at random. This has the advantage of equally weighting all possible combinations, which may or may not be useful or necessary to what you're doing.
That will become unwieldy with large totals or large numbers of elements, in which case the above algorithm can be modified to return a random element in the range from $start to $limit instead of every value.
I would use 'area under triangle' formula... like cletus(!?)
Im really gonna have to start paying more attention to things...
Anyway, i think this solution is pretty elegant now, it applies the desired minimum spacing between all elements, evenly, scales the gaps (distribution) evenly to maintain the original sum and does the job non-recursively (except for the sort):
Given an array a() of random numbers of length n
Generate a sort index s()
and work on the sorted intervals a(s(0))-a(s(1)), a(s(1))-a(s(2)) etc
increase each interval by the
desired minimum separation size eg 1
(this necessarily warps their
'randomness')
decrease each interval by a factor
calculated to restore the series sum
to what it is without the added
spacing.
If we add 1 to each of a series we increase the series sum by 1 * len
1 added to each of series intervals increases sum by:
len*(len+1)/2 //( ?pascal's triangle )
Draft code:
$series($length); //the input sequence
$seriesum=sum($series); //its sum
$minsepa=1; //minimum separation
$sorti=sort_index_of($series) //sorted index - php haz function?
$sepsum=$minsepa*($length*($length+1))/2;
//sum of extra separation
$unsepfactor100=($seriesum*100)/($seriesum+sepsum);
//scale factor for original separation to maintain size
//(*100~ for integer arithmetic)
$px=series($sorti(0)); //for loop needs the value of prev serie
for($x=1 ; $x < length; $x++)
{ $tx=$series($sorti($x)); //val of serie to
$series($sorti($x))= ($minsepa*$x) //adjust relative to prev
+ $px
+ (($tx-$px)*$unsepfactor100)/100;
$px=$tx; //store for next iteration
}
all intervals are reduced by a
constant (non-random-warping-factor)
separation can be set to values other
than one
implementantions need to be carefuly
tweaked (i usualy test&'calibrate')
to accomodate rounding errors.
Probably scale everything up by ~15
then back down after. Intervals should survive if done right.
After sort index is generated, shuffle the order of indexes to duplicate values to avoid runs in the sequence of collided series.
( or just shuffle final output if order never mattered )
Shuffle indexes of dupes:
for($x=1; $x<$len; $x++)
{ if ($series($srt($x))==$series($srt($x-1)))
{ if( random(0,1) )
{ $sw= $srt($x);
$srt($x)= $srt($x-1);
$srt($x-1)= $sw;
} } }
A kind of minimal disturbance can be done to a 'random sequence' by just parting dupes by the minimum required, rather than moving them more than minimum -some 'random' amount that was sought by the question.
The code here separates every element by the min separation, whether duplicate or not, that should be kindof evenhanded, but overdone maybe. The code could be modified to only separate the dupes by looking through the series(sorti(n0:n1..len)) for them and calculating sepsum as +=minsep*(len-n) for each dupe. Then the adjustment loop just has to test again for dupe before applying adjustment.