I would like to draw a random number from the interval 1,49 but I would like to add a number as an exception ( let's say 44 ) , I cannot use round(rand(1,49)) .So I decided to make an array of 49 numbers ( 1-49) , unset[$aray[44]] and apply array_rand
Now I want to draw a number from the interval [$left,49] , how can I do that using the same array that I used before ?The array now misses value 44.
The function pick takes an array as an argument with all the numbers you have already picked. It will then pick a number between the start and the end that IS NOT in that array. It will add this number into that array and return the number.
function pick(&$picked, $start, $end) {
sort($picked);
if($start > $end - count($picked)) {
return false;
}
$pick = rand($start, $end - count($picked));
foreach($picked as $p) {
if($pick >= $p) {
$pick += 1;
} else {
break;
}
}
$picked[] = $pick;
return $pick;
}
This function will efficiently get a random number that is not in the array AND WILL NEVER INFINITELY RECURSE!
To use this like you want:
$array = array(44); // you have picked 44 for example
$num = pick($array, 1, 49); // get a random number between 1 and 49 that is not in $array
// $num will be a number between 1 and 49 that is not in $arrays
How the function works
Say you are getting a number between 1 and 10. And you have picked two numbers (e.g. 2 and 6). This will pick a number between 1 and (10 minus 2) using rand: rand(1, 8).
It will then go through each number that has been picked and check if the number is bigger.
For Example:
If rand(1, 8) returns 2.
It looks at 2 (it is >= then 2 so it increments and becomes 3)
It looks at 6 (it is not >= then 6 so it exits the loop)
The result is: 3
If rand(1, 8) returns 3
It looks at 2 (it is >= then 2 so it increments and becomes 4)
It looks at 6 (it is not >= then 6 so it exits the loop)
The result is 4
If rand(1, 8) returns 6
It looks at 2 (it is >= then 2 so it increments and becomes 7)
It looks at 6 (it is >= then 6 so it increments and becomes 8)
The result is: 8
If rand(1, 8) returns 8
It looks at 2 (it is >= then 2 so it increments and becomes 9)
It looks at 6 (it is >= then 6 so it increments and becomes 10)
The result is: 10
Therefore a random number between 1 and 10 is returned and it will not be 2 or 6.
I implemented this a long time ago to randomly place mines in a 2-dimensional array (because I wanted random mines, but I wanted to guarantee the number of mines on the field to be a certain number)
Why not just check your exceptions:
function getRand($min, $max) {
$exceptions = array(44, 23);
do {
$rand = mt_rand($min, $max);
} while (in_array($rand, $exceptions));
return $rand;
}
Note that this could result in an infinite loop if you provide a min and max that force mt_rand to return an exception character. So if you call getRand(44,44);, while meaningless, will result in an infinite loop... (And you can avoid the infinite loop with a bit of logic in the function (checking that there is at least one non-exception value in the range $min to $max)...
The other option, would be to build the array with a loop:
function getRand($min, $max) {
$actualMin = min($min, $max);
$actualMax = max($min, $max);
$values = array();
$exceptions = array(44, 23);
for ($i = $actualMin; $i <= $actualMax; $i++) {
if (in_array($i, $exceptions)) {
continue;
}
$values[] = $i;
}
return $values[array_rand($values)];
}
The simplest solution would be to just search for a random number from min to max - number of exceptions. Then just add 1 to the result for each exception lower than the result.
function getRandom($min, $max)
{
$exceptions = array(23, 44); // Keep them sorted or you have to do sort() every time
$random = rand($min, $max - count($exceptions));
foreach ($exceptions as $ex)
{
if ($ex > $random) break;
++$random;
}
return $random;
}
Runtime should be O(1+n) with n being the number of exceptions lower than the result.
Related
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
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
I have to fill an array with random numbers to satisfy a few conditions:
The number of elements in the result array must match the designated number.
The sum of the numbers in the result array must equal the designated number.
Random numbers must be selected between designated lower and upper bounds.
For example:
Sum of the array: 130
Total array elements: 3
Random integers' lower bound: 23
Random integers' upper bound: 70
Possible result:
array(23, 70, 37)
What to do now? How to split/divide my number?
I started with this (pseudo code):
i=0;
while(sum(number) > 0 and i < arraykeys){
x = randomize(from, to)
number = number - x
myarray[i] = x
i++
}
This should work for you:
Code explanation
Workability
The first thing we need to check is, if it is possible to build the goal out of numbers from the scope:
if(checkWorkability($result, $goal, $amountOfElementsLeft, $scope))
Means it just uses the highest values possible and looks if it is bigger than the goal.
While loop
In the while loop we need to check if we still have elements left which we can use:
while($amountOfElementsLeft > 0)
Scope adjustment
Every iteration we need to check if we need to adjust the scope, so that at the end we will be able to build the goal.
This means if the current sum of numbers + the highest possible number is bigger than the goal, we need to make the max value of the scope smaller.
Also on the opposite side we need to make the min value of the scope bigger, when we can't reach our goal anymore.
Code
<?php
$goal = 130;
$amountOfElementsLeft = 3;
$scope = [23, 70];
$result= [];
function adjustScope(array $result, $goal, $amountOfElementsLeft, $scope) {
$newScope = $scope;
if($amountOfElementsLeft == 1) {
$leftOver = $goal - array_sum($result);
return [$leftOver, $leftOver];
}
if((($goal - (array_sum($result) + $scope[1])) / ($amountOfElementsLeft - 1)) < $scope[0])
$newScope[1] = (int) ($goal - array_sum($result)) / ($scope[0] * ($amountOfElementsLeft - 1));
elseif(($adjustTop = $goal - array_sum($result)) < $scope[1])
$newScope[1] = $adjustTop;
if(($adjustBottom = $goal - (array_sum($result) + $scope[0] + (($amountOfElementsLeft - 1) * $scope[1]))) < $goal && $adjustBottom > 0)
$newScope[0] = $scope[0] + $adjustBottom;
return $newScope;
}
function checkWorkability(array $result, $goal, $amountOfElementsLeft, $scope) {
if(array_sum($result) + $amountOfElementsLeft * $scope[1] >= $goal)
return TRUE;
return FALSE;
}
if(checkWorkability($result, $goal, $amountOfElementsLeft, $scope)) {
while($amountOfElementsLeft > 0) {
$scope = adjustScope($result, $goal, $amountOfElementsLeft, $scope);
$result[] = rand($scope[0], $scope[1]);
$amountOfElementsLeft--;
}
}
print_r($result);
echo array_sum($result);
?>
possible outputs:
Array
(
[0] => 58
[1] => 30
[2] => 42
) -> 130
Array
(
[0] => 35
[1] => 54
[2] => 41
) -> 130
Array
(
[0] => 52
[1] => 51
[2] => 27
) -> 130
I've written a custom function for portability and to meaningfully implement some guard conditions which throw exceptions when incoming parameters make the desired result impossible.
Loop one less than $count times -- this is because the final element in the returned array is determined by the difference between the desired total and the sum of the randomly acquired values.
Adjust the lower and upper bounds of the $scope array (if required) to ensure a successfully populated return array.
Get a random integer, push it into the return array, then subtract it from the $total.
When the looped processes are finished, push the remaining $total value as the final element in the return array.
Code: (Demo)
function getRandWithStipulations(int $total, int $count, array $scope): array
{
if ($scope[0] > $scope[1]) {
throw new Exception('Argument 3 (\$scope) is expected to contain a minimum integer then a maximum integer.');
}
if ($scope[0] * $count > $total) {
throw new Exception('Arguments 2 (\$count) and 3 (\$scope) can only exceed argument 1 (\$total).');
}
if ($scope[1] * $count < $total) {
throw new Exception('Arguments 2 (\$count) and 3 (\$scope) cannot reach argument 1 (\$total).');
}
$result = [];
for ($x = 1; $x < $count; ++$x) { // count - 1 iterations
$scope[0] = max($scope[0], $total - ($scope[1] * ($count - $x)));
$scope[1] = min($scope[1], $total - ($scope[0] * ($count - $x)));
$rand = rand(...$scope);
$result[] = $rand;
$total -= $rand;
}
$result[] = $total;
return $result;
}
try {
var_export(
getRandWithStipulations(
130,
3,
[23, 70]
)
);
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage();
}
A few random results:
[60, 34, 36]
[23, 59, 48]
[67, 36, 27]
[47, 23, 60]
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);
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.