Randomize a Number over multiple points in descending order - php

I need to distribute a number for multiple points in degraded order.
For example,
N = 100 // this 100 is fixed
no_of_points = 10 // no of points will vary
Need to break 100 into 10 points in descending order RANDOMLY.
e.g:
80, 55, 32, 18, 10, 10, 10, 8, 2, 0
Here,
degradation will be faster towards end
Must contain a zero to end
I'm trying to do something like:
private static function generateRandomPercentage($no_of_points)
{
$distributions = [];
// need to start from 80
// but it may vary also
$max = mt_rand(80, 81);
$ratio = $max / $no_of_points;
for( $i = 1; $i <= $no_of_points; $i++ ) {
$delta = ($ratio * $i);
$distributions[] = round(($max - $delta) / 100, 2);
}
print_r($distributions);
}
But nothing working.
Please help me.

Can be done with php standard functions:
$N = 100 // this 100 is fixed
$no_of_points = 10 // no of points will vary
$distributions= rsort(array_slice(shuffle(range(1,$N)), 0, $no_of_points);
print_r($distributions));
range = create array with values 1...n
shuffle = shuffle array
array_slice = return part of an array
rsort = sort array values hight to low values

Related

To sum elements of array between a range of index, what would be best way to do it in PHP?

Lets say for $numArr = array(10,20,30,40,50,60,70,80,90,100);
how to sum between specific range like 30 - 60 to get sum of 180.. (30+40+50+60).
edit : This is my latest code
<?php
function sum_array ($no1, $no2){
$array = array(10,20,30,40,50,60,70,80,90,100);
$input = array_slice($array, $no1, $no2);
return array_sum($input);
}
echo sum_array(0,3);
?>
I made a basic function for this according to u guys replies .. though still i want to put some validations into this like the parameters should be positive else the function should return -1 .. and what if the second index of the range is not in array.. like (90-120). Would it able to still sum what's in range and in array .. and give 190 to the above range.
$numArr = [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ];
$startValue = 30;
$endValue = 60;
$startIndex = array_search($startValue, $numArr);
$endIndex = array_search($endValue, $numArr);
$length = $endIndex - $startIndex + 1;
$result = array_sum(array_slice($numArr, $startIndex, $length));
print_r($result);

PHP - Get range either side of integer

In PHP, I am getting a range like this...
$number = range(0,50,10);
This is working, but now I am trying to modify it so that given a number, it will get the range 5 digits either side of that number.
So, for example, given the number 25, I would like...
20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30
To further slightly complicate things, I would only like these numbers to be positive, so if the start number was 3, then it would only get the range 1-8
Just subtract and add to the number in the center. Use the max() function to restrict the beginning to at least 1.
$n = 25;
$numbers = range(max(1, $n-5), $n+5)
max() will help you out here.
$width = 5; // Width of your range
$center = 3; // Center of your range
$r = range(max(1, $center-$width), $center+$width);
You could try something like this:
for ($i = $number - 5; $i <= $number + 5; $i++) {
if ($i > 0) {
return $i;
} else {
return "negative";
}
}
I am new to PHP and not entirely sure if that will work 😬

PHP rand() exclude certain numbers

