Making number ranges adapt to the smallest and largest numbers - php

I have two numbers (for this example lets say 695 is the smallest and 36000 is the largest). Currently once I know the min and the max I divide the difference by ten and then cycle it through a loop to crate the ranges. It looks something like this:
$min = (int)$min-1;
$max = (int)$max;
$diff = ($max - $min) / 10;
$range = array();
for ( $i=1; $i<10; $i++){
$range[] = array(
"low"=>($i==1? $min: ($i*$diff) + $min),
"high"=>($i+1)*$diff + $min
);
}
This works great when the numbers are 695-36000. When the numbers get close together it becomes a little cumbersome to have 10 ranges. For example, the min is 34000 and the max is 36000 the ranges would be 34000-34200, 34200-34400, etc, etc.
Ideally if 695-36000 is 10 different ranges than 34000-36000 would be one range.
What would be an easy way to calculate how many ranges should show up and what those ranges should be?

Here is something to consider:
$diff = ($max - $min) / 10;
$range_max = 2000;
if($diff>$range_max) $range_size = $range_max;
else $range_size = $diff;
$creep = ($max-$min-$range_size)/9-$diff;
$range = array();
for($i=0; $i<10; $i++) {
$range[] = array (
'low' => (int)(($diff+$creep)*$i+$min),
'high' => (int)(($diff+$creep)*$i+$min+$range_size)
);
}

Related

Random Float against an specific amount with 100 days php

I am trying to generate random values against '100' amount of value for 100 days. I am trying this way with for loop:
$total_days = 100;
$total_amount = 100;
$arr = array();
for($i = 0; $i < $total_days; ++$i)
{
$arr[] = rand(0.0, 1000.0);
}
$actual_sum = array_sum($arr);
for($i = 0; $i < $total_days; ++$i)
{
$arr[$i] *= $total_amount /$actual_sum;
//echo $arr[$i]."<BR><BR>";
$value = $arr[$i];
$genpackageroi = array(
'userid' => $userid,
'pkgid' => $pkgid,
'pkgcount' => $pkcount,
'amount' => $value,
'created_at' => $created_at,
'updated_at' => $updated_at,
);
DB::table('genpackageroi')->insert($genpackageroi);
}
The random generated value is like 0.1245444 OR 1.1245545, lots of numbers after decimal. While I just want this to be generated in float value like 1.21, 0.10, 2.25 like so. I it possible to do that?
But I want to generate the random numbers in round numbers. Because It
dose not matches the actual amount after generating all the random
numbers if we sum the generated random numbers. Like if I generates
the random numbers for amount of 100, then the sum of all generated
random numbers should 100. This is what actually I want.
What I gathered from your statement is that you want 100 floating numbers with 2 decimal places, and the sum of all these numbers should add up to 100.
This certainly is not the cleanest solution, but here goes:
$total_days = 100;
$total_amount = 100;
$arr = array();
for($i = 0; $i < $total_days; ++$i)
{
$arr[] = rand(0.0, 1000.0);
}
$actual_sum = array_sum($arr);
for($i = 0; $i < $total_days; ++$i)
{
$y = $arr[$i] * ($total_amount /$actual_sum);
$arr[$i] = round($y, 2); //round the numbers to 2 dp. Sum of all numbers could be greater than 100.
}
//hack, logic explained below
$maxElementKeys = array_keys($arr, max($arr)); //get max element's key
unset($arr[$maxElementKeys[0]]); //remove the max element from array, now the array contains 99 elements
$arr = array_values($arr); //rebase the keys
for($i = 0; $i < $total_days; ++$i)
{
if($i < ($total_days - 1) )
$value = $arr[$i];
else
$value = $total_amount - array_sum($arr); //to get the 100th number, subtract from 100 to ensure the total always adds up to 100
$genpackageroi = array(
'userid' => $userid,
'pkgid' => $pkgid,
'pkgcount' => $pkcount,
'amount' => $value,
'created_at' => $created_at,
'updated_at' => $updated_at,
);
DB::table('genpackageroi')->insert($genpackageroi);
}
The logic here is that round all numbers to 2dps. The total of which would in most cases exceed 100 by a few dps.
Next, get the max random number in the array and remove it from the array to make it an array containing 99 elements. The max number is removed to ensure that the sum of the new array stays below well clear of 100. So, to get the 100th element, get the sum of the new array and subtract that value from 100. Now, the total of all elements of the array and the 100th element that was just calculated should add up to exactly 100, with negligible chance for failure.

Create an array only knowing max and min values

