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.
Related
Okay this is just something me and my coworker are playing with.
We know PHP has it's own PI function but this came forth out of a theory and curiosity.
So we were wondering if and how PHP was able to calculate pi.
Formule of pi = π= 4/1 - 4/3 + 4/5 - 4/7 + 4/9...
Here is what we did:
$theValue = 100;// the max
for ($i=1; $i<$theValue; $i++){
if ($i % 2 == 1){
$iWaardes[] = 4 / $i; // divide 4 by all uneven numbers and store them in an array
}
}
// Use the array's $keys as incrementing numbers to calculate the $values.
for ($a=0, $b=1, $c=2; $a<$theValue; $a+=3, $b+=3, $c+=3 ){
echo ($iWaardes[$a] - $iWaardes[$b] + $iWaardes[$c]).'<br>';
}
So now we have a loop that calculated the first series of 4/1 - 4/3 + 4/5 but it stops after that and starts over with the following 3 sequences.
How can we make it run the entire $theValue and calculate the whole series?
Please keep in mind that this is nothing serious and just a fun experiment for us.
You're overthinking this. Just use the modulo to decide if you want to add or subtract and do it in place.
$theValue = 100;// the max
$pi = 0;
for ($i=1; $i<$theValue; $i++){
if ($i % 2 == 1){
$pi += 4.0 / ($i * 2 - 1);
} else {
$pi -= 4.0 / ($i * 2 - 1);
}
}
Just use one loop. Have a $bottom variable that you add 2 on each iteration, divide by it, and add it/subtract it depending on the modulo:
$theValue = 10000; // the max
$bottom = 1;
$pi = 0;
for ($i = 1; $i < $theValue; $i++) {
if ($i % 2 == 1) {
$pi += 4 / $bottom;
} else {
$pi -= 4 / $bottom;
}
$bottom += 2;
}
var_dump($pi); // 3.14169266359
Demo
What's wrong with your code (other than not dividing by the appropriate number) is the second loop. You're for some reason printing out the stored numbers 3 by 3. This, until $a, that increases by 3, is lower than $theValue which is much higher. So, for example, if $theValue is 10, you only need 2 loops before you start getting out of bound errors.
pi() Returns an approximation of pi. The returned float has a precision based on the precision directive in php.ini, which defaults to 14. Also, you can use the M_PI constant which yields identical results to pi()
source
Using PHP we can also calculate Pi, albeit very slowly.
$pi = 4; $top = 4; $bot = 3; $minus = TRUE;
$accuracy = 1000000;
for($i = 0; $i < $accuracy; $i++)
{
$pi += ( $minus ? -($top/$bot) : ($top/$bot) );
$minus = ( $minus ? FALSE : TRUE);
$bot += 2;
}
print "Pi ~=: " . $pi;
This method of calculating Pi is slow, but it is easy to read code.
You can read more about this method here:
http://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80
If you increase the $accuracy variable, Pi will be calculated more and more accurately. Depending on how fast your web server is, you can calculate the first 6 digits of Pi fairly quickly.
The time it takes to calculate each succeeding number goes up exponentially however. To calculate 20 digits of Pi using this method method could take years.
I tried to do something like that:
$total = 0;
for ($j = 0; $j < 1000; $j++) {
$x = $j / 1000;
$total += pow($x, 1500) * pow((1 - $x), 500);
}
$total is 0.
PHP can't work with too small float values. What can I do? Which libraries can I use?
The function
f(x) = x^1500 * (1-x)^500
has (logarithmic) derivative
f'(x)/f(x)=d/dx log(f(x))
= 1500/x - 500/(1-x)
which is zero for
x0 = 3/4
having the maximum value of
f(3/4) = 3^1500/2^4000 = exp(-1124.6702892376163)
= 10^(-488.4381005764309)
= 3.646694848749686e-489
Using that as reference value, one can now sum up
f(i/1000)/f(3/4)=exp(1500*log(i/1000)+500*log(1-i/1000)+1124.6702892376163)
giving a sum of 24.26257515625789 so that the desired result is
24.26257515625789*f(3/4)=8.847820783972776e-488
A practical way to compute such a sum would be to compute the list of logarithms (more python than PHP, look up the corresponding array operations)
logf = [ log(f(i/1000.0)) for i=1..999 ]
using the transformed logarithm of f, log(f(x))=1500*log(x)+500*log(1-x).
Then compute maxlogf = max(logf), extract the number N=floor(maxlogf/log(10)) of the decimal power and compute the sum as
sumfred = sum([ exp( logfx - N*log(10) ) for logfx in logf ])
so that the final result is sumfred*10^N.
My rand(0,1) php function returns me the 0 and 1 randomly when I call it.
Can I define something in php, so that it makes 30% numbers will be 0 and 70% numbers will be 1 for the random calls? Does php have any built in function for this?
Sure.
$rand = (float)rand()/(float)getrandmax();
if ($rand < 0.3)
$result = 0;
else
$result = 1;
You can deal with arbitrary results and weights, too.
$weights = array(0 => 0.3, 1 => 0.2, 2 => 0.5);
$rand = (float)rand()/(float)getrandmax();
foreach ($weights as $value => $weight) {
if ($rand < $weight) {
$result = $value;
break;
}
$rand -= $weight;
}
You can do something like this:
$rand = (rand(0,9) > 6 ? 1 : 0)
rand(0,9) will produce a random number between 0 and 9, and whenever that randomly generated number is greater than 6 (which should be nearly 70% time), it will give you 1 otherwise 0...
Obviously, it seems to be the easiest solution to me, but definitely, it wont give you 1 exactly 70% times, but should be quite near to do that, if done correctly.
But, I doubt that any solution based on rand will give you 1 exactly 70% times...
Generate a new random value between 1 and 100. If the value falls below 30, then use 0, and 1 otherwise:
$probability = rand(1, 100);
if ($probability < 30) {
echo 0;
} else {
echo 1;
}
To test this theory, consider the following loop:
$arr = array();
for ($i=0; $i < 10000; $i++) {
$rand = rand(0, 1);
$probability = rand(1, 100);
if ($probability < 30) {
$arr[] = 0;
} else {
$arr[] = 1;
}
}
$c = array_count_values($arr);
echo "0 = " . $c['0'] / 10000 * 100;
echo "1 = " . $c['1'] / 10000 * 100;
Output:
0 = 29.33
1 = 70.67
Create an array with 70% 1 and 30% 0s. Then random sort it. Then start picking numbers from the beginning of the array to the end :)
$num_array = array();
for($i = 0; $i < 3; $i++) $num_array[$i] = 0;
for($i = 0; $i < 7; $i++) $num_array[$i] = 1;
shuffle($num_array);
Pros:
You'll get exactly 30% 0 and 70% 1 for any such array.
Cons: Might take longer computation time than a rand() only solution to create the initial array.
I searched for an answer to my question and this was the topic I found.
But it didn't answered my question, so I had to figure it out myself, and I did :).
I figured out that maybe this will help someone else as well.
It's regarding what you asked, but for more usage.
Basically, I use it as a "power" calculator for a random generated item (let's say a weapon). The item has a "min power" and a "max power" value in the db. And I wanted to have 80% chances to have the "power" value closer to the lower 80% of the max possible power for the item, and 20% for the highest 20% possible max power (that are stored in the db).
So, to do this I did the following:
$min = 1; // this value is normally taken from the db
$max = 30; // this value is normally taken from the db
$total_possibilities = ($max - $min) + 1;
$rand = random_int(1, 100);
if ($rand <= 80) { // 80% chances
$new_max = $max - ($total_possibilities * 0.20); // remove 20% from the max value, so you can get a number only from the lowest 80%
$new_rand = random_int($min, $new_max);
} elseif ($rand <= 100) { // 20% chances
$new_min = $min + ($total_possibilities * 0.80); // add 80% for the min value, so you can get a number only from the highest 20%
$new_rand = random_int($new_min, $max);
}
echo $new_rand; // this will be the final item power
The only problem you can have, is if the initial $min and $max variables are the same (or obviously, if the $max is bigger than the $min). This will throw an error since the random works like this ($min, $max), not the other way around.
This code can be very easily changed to have more percentages for different purposes, instead of 80% and 20% to put 40%, 40% and 20% (or whatever you need). I think the code is pretty much easy to read and understand.
Sorry if this is not helpful, but I hope it is :).
It can't do any harm either way ;).
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.
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.