I have this:
<?php $n = rand(1,1600); echo $n ?>
I want to exclude from random numbers let's say 234, 1578 ,763 , 1274 and other numbers. How would I do that?
<?php
while( in_array( ($n = mt_rand(1,1600)), array(234, 1578 ,763 , 1274) ) );
Try like this
do {
$n = rand(1,1600);
} while(in_array($n, array(234, 1578 ,763 , 1274 ));
echo $n;
Check if the number is one that you don't want, if it is get a new random number.
function getRandomNumber() {
do {
$n = mt_rand(1,1600);
} while(in_array($n, array(234,1578, 763, 1274)));
return $n;
}
Always use cryptographically strong algorithms for generating random numbers:
/**
* #param int $from From number
* #param int $to To number
* #param array $excluded Additionally exclude numbers
* #return int
*/
function randomNumber($from, $to, array $excluded = [])
{
$func = function_exists('random_int') ? 'random_int' : 'mt_rand';
do {
$number = $func($from, $to);
} while (in_array($number, $excluded, true));
return $number;
}
var_dump(randomNumber(1, 100));
var_dump(randomNumber(1, 10, [5, 6, 7, 8]));
var_dump(randomNumber(1, 100, range(10, 90)));
I'd also recommend using the paragonie/random_compat library for compatibility in case of using multiple PHP versions.
Or avoid making loops with random (possibly infinite) running time:
/**
* Returns a random integer between $min and $max (inclusive) and
* excludes integers in $exarr, returns false if no such number
* exists.
*
* $exarr is assumed to be sorted in increasing order and each
* element should be unique.
*/
function random_exclude($min, $max, $exarr = array()) {
if ($max - count($exarr) < $min) {
return false;
}
// $pos is the position that the random number will take
// of all allowed positions
$pos = rand(0, $max - $min - count($exarr));
// $num being the random number
$num = $min;
// while $pos > 0, step to the next position
// and decrease if the next position is available
for ($i = 0; $i < count($exarr); $i += 1) {
// if $num is on an excluded position, skip it
if ($num == $exarr[$i]) {
$num += 1;
continue;
}
$dif = $exarr[$i] - $num;
// if the position is after the next excluded number,
// go to the next excluded number
if ($pos >= $dif) {
$num += $dif;
// -1 because we're now at an excluded position
$pos -= $dif - 1;
} else {
// otherwise, return the free position
return $num + $pos;
}
}
// return the number plus the open positions we still had to go
return $num + $pos;
}
This function chooses a random position and walks the exclusion array to find the free position. It's running time depends on the amount of numbers to exclude. If you want to exclude certain ranges, you may want to adapt the algorithm to take this into account.
As the volume of "blacklisted" integers approaches the volume of the full range of integers, it becomes increasingly compelling to take the advice of #regenschein.
A non-iterative approach might look like this:
$range = range(1, 1600);
$blacklist = [234, 1578, 763, 1274]; // 4 blacklisted versus 1600 full range is NOT compelling
$valids = array_diff($range, $blacklist);
echo array_values($valids)[rand(0, count($valids) - 1)];
// or
echo $valids[array_rand($valids)];
// the two approaches use different randomizers
Or if you'd be just as happy shuffling, you could do:
$blacklist = [234, 1578, 763, 1274];
$range = range(1, 1600);
$valids = array_diff($range, $blacklist);
shuffle($valids);
echo $valids[0];
*Note array_diff() is particularly great if you want to pass multiple blacklist arrays -- just comma-separate them.
For example:
var_export($valids = array_diff(range(1, 100), range(5, 50), range(61, 99), [55]));
Output:
array (
0 => 1,
1 => 2,
2 => 3,
3 => 4,
50 => 51,
51 => 52,
52 => 53,
53 => 54,
55 => 56,
56 => 57,
57 => 58,
58 => 59,
59 => 60,
99 => 100,
)
Another solution for this could be as follows:
function random_number($min, $max, $exclude)
{
$number = rand($min, $max);
if(in_array($number, $exclude))
{
random_number($min, $max, $exclude);
} else {
return $number;
}
}
$number = random_number(1,10, [2,5,6]);
I know this is a bit old,but I think what the op tries to do is shuffle the integers. If so the following method is better
$array = array(1,2,3,4,5,6,7);
shuffle($array);
This code will randomize the order of the array's exact elements without repetition and return the result inside the array itself.
You could create an array with valid numbers.
Then, your random number generation should return the index into that array.
If you don't have too many numbers to exclude, it is easier and faster to just retry if you find an unwanted number:
$n = 0;
while (in_array($n, array(0, 234, 1578 ,763 , 1274))) {
$n = rand(1,1600);
}
echo $n;

Awkward criteria when generating random sequence

What I need to do to generate a sequence of non-repeating integers within a given range that meets the specific criteria that I have?
Here are the criteria:
Use only the numbers between 1 and MAX (let's say 9).
Numbers cannot repeat within the sequence except:
2a. Two of the first 5 numbers from the sequence must be repeated.
2b. These two numbers must be repeated at random points within the last 5 places in the final sequence (the last 5 includes the repeats).
For example:
SET: 1,2,3,4,5,6,7,8,9
Random Sequence (with repeats):
2,4,6,9,3,1,5,2,8,7,3
r, , , ,r, , ,x, , ,x
Here I have indicated the numbers that were randomly selected to be repeated (out of the first 5 in the random sequence) with an r and the insertion points where they were randomly placed (into the last 5 of the final sequence) with an x.
Any help in figuring this out is much appreciated. Actual use will be a bit more complicated than this, but I know what I will need to do once I can get this far.
Edit
To clarify a little more, I have 1-20, and I need a 22 digit random sequence. Every number must be used, two will be used twice as discussed in my original post. I chose 10 above to simplify a little. I should be able to adapt the logic you've all given.
I assume when you say "non-repeating" you mean "distinct" (unique) as opposed to "eventually becomes periodic" (as in "the digits of pi do not repeat")
Generate n distinct integers in your range.
Pick two from the first 5. Call these a and b.
Remove the last 3 from the list.
Insert a at position 0, 1, 2, or 3 in the sublist.
Insert b at position 0, 1, 2, 3, or 4 in the sublist.
Add the sublist back to the end of the list.
Removal of the sublist is not necessary but makes it easier to conceptualize.
Not obvious what to do if n+2 is less than 10. In particular, this algorithm may crash for n < 5 and return the wrong result for n=7.
If I understand you correctly, you have 1 to N random numbers that must be used in a 10-set permutation with some specific criteria about repeats. In php, I suggest this (not counting php-internals) O(n) solution:
//Generate a full list of keys
$source = range(1, MAX);
//NOTE: if MAX < 10, you must pad the array
//Get a random group of 10 of the keys
$input = array_rand(array_flip($source), 10);
//Shuffle (can be done later as well; this is the randomization).
//array_rand() does not change order.
shuffle($input);
//Select the first of 5 that must be repeated in the last 5
$one = rand(0, 4);
$onev = $input[$one];
//Remove this array key to prevent collisions with the second of 5
$input = array_diff($input, array($onev));
//Select a random index in the last 5 to be replaced with $one
$rep = rand(5, 9);
$repv = $input[$rep];
//Remove this array key to prevent collisions with the other to-be-replaced
$input = array_diff($input, array($repv));
//Acquire the new keys list of input now that two elements have been removed
$keys = array_slice(array_keys($input), 0, 3);
//Select the second-of-5 to replace in the last 5. No worry of collision now.
$two = array_rand($keys, 1);
$two = $keys[$two];
//Select the second from the last-of-5 to be replaced by $two
//No worry of collision because the other index is removed.
$keys = array_slice(array_keys($input), 4, 8);
$rept = array_rand($keys, 1);
$rept = $keys[$rept];
//Replace one of the last-of-five with one of the first-of-five
$input[$rept] = $input[$two];
//Restore removed keys as well as perform replacement of other last-of-five
$input[$one] = $onev;
$input[$rep] = $onev;
//re-randomize based on shuffle
ksort($input);
No loops, no conditionals.
A word of warning on this solution. I wouldn't use it for a large set of numbers. If I were doing this same solution for a much larger set, I would use array_splice to drop chosen members from the array. As you get a much larger space, finding an unused number in your range becomes quite expensive, and demands a better solution than the brute force method below.
This will build half of your target set. You will call it twice, once for each half.
function build_half($min, $max, $num_elements, $arr = array() ){
while( count($arr) <= $num_elements)
{
$candidate = rand($min, $max);
if( !in_array($candidate, $arr))
{
array_push($arr, $candidate);
}
}
return $arr;
}
This will grab $this_many elements from the array.
function random_grab($arr, $this_many){ // don't try this on the subway
$nums_to_repeat = array();
// catch some edge cases...
if( $this_many > count($arr) )
{
return FALSE;
}
else if( $this_many == count($arr) )
{
return shuffle($arr);
}
while( count($nums_to_repeat) <= $this_many)
{
$rand_key = rand(0, count($arr) - 1);
if( ! in_array($arr[$rand_key], $nums_to_repeat))
{
array_push($nums_to_repeat, $arr[$rand_key]);
}
}
return $nums_to_repeat;
}
This is a fairly specialized case, but could be made more general by allowing the offset floor and ceiling to be passed in as parameters. For your problem they would be 5 and 9, so we just derive them directly.
function random_insert_2nd_half($target, $source){
$offsets_consumed = array();
$num_elements = count($target);
while( count($source) > 0 )
{
$offset = rand( ($num_elements/2), $num_elements - 1);
if( ! in_array( $offset, $offsets_consumed)
{
$arr[$offset] = array_pop($nums_to_repeat);
}
}
}
Ok so after having done all that, let's put it to work.
// Generate the first half of the array
$my_array = $repeated_nums = array();
$my_array = build_half(1, 10, 5);
// then grab the 2 random numbers from that first half.
$repeated_nums = random_grab($my_array, 2);
// So now we have our random numbers and can build the 2nd half of the array.
// we'll just repeat the call to the first function.
$my_array = build_half(1, 10, 5, $my_array);
// Then swap out two of the values in the second half.
$my_array = random_insert_2nd_half($my_array, $repeated_nums);
// at this point $my_array should match what you are looking for.
Hope this gets you on your way:
$max = 20; // max value
$repeats = 2; // numbers to be repeated
$nums = range(1, $max);
shuffle($nums);
$halfPoint = ceil($max / 2);
$firstHalf = array_slice($nums, 0, $halfPoint);
$repeaters = array_intersect_key($firstHalf, array_flip(array_rand($firstHalf, $repeats)));
$secondHalf = array_merge(array_slice($nums, $halfPoint), $repeaters);
shuffle($secondHalf);
$result = array_merge($firstHalf, $secondHalf);
var_dump(join(',', $result));
To generate distinct numbers within a range you can use something like this:
$arr_num = array();
while(count($arr_num)<=7)
{
$num = rand(1, 9);
if (!in_array($num, $arr_num))
{
$arr_num[] = $num;
}
}
$arr_num now has 8 distinct elements. Pick five elements of the array:
for ($i=0; $i<=4; $i+=1)
{
$new_arr[$i] = $arr_num[$i];
}
Now pick two numbers from $new_arr numbers:
$r1 = array_rand($new_arr);
$r2 = array_rand($new_arr);
Now you can insert these numbers into the original array at two of the last random positions. Hope it helped!
$max = 15;
$array = array(1, $max);
for($x = 1; $x <= $max; $x++)
{ $array[$x] = rand(1, $max); }
$firstDup = $array[rand(1,5)];
$secondDup = $firstDup;
do { $firstDup = $array[rand(1,5)];
} while($firstDup == $secondDup);
do { $array[rand($max-5,$max)] = $firstDup;
} while(!in_array($firstDup,array_slice($array,$max-5,5)));
do { $array[rand($max-5,$max)] = $secondDup;
} while(!in_array($secondDup,array_slice($array,$max-5,5)));

Create numbers within an array that add up to a set amount

I'm fairly new to PHP - programming in general. So basically what I need to accomplish is, create an array of x amount of numbers (created randomly) whose value add up to n:
Let's say, I have to create 4 numbers that add up to 30. I just need the first random dataset. The 4 and 30 here are variables which will be set by the user.
Essentially something like
x = amount of numbers;
n = sum of all x's combined;
// create x random numbers which all add up to n;
$row = array(5, 7, 10, 8) // these add up to 30
Also, no duplicates are allowed and all numbers have to be positive integers.
I need the values within an array. I have been messing around with it sometime, however, my knowledge is fairly limited. Any help will be greatly appreciated.
First off, this is a really cool problem. I'm almost sure that my approach doesn't even distribute the numbers perfectly, but it should be better than some of the other approaches here.
I decided to build the array from the lowest number up (and shuffle them at the end). This allows me to always choose a random range that will allows yield valid results. Since the numbers must always be increasing, I solved for the highest possible number that ensures that a valid solution still exists (ie, if n=4 and max=31, if the first number was picked to be 7, then it wouldn't be possible to pick numbers greater than 7 such that the sum of 4 numbers would be equal to 31).
$n = 4;
$max = 31;
$array = array();
$current_min = 1;
while( $n > 1 ) {
//solve for the highest possible number that would allow for $n many random numbers
$current_max = floor( ($max/$n) - (($n-1)/2) );
if( $current_max < $current_min ) throw new Exception( "Can't use combination" );
$new_rand = rand( $current_min, $current_max ); //get a new rand
$max -= $new_rand; //drop the max
$current_min = $new_rand + 1; //bump up the new min
$n--; //drop the n
$array[] = $new_rand; //add rand to array
}
$array[] = $max; //we know what the last element must be
shuffle( $array );
EDIT: For large values of $n you'll end up with a lot of grouped values towards the end of the array, since there is a good chance you will get a random value near the max value forcing the rest to be very close together. A possible fix is to have a weighted rand, but that's beyond me.
I'm not sure whether I understood you correctly, but try this:
$n = 4;
$max = 30;
$array = array();
do {
$random = mt_rand(0, $max);
if (!in_array($random, $array)) {
$array[] = $random;
$n--;
}
} while (n > 0);
sorry i missed 'no duplicates' too
-so need to tack on a 'deduplicator' ...i put it in the other question
To generate a series of random numbers with a fixed sum:
make a series of random numbers (of largest practical magnitude to hide granularity...)
calculate their sum
multiply each in series by desiredsum/sum
(basicaly to scale a random series to its new size)
Then there is rounding error to adjust for:
recalculate sum and its difference
from desired sum
add the sumdiff to a random element
in series if it doesnt result in a
negative, if it does loop to another
random element until fine.
to be ultratight instead add or
subtract 1 bit to random elements
until sumdiff=0
Some non-randomness resulting from doing it like this is if the magnitude of the source randoms is too small causing granularity in the result.
I dont have php, but here's a shot -
$n = ; //size of array
$targsum = ; //target sum
$ceiling = 0x3fff; //biggish number for rands
$sizedrands = array();
$firstsum=0;
$finsum=0;
//make rands, sum size
for( $count=$n; $count>0; $count--)
{ $arand=rand( 0, $ceiling );
$sizedrands($count)=$arand;
$firstsum+=$arand; }
//resize, sum resize
for( $count=$n; $count>0; $count--)
{ $sizedrands($count)=($sizedrands($count)*$targsum)/$firstsum;
$finsum+=$sizedrands($count);
}
//redistribute parts of rounding error randomly until done
$roundup=$targsum-$finsum;
$rounder=1; if($roundup<0){ $rounder=-1; }
while( $roundup!=0 )
{ $arand=rand( 0, $n );
if( ($rounder+$sizedrands($arand) ) > 0 )
{ $sizedrands($arand)+=$rounder;
$roundup-=$rounder; }
}
Hope this will help you more....
Approch-1
$aRandomarray = array();
for($i=0;$i<100;$i++)
{
$iRandomValue = mt_rand(1000, 999);
if (!in_array($iRandomValue , $aRandomarray)) {
$aRandomarray[$i] = $iRandomValue;
}
}
Approch-2
$aRandomarray = array();
for($i=0;$i<100;$i++)
{
$iRandomValue = mt_rand(100, 999);
$sRandom .= $iRandomValue;
}
array_push($aRandomarray, $sRandom);

Categories