Coding in PHP, I currently have an array of 100 values, which looks like this:
$cmassarray = array(630.00,629.70,629.40,629.10,628.80,628.50,628.20,627.90,627.60,627.30,627.00,
626.70,626.40,626.10,625.80,625.50,625.20,624.90,624.60,624.30,624.00,623.70,
623.40,623.10,622.80,622.50,622.20,621.90,621.60,621.30,621.00,620.70,620.40,
620.10,619.80,619.50,619.20,618.90,618.60,618.30,618.00,617.70,617.40,617.10,
616.80,616.50,616.20,615.90,615.60,615.30,615.00,614.70,614.40,614.10,613.80,
613.50,613.20,612.90,612.60,612.30,612.00,611.70,611.40,611.10,610.80,610.50,
610.20,609.90,609.60,609.30,609.00,608.70,608.40,608.10,607.80,607.50,607.20,
606.90,606.60,606.30,606.00,605.70,605.40,605.10,604.80,604.50,604.20,603.90,
603.60,603.30,603.00,602.70,602.40,602.10,601.80,601.50,601.20,600.90,600.60,
600.30,600.00);
All of the steps are the same length, and I may need to change them (and/or the max/min values) at a future stage, so I would like to find a way to avoid having to re-calculate them manually and type them each time.
If I know the maximum value is 630.00 and the minimum value is 600.00, and that I have 100 steps, would it be possible to create an array specifying that each value is an increment on this equation?
x (array value) = 600+((Max-Min)/100)*y)
where y is the incremental step in the scale.
Thank you for any help!
An alternative to using loops and all that would be range
$start=630;
$stop=600;
$steps=100;
$cmassarray=range( $start, $stop, ( ( $start-$stop ) / $steps ) );
You might want to have a look at the range function, which takes an optional third argument for step. This argument you can easily derive from your maximum, minimum and amount of steps. Here's an example:
$max = 630;
$min = 600;
$steps = 100;
$step = ($max - $min) / $steps;
$your_result = range( $max, $min, $step );
Start with this code:
$max = 630;
$min = 600;
$steps = 100;
$step = ($max - $min) / $steps;
$ar = [];
for ($i = $max; $i >= $min; $i -= $step) {
$ar[] = $i;
}

Generating random values and keeping track of their sum

I have more than 200 entries in a database table and I would like to generate a random value for each entry, but in the end, the sum of entries values must equal 100. Is it possible to do this using a for loop and rand() in PHP?
You could simply normalize a set of numbers, like:
$numbers = array();
for ($i = 0; $i < 200; $i += 1) {
$numbers[] = rand();
}
$sum = array_sum($numbers);
// divide $sum by the target sum, to have an instant result, e.g.:
// $sum = array_sum($numbers) / 100;
// $sum = array_sum($numbers) / 42;
// ...
$numbers = array_map(function ($n) use($sum) {
return $n / $sum;
}, $numbers);
print_r($numbers);
print_r(array_sum($numbers)); // ~ 1
demo: http://codepad.viper-7.com/RDOIvX
The solution for your problem is to rand number from 0 to 200 then put in array, then sum the values and divide it by 200 after that. Loop through elements and divide every element by result of previous equatation it will give you the answer
$sum = 0;
$max = 100; //max value to be sumed
$nr_of_records = 200; // number of records that should sum to $max
$arr = array();
for($i=0;$i<$nr_of_records;++$i)
{
$arr[$i] = rand(0,$max);
}
$div = array_sum($arr) / $max;
for($i=0;$i<$nr_of_records;++$i)
{
$arr[$i] /= $div;
echo $arr[$i].'<br>';
}
echo array_sum($arr);
Created living example
How exact has the 100 to be? Just curious, because all hints end at using floating point values, which tend to be inacurate.
I'd propose using fractions... lets say 10000 fractions, each count 1/100 point (10000 * 1/100 = 100 points). Distribute 10000 points to 200 elements, using integers - and be absolutely sure, that the sum of all integers divided by 10000 is 100. There is no need for floats, just think around the corner...
Do a little over/under:
$size = 200;
$sum = 100;
$places = 3;
$base = round($sum/$size, $places);
$values = array_fill(0, $size, $base);
for($i=0; $i<$size; $i+=2) {
$diff = round((rand()/getrandmax()) * $base, $places);
$values[$i] += $diff;
$values[$i+1] -= $diff;
}
//optional: array_shuffle($values);
$sum = 0;
foreach($values as $item) {
printf("%0.3f ", $item);
$sum += $item;
}
echo $sum;
Output:
0.650 0.350 0.649 0.351 0.911 0.089 0.678 0.322 0.566 0.434 0.563 0.437 0.933 0.067 0.505 0.495 0.503 0.497 0.752 0.248 0.957 0.043 0.856 0.144 0.977 0.023 0.863 0.137 0.766 0.234 0.653 0.347 0.770 0.230 0.888 0.112 0.637 0.363 0.716 0.284 0.891 0.109 0.549 0.451 0.629 0.371 0.501 0.499 0.652 0.348 0.729 0.271 0.957 0.043 0.769 0.231 0.767 0.233 0.513 0.487 0.647 0.353 0.612 0.388 0.509 0.491 0.925 0.075 0.797 0.203 0.799 0.201 0.588 0.412 0.788 0.212 0.693 0.307 0.688 0.312 0.847 0.153 0.903 0.097 0.843 0.157 0.801 0.199 0.538 0.462 0.954 0.046 0.541 0.459 0.893 0.107 0.592 0.408 0.913 0.087 0.711 0.289 0.679 0.321 0.816 0.184 0.781 0.219 0.632 0.368 0.839 0.161 0.568 0.432 0.914 0.086 0.991 0.009 0.979 0.021 0.666 0.334 0.678 0.322 0.705 0.295 0.683 0.317 0.869 0.131 0.837 0.163 0.792 0.208 0.618 0.382 0.606 0.394 0.574 0.426 0.927 0.073 0.661 0.339 0.986 0.014 0.759 0.241 0.547 0.453 0.804 0.196 0.681 0.319 0.960 0.040 0.708 0.292 0.558 0.442 0.605 0.395 0.986 0.014 0.621 0.379 0.992 0.008 0.622 0.378 0.937 0.063 0.884 0.116 0.840 0.160 0.607 0.393 0.765 0.235 0.632 0.368 0.898 0.102 0.946 0.054 0.794 0.206 0.561 0.439 0.801 0.199 0.770 0.230 0.843 0.157 0.681 0.319 0.794 0.206 100
The rounding gets a bit squiffy if you're not using nice numbers like 100 and 200, but never more than 0.1 off.
Original question yesterday had exactly 200 entries and the sum "not greater than 100".
My original answer from yesterday:
Use random numbers not greater than 0.5 to be sure.
Alternatively, depending on how "random" those numbers need to be (how
much correlation is allowed), you could keep a running total, and if
it gets disproportionately high, you can mix in a bunch of smaller
values.
Edit:
Way to go changing the question, making me look stupid and get downvoted.
To get the exact sum you have to normalize, and better use exact fractions instead of floats to avoid rounding errors.

