I'm struggling with Project Euler problem 23: Non-abundant sums.
I have a script, that calculates abundant numbers:
function getSummOfDivisors( $number )
{
$divisors = array ();
for( $i = 1; $i < $number; $i ++ ) {
if ( $number % $i == 0 ) {
$divisors[] = $i;
}
}
return array_sum( $divisors );
}
$limit = 28123;
//$limit = 1000;
$matches = array();
$k = 0;
while( $k <= ( $limit/2 ) ) {
if ( $k < getSummOfDivisors( $k ) ) {
$matches[] = $k;
}
$k++;
}
echo '<pre>'; print_r( $matches );
I checked those numbers with the available on the internet already, and they are correct. I can multiply those by 2 and get the number that is the sum of two abundant numbers.
But since I need to find all numbers that cannot be written like that, I just reverse the if statement like this:
if ( $k >= getSummOfDivisors( $k ) )
This should now store all, that cannot be created as the sum of to abundant numbers, but something is not quit right here. When I sum them up I get a number that is not even close to the right answer.
I don't want to see an answer, but I need some guidelines / tips on what am I doing wrong ( or what am I missing or miss-understanding ).
EDIT: I also tried in the reverse order, meaning, starting from top, dividing by 2 and checking if those are abundant. Still comes out wrong.
An error in your logic lies in the line:
"I can multiply those by 2 and get the number that is the sum of two abundant numbers"
You first determine all the abundant numbers [n1, n2, n3....] below the analytically proven limit. It is then true to state that all integers [2*n1, 2*n2,....] are the sum of two abundant numbers but n1+n2, and n2+n3 are also the sum of two abundant numbers. Therein lies your error. You have to calculate all possible integers that are the sum of any two numbers from [n1, n2, n3....] and then take the inverse to find the integers that are not.
I checked those numbers with the available on the internet already, and they are correct. I can multiply those by 2 and get the number that is the sum of two abundant numbers.
No, that's not right. There is only one abundant number <= 16, but the numbers <= 32 that can be written as the sum of abundant numbers are 24 (= 12 + 12), 30 (= 12 + 18), 32 (= 12 + 20).
If you have k numbers, there are k*(k+1)/2 ways to choose two (not necessarily different) of them. Often, a lot of these pairs will have the same sum, so in general there are much fewer than k*(k+1)/2 numbers that can be written as the sum of two of the given k numbers, but usually, there are more than 2*k.
Also, there are many numbers <= 28123 that can be written as the sum of abundant numbers only with one of the two abundant numbers larger than 28123/2.
This should now store all, that cannot be created as the sum of to abundant numbers,
No, that would store the non-abundant numbers, those may or may not be the sum of abundant numbers, e.g. 32 is a deficient number (sum of all divisors except 32 is 31), but can be written as the sum of two abundant numbers (see above).
You need to find the abundant numbers, but not only to half the given limit, and you need to check which numbers can be written as the sum of two abundant numbers. You can do that by taking all pairs of two abundant numbers (<= $limit) and mark the sum, or by checking $number - $abundant until you either find a pair of abundant numbers or determine that none sums to $number.
There are a few number theoretic properties that can speed it up greatly.
Below is php code takes 320 seconds
<?php
set_time_limit(0);
ini_set('memory_limit', '2G');
$time_start = microtime(true);
$abundantNumbers = array();
$sumOfTwoAbundantNumbers = array();
$totalNumbers = array();
$limit = 28123;
for ($i = 12; $i <= $limit; $i++) {
if ($i >= 24) {
$totalNumbers[] = $i;
}
if (isAbundant($i)) {
$abundantNumbers[] = $i;
}
}
$countOfAbundantNumbers = count($abundantNumbers);
for ($j = 0; $j < $countOfAbundantNumbers; $j++) {
if (($j * 2) > $limit)
break; //if sum of two abundant exceeds limit ignore that
for ($k = $j; $k < $countOfAbundantNumbers; $k++) { //set $k = $j to avoid duble addtion like 1+2, 2+1
$l = $abundantNumbers[$j] + $abundantNumbers[$k];
$sumOfTwoAbundantNumbers[] = $l;
}
}
$numbers = array_diff($totalNumbers, $sumOfTwoAbundantNumbers);
echo '<pre>';print_r(array_sum($numbers));
$time_end = microtime(true);
$execution_time = ($time_end - $time_start);
//execution time of the script
echo '<br /><b>Total Execution Time:</b> ' . $execution_time . 'seconds';
exit;
function isAbundant($n) {
if ($n % 12 == 0 || $n % 945 == 0) { //first even and odd abundant number. a multiple of abundant number is also abundant
return true;
}
$k = round(sqrt($n));
$sum = 1;
if ($n >= 1 && $n <= 28123) {
for ($i = 2; $i <= $k; $i++) {
if ($n % $i == 0)
$sum+= $i + ( $n / $i);
if ($n / $i == $i) {
$sum = $sum - $i;
}
}
}
return $sum > $n;
}
Related
In PHP I want to generate random numbers from 1 to 10 in a loop.
So for example:
$factor="1"; // this will be changed in another area
for ($i=0;$i<10;$i++) {
if ($factor=="1") {$output = rand(1,10);}
else if ($factor=="2") {$output = rand(1,10);}
else {$output = rand(1,10);}
}
Now to explain this - In result I want to receive 10 random numbers, but when $factor = "2", in that case I want to receive numbers from 6 to 10 more frequently as lower numbers.
It means, from 10 numbers I need to have 80% higher random numbers (it means larger than 5) and in 20% lower numbers (5 or lower).
E.g. 1,2,6,7,8,9,7,9,8,6 (1,2 are the only lower numbers = 20%, the rest are higher = 80)
If the $factor will change, then I want to change the percentage, in that case for example 40% lower numbers, 60% higher numbers.
The idea I have is to put each output in the loop to an array, then check each result and somehow calculate, if there is no 80% of larger numbers, then get random numbers again for those, but this seems to be an overkill.
Is there a simplier solution?
Let's go with the percentages you mention and first generate a random number between 1 and 100. Then the lower number, 1 to 20, have to represent outputs 1 to 5 and the higher numbers, 21 to 100, have to represent output 6 to 10. In PHP that would look like this:
function HighMoreOften()
{
$percentage = rand(1, 100);
if ($percentage <= 20) {
return rand(1, 5);
} else {
return rand(6, 10);
}
}
That should do the trick. You can also convert the percentage you got into the output, this would probably be slightly faster:
function HighMoreOften()
{
$percentage = rand(1, 100);
if ($percentage <= 20) {
return ceil($percentage / 5);
} else {
return 6 + ceil(($percentage - 20) / 20);
}
}
but personally I think the first version is easier to understand and change.
To change frequency you gonna need an array of numbers. And a sum to this direction. frequency is the relation of something between an array of things.
$t = 0;
// $factor = 1; // let's say that this means 20%
$factor = 2; // let's say that this means 40%
if ($factor === 1) {
for ($i = 1; $i <= 80; $i++) {
$t += rand(1,10);
}
for ($i = 1; $i <= 20; $i++) {
$t += rand(6,10);
}
} else if ($factor === 2) {
for ($i = 1; $i <= 60; $i++) {
$t += rand(1,10);
}
for ($i = 1; $i <= 40; $i++) {
$t += rand(6,10);
}
} else {
for ($i = 1; $i <= 100; $i++) {
$t += rand(1,10);
}
}
echo round(($t/100), 0);
Something like that! :)
I came with a very simple (maybe creepy) solution, but this works as I wanted:
echo print_r(generate("2"));
function generate($factor) {
$nums=array();
for ($i=0;$i<10;$i++) {
if ($i<$factor) {$rnd = rand(1,5);}
else {$rnd = rand(6,10);}
array_push($nums,$rnd);
}
return $nums;
}
I can also shuffle the final array results, as the lower numbers will be on the beginning always, but in my case it doesn't matter.
For an endless number such as Pi, how would one go about finding the first occurrence of an exact sum of digits for a given number n.
For example. If n=20
Pi=3.14159265358979323846264338327950288419716939...
then the first occurrence is from digit 1 to digit 5 since:
1+4+1+5+9=20
if n=30, then the first occurrence is from digit 5 to digit 11
since 9+2+6+5+3+5=30
answer should have a working php demo
The answer to this is using sliding window that will maintain the sum. so maintain two pointers say i and j. Keep increasing j and adding the elements inside. when it crosses the desired sum increase i and decrease the element at i. Then keep increasing j until the sum is reached or the sum overflows so you repeat the above process.
Example sum = 30
141592653589793238 >> i=j=0 current_sum = 1
141592653589793238 >> i=0 j=6 current_sum=28
in the next iteration adding 5 will result in current_sum>30 so hence you increment i
141592653589793238 >> i=1 j=6 current_sum=27
141592653589793238 >> i=2 j=6 current_sum=23
141592653589793238 >> i=2 j=7 current_sum=28
Keep going in this manner and it will finally reach the window that is equal to the sum =30 . That should break you out of the loop and help you find the answer.
Method 1 (suggested by Ashwin Bhat)
This implementation uses two pivots. The sum of digits between $pivot_a and $pivot_b is computed. Depending on the value of the sum, we increment $pivot_b (if the sum is less) or $pivot_a (if the sum is greater). If the sum is equal to $n, break. The values of the pivots give the appropriate digit indices.
$pi = "314159265358979323846264338327950288419716939";
$n = 30;
$pivot_a = $pivot_b = 0;
$sum = 0;
for( ; $pivot_b < strlen($pi); ) {
if($sum < $n) {
$sum += $pi[$pivot_b++];
} elseif ($sum > $n) {
$sum -= $pi[$pivot_a++];
} else {
print('Solution found from digit '.$pivot_a.' to '.$pivot_b.'.');
exit;
}
}
print('No match was found.');
Method 2
This implementation uses one pivot only, from which it starts summing up the digits. If the sum happens to be greater than the desired value, it resets the sum to zero, shifts the pivot one position and starts the summing again.
$pi = "314159265358979323846264338327950288419716939";
$n = 30;
// Let's sum up all the elements from $pivot until we get the exact sum or a
// number greater than that. In the latter case, shift the $pivot one place.
$pivot = 0;
$sum = 0;
for($k=0 ; $sum != $n && $k < strlen($pi) ; $k++) {
$sum += $pi[$k];
print($pi[$k]);
if($sum > $n) {
print(' = '.$sum.' fail, k='.($pivot+1).PHP_EOL);
$sum = 0;
$k = $pivot++;
} elseif($sum < $n) {
print("+");
}
}
print(' = '.$n.' found from digit '.$pivot.' to '.$k.'.');
The implementation is not very effective but tries to explain the steps. It prints
3+1+4+1+5+9+2+6 = 31 fail, k=1
1+4+1+5+9+2+6+5 = 33 fail, k=2
4+1+5+9+2+6+5 = 32 fail, k=3
1+5+9+2+6+5+3 = 31 fail, k=4
5+9+2+6+5+3 = 30 found from digit 4 to 10.
Here's another approach. It builds an array of sums along the way and, on every iteration, attempts to add the current digit to the previous sums, and so on, while always only keeping the sums that are still relevant (< target).
The function either returns:
an array of 2 values representing the 0-based index interval within the digits,
or null if it couldn't find the target sum
Code:
function findFirstSumOccurrenceIndexes(string $digits, int $targetSum): ?array
{
$sums = [];
for ($pos = 0, $length = strlen($digits); $pos < $length; $pos++) {
$digit = (int)$digits[$pos];
if ($digit === $targetSum) {
return [$pos, $pos];
}
foreach ($sums as $startPos => $sum) {
if ($sum + $digit === $targetSum) {
return [$startPos, $pos];
}
if ($sum + $digit < $targetSum) {
$sums[$startPos] += $digit;
}
else {
unset($sums[$startPos]);
}
}
$sums[] = $digit;
}
return null;
}
Demo: https://3v4l.org/9t3vf
I'm trying to make a PHP counter and this is what I have:
<?PHP
for ($i = 0; $i < 10; $i ++) {
print "$i";
}
?>
It's quite a simple counter but I was wondering if it could count all the decimal numbers (only one decimal place) as well, is there any way I could do that?
This is what I would like:
0.1,
0.2,
0.3,
...
9.8,
9.9,
10.0.
It's impossible to count from 1 to 10, including ALL decimals. There would be infinitely many of them.
However, you could count in tenths:
<?php
for ($i = 0; $i <= 10; $i+=0.1) {
print "$i\r\n";
}
?>
You need to decide on a precision. Let's say you want a decimal precision of 2 decimal places for this exercise (but this could obviously be changed in code). My suggestion would be to simply implement an integer counter between 0 and 1000 and then divide counter value by 100 to get decimal values.
$decimal_precision = 2;
$counter_limit = 10;
$counter_ratio = (int)pow(10, $decimal_precision);
$integer_limit = $counter_limit * $counter_ratio;
for($i = 1; $i <= $integer_limit; $i++) {
echo (float) $i / $counter_ratio;
}
I need to generate x amount of random odd numbers, within a given range.
I know this can be achieved with simple looping, but I'm unsure which approach would be the best, and is there a better mathematical way of solving this.
EDIT: Also I cannot have the same number more than once.
Generate x integer values over half the range, and for each value double it and add 1.
ANSWERING REVISED QUESTION: 1) Generate a list of candidates in range, shuffle them, and then take the first x. Or 2) generate values as per my original recommendation, and reject and retry if the generated value is in the list of already generated values.
The first will work better if x is a substantial fraction of the range, the latter if x is small relative to the range.
ADDENDUM: Should have thought of this approach earlier, it's based on conditional probability. I don't know php (I came at this from the "random" tag), so I'll express it as pseudo-code:
generate(x, upper_limit)
loop with index i from upper_limit downto 1 by 2
p_value = x / floor((i + 1) / 2)
if rand <= p_value
include i in selected set
decrement x
return/exit if x <= 0
end if
end loop
end generate
x is the desired number of values to generate, upper_limit is the largest odd number in the range, and rand generates a uniformly distributed random number between zero and one. Basically, it steps through the candidate set of odd numbers and accepts or rejects each one based how many values you still need and how many candidates still remain.
I've tested this and it really works. It requires less intermediate storage than shuffling and fewer iterations than the original acceptance/rejection.
Generate a list of elements in the range, remove the element you want in your random series. Repeat x times.
Or you can generate an array with the odd numbers in the range, then do a shuffle
Generation is easy:
$range_array = array();
for( $i = 0; $i < $max_value; $i++){
$range_array[] .= $i*2 + 1;
}
Shuffle
shuffle( $range_array );
splice out the x first elements.
$result = array_slice( $range_array, 0, $x );
This is a complete solution.
function mt_rands($min_rand, $max_rand, $num_rand){
if(!is_integer($min_rand) or !is_integer($max_rand)){
return false;
}
if($min_rand >= $max_rand){
return false;
}
if(!is_integer($num_rand) or ($num_rand < 1)){
return false;
}
if($num_rand <= ($max_rand - $min_rand)){
return false;
}
$rands = array();
while(count($rands) < $num_rand){
$loops = 0;
do{
++$loops; // loop limiter, use it if you want to
$rand = mt_rand($min_rand, $max_rand);
}while(in_array($rand, $rands, true));
$rands[] = $rand;
}
return $rands;
}
// let's see how it went
var_export($rands = mt_rands(0, 50, 5));
Code is not tested. Just wrote it. Can be improved a bit but it's up to you.
This code generates 5 odd unique numbers in the interval [1, 20]. Change $min, $max and $n = 5 according to your needs.
<?php
function odd_filter($x)
{
if (($x % 2) == 1)
{
return true;
}
return false;
}
// seed with microseconds
function make_seed()
{
list($usec, $sec) = explode(' ', microtime());
return (float) $sec + ((float) $usec * 100000);
}
srand(make_seed());
$min = 1;
$max = 20;
//number of random numbers
$n = 5;
if (($max - $min + 1)/2 < $n)
{
print "iterval [$min, $max] is too short to generate $n odd numbers!\n";
exit(1);
}
$result = array();
for ($i = 0; $i < $n; ++$i)
{
$x = rand($min, $max);
//not exists in the hash and is odd
if(!isset($result{$x}) && odd_filter($x))
{
$result[$x] = 1;
}
else//new iteration needed
{
--$i;
}
}
$result = array_keys($result);
var_dump($result);
This is a simple program to find prime numbers which check for the division of a number at later stage.
I tried to short it by initially taking the integer square root of the number to break down the complexity. But still it is taking very much time in executing the script. What other changes I can implement in my code to decrease the execution time( I already set the max execution time to 5 min)
<?php
error_reporting(E_ALL);
$num = 600851475143;
//$sqrt_num = (int)sqrt($num);
for( $j = 2; $j <= $num; $j++ )
{
for( $k = 2; $k < $j; $k++ )
{
if( $j % $k == 0 )
{
break;
}
}
if( $k == $j )
{
//echo "Prime Number : ", $j, "<br>";
if( $num % $j == 0 )
{
echo "Prime number : ", $j, "<br>";
}
}
}
EDIT Just commented the line for sqrt as this seems to be in correct..but still the loop is taking much time.
One way to improve execution time of your code would be this:
$num = 1000;
for($j = 2; $j <= $num; $j++)
{
$cond = sqrt($j);
for($k = 2; $k <= $cond; $k++)
{
if($j % $k == 0)
{
break;
}
}
if($k > $cond)
{
echo 'Prime number: ' . $j . '<br>';
}
}
But there is no need to calculate prime numbers from the begining each time. You can remember every 30 seconds or so where you were, save the result to a database, file, or an array, and then restart the script, which should the continue from where it stopped.
The best way to reduce this code execution time is by removing it.
It's unnecessary to calculate every prime number each time you run this- they are not going to be changing any time soon.
Run it once, write it to a file or database and use that when you need it.
I'd personally put it in an array for later use.
Here's the basic algorithm for factoring by trial division; I don't know PHP, so I'll just give pseudocode:
function factors(n)
f, fs := 2, []
while f * f <= n
while n % f == 0
append f to fs
n := n / f
f := f + 1
if n > 1 append n to fs
return fs
There are better ways than that to find the factors of a number, but even that simple method should be sufficient for the Project Euler problem you are trying to solve; you should have a soluion in less than a second. When you're ready for more programming with prime numbers, I modestly recommend this essay at my blog.