displaying axis from min to max value - calculating scale and labels

Writing a routine to display data on a horizontal axis (using PHP gd2, but that's not the point here).
The axis starts at $min to $max and displays a diamond at $result, such an image will be around 300px wide and 30px high, like this:
(source: testwolke.de)
In the example above, $min=0, $max=3, $result=0.6.
Now, I need to calculate a scale and labels that make sense, in the above example e.g. dotted lines at 0 .25 .50 .75 1 1.25 ... up to 3, with number-labels at 0 1 2 3.
If $min=-200 and $max=600, dotted lines should be at -200 -150 -100 -50 0 50 100 ... up to 600, with number-labels at -200 -100 0 100 ... up to 600.
With $min=.02and $max=5.80, dotted lines at .02 .5 1 1.5 2 2.5 ... 5.5 5.8 and numbers at .02 1 2 3 4 5 5.8.
I tried explicitly telling the function where to put dotted lines and numbers by arrays, but hey, it's the computer who's supposed to work, not me, right?!
So, how to calculate???
An algorithm (example values $min=-186 and $max=+153 as limits):
Take these two limits $min, $max and mark them if you wish
Calculate the difference between $max and $min: $diff = $max - $min
153 - (-186) = 339
Calculate 10th logarithm of the difference $base10 = log($diff,10) = 2,5302
Round down: $power = round($base10) = 2.
This is your tenth power as base unit
To calculate $step calculate this:
$base_unit = 10^$power = 100;
$step = $base_unit / 2; (if you want 2 ticks per one $base_unit).
Calculate if $min is divisible by $step, if not take the nearest (round up) one
(in the case of $step = 50 it is $loop_start = -150)
for ($i=$loop_start; $i<=$max; $i++=$step){ // $i's are your ticks
end
I tested it in Excel and it gives quite nice results, you may want to increase its functionality,
for example (in point 5) by calculating $step first from $diff,
say $step = $diff / 4 and round $step in such way that $base_unit is divisible by $step;
this will avoid such situations that you have between (101;201) four ticks with $step=25 and you have 39 steps $step=25 between 0 and 999.
ACM Algorithm 463 provides three simple functions to produce good axis scales with outputs xminp, xmaxp and dist for the minimum and maximum values on the scale and the distance between tick marks on the scale, given a request for n intervals that include the data points xmin and xmax:
Scale1() gives a linear scale with approximately n intervals and dist being an integer power of 10 times 1, 2 or 5.
Scale2() gives a linear scale with exactly n intervals (the gap between xminp and xmaxp tends to be larger than the gap produced by Scale1()).
Scale3() gives a logarithmic scale.
The original 1973 paper is online here, which provides more explanation than the code linked to above.
The code is in Fortran but it is just a set of arithmetical calculations so it is very straightforward to interpret and convert into other languages. I haven't written any PHP myself, but it looks a lot like C so you might want to start by running the code through f2c which should give you something close to runnable in PHP.
There are more complicated functions that give prettier scales (e.g. the ones in gnuplot), but Scale1() would likely do the job for you with minimal code.
(This answer builds on my answer to a previous question Graph axis calibration in C++)
(EDIT -- I've found an implementation of Scale1() that I did in Perl):
use strict;
sub scale1 ($$$) {
# from TOMS 463
# returns a suitable scale ($xMinp, $xMaxp, $dist), when called with
# the minimum and maximum x values, and an approximate number of intervals
# to divide into. $dist is the size of each interval that results.
# #vInt is an array of acceptable values for $dist.
# #sqr is an array of geometric means of adjacent values of #vInt, which
# is used as break points to determine which #vInt value to use.
#
my ($xMin, $xMax, $n) = #_;
#vInt = {1, 2, 5, 10};
#sqr = {1.414214, 3.162278, 7.071068 }
if ($xMin > $xMax) {
my ($tmp) = $xMin;
$xMin = $xMax;
$xMax = $tmp;
}
my ($del) = 0.0002; # accounts for computer round-off
my ($fn) = $n;
# find approximate interval size $a
my ($a) = ($xMax - $xMin) / $fn;
my ($al) = log10($a);
my ($nal) = int($al);
if ($a < 1) {
$nal = $nal - 1;
}
# $a is scaled into a variable named $b, between 1 and 10
my ($b) = $a / 10^$nal;
# the closest permissable value for $b is found)
my ($i);
for ($i = 0; $i < $_sqr; $i++) {
if ($b < $sqr[$i]) last;
}
# the interval size is computed
$dist = $vInt[$i] * 10^$nal;
$fm1 = $xMin / $dist;
$m1 = int($fm1);
if ($fm1 < 0) $m1--;
if (abs(($m1 + 1.0) - $fm1) < $del) $m1++;
# the new minimum and maximum limits are found
$xMinp = $dist * $m1;
$fm2 = $xMax / $dist;
$m2 = $fm2 + 1;
if ($fm2 < -1) $m2--;
if (abs ($fm2 + 1 - $m2) < $del) $m2--;
$xMaxp = $dist * $m2;
# adjust limits to account for round-off if necessary
if ($xMinp > $xMin) $xMinp = $xMin;
if ($xMaxp < $xMax) $xMaxp = $xMax;
return ($xMinp, $xMaxp, $dist);
}
sub scale1_Test {
$par = (-3.1, 11.1, 5,
5.2, 10.1, 5,
-12000, -100, 9);
print "xMin\txMax\tn\txMinp\txMaxp,dist\n";
for ($i = 0; $i < $_par/3; $i++) {
($xMinp, $xMaxp, $dist) = scale1($par[3*$i+0],
$par[3*$i+1], $par[3*$i+2]);
print "$par[3*$i+0]\t$par[3*$i+1]\t$par[3*$i+2]\t$xMinp\t$xMaxp,$dist\n";
}
}
I know that this isn't exactly what you are looking for, but hopefully it will get you started in the right direction.
$min = -200;
$max = 600;
$difference = $max - $min;
$labels = 10;
$picture_width = 300;
/* Get units per label */
$difference_between = $difference / ($labels - 1);
$width_between = $picture_width / $labels;
/* Make the label array */
$label_arr = array();
$label_arr[] = array('label' => $min, 'x_pos' => 0);
/* Loop through the number of labels */
for($i = 1, $l = $labels; $i < $l; $i++) {
$label = $min + ($difference_between * $i);
$label_arr[] = array('label' => $label, 'x_pos' => $width_between * $i);
}
A quick example would be something in the lines of $increment = ($max-$min)/$scale where you can tweak scale to be the variable by which the increment scales. Since you devide by it, it should change proportionately as your max and min values change. After that you will have a function like:
$end = false;
while($end==false){
$breakpoint = $last_value + $increment; // that's your current breakpoint
if($breakpoint > $max){
$end = true;
}
}
At least thats the concept... Let me know if you have troubles with it.

Project Euler #23: Non-abundant sums

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

Categories