PHP rand() exclude certain numbers - php

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;

Related

Check in what range is a number

I have a random number from 0 to 100 (x) and different ranges. How to check in which range the number is?
Example:
Given
Case1 (0 - 40),
Case2 (40 - 60),
Case3 (60 - 75),
Case4 (75 - 85),
Case5 (85 - 100)
and x = 50, the result should be Case2.
Here's one example on how to check which range a given number belongs to. Define an associative ranges array, where cases are keys and bottom limits are key values. Then loop the array through and compare the given number to the limits. If the number equals or is bigger than the limit, return the limit's case.
$range = in_which_range( rand(0,100) );
function in_which_range( int $number ) {
foreach ( ranges() as $case => $bottom_limit ) {
if ( $number >= $bottom_limit ) {
return $case;
}
}
}
function ranges() {
return array(
'case5' => 85,
'case4' => 75,
'case3' => 60,
'case2' => 40,
'case1' => 0,
);
}
So, you have 5 ranges:
Range #0: 0-40
Range #1: 41-60 (note that this range starts from 41, not from 40; otherwise you will get intersecting ranges that's probably not what you originally wanted)
Range #2: 61-75
Range #3: 76-85
Range #4: 86-100
You can store your ranges in a single array, just put there all range borders sorted.
$ranges = [0, 40, 60, 75, 85, 100];
Note that you have 5 ranges, but 6 elements in array - that's important. Now you can easily check for each range:
$findRange = function(int $x, int ...$ranges): ?int
{
for ($i = 0; $i < count($ranges) - 1; $i++) {
$start = 0 == $i ? $ranges[$i] : $ranges[$i] + 1;
$end = $ranges[$i + 1];
if ($x >= $start && $x <= $end) {
return $i;
}
}
return null;
};
foreach ([0, 41, 86, 101] as $x) {
$range = $findRange($x, ...$ranges);
$message = null === $range
? "Value {$x} is not in the range"
: "Value {$x} is in range #{$range}";
echo $message, PHP_EOL;
}
This will output:
Value 0 is in range #0
Value 41 is in range #1
Value 86 is in range #4
Value 101 is not in the range
Here is the working demo.

Fill an array with random numbers while obeying designated sum, count, and number boundaries

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]

How to generate random numbers to produce a non-standard distributionin PHP

I've searched through a number of similar questions, but unfortunately I haven't been able to find an answer to this problem. I hope someone can point me in the right direction.
I need to come up with a PHP function which will produce a random number within a set range and mean. The range, in my case, will always be 1 to 100. The mean could be anything within the range.
For example...
r = f(x)
where...
r = the resulting random number
x = the mean
...running this function in a loop should produce random values where the average of the resulting values should be very close to x. (The more times we loop the closer we get to x)
Running the function in a loop, assuming x = 10, should produce a curve similar to this:
+
+ +
+ +
+ +
+ +
Where the curve starts at 1, peeks at 10, and ends at 100.
Unfortunately, I'm not well versed in statistics. Perhaps someone can help me word this problem correctly to find a solution?
interesting question. I'll sum it up:
We need a funcion f(x)
f returns an integer
if we run f a million times the average of the integer is x(or very close at least)
I am sure there are several approaches, but this uses the binomial distribution: http://en.wikipedia.org/wiki/Binomial_distribution
Here is the code:
function f($x){
$min = 0;
$max = 100;
$curve = 1.1;
$mean = $x;
$precision = 5; //higher is more precise but slower
$dist = array();
$lastval = $precision;
$belowsize = $mean-$min;
$abovesize = $max-$mean;
$belowfactor = pow(pow($curve,50),1/$belowsize);
$left = 0;
for($i = $min; $i< $mean; $i++){
$dist[$i] = round($lastval*$belowfactor);
$lastval = $lastval*$belowfactor;
$left += $dist[$i];
}
$dist[$mean] = round($lastval*$belowfactor);
$abovefactor = pow($left,1/$abovesize);
for($i = $mean+1; $i <= $max; $i++){
$dist[$i] = round($left-$left/$abovefactor);
$left = $left/$abovefactor;
}
$map = array();
foreach ($dist as $int => $quantity) {
for ($x = 0; $x < $quantity; $x++) {
$map[] = $int;
}
}
shuffle($map);
return current($map);
}
You can test it out like this(worked for me):
$results = array();
for($i = 0;$i<100;$i++){
$results[] = f(20);
}
$average = array_sum($results) / count($results);
echo $average;
It gives a distribution curve that looks like this:
I'm not sure if I got what you mean, even if I didn't this is still a pretty neat snippet:
<?php
function array_avg($array) { // Returns the average (mean) of the numbers in an array
return array_sum($array)/count($array);
}
function randomFromMean($x, $min = 1, $max = 100, $leniency = 3) {
/*
$x The number that you want to get close to
$min The minimum number in the range
$max Self-explanatory
$leniency How far off of $x can the result be
*/
$res = [mt_rand($min,$max)];
while (true) {
$res_avg = array_avg($res);
if ($res_avg >= ($x - $leniency) && $res_avg <= ($x + $leniency)) {
return $res;
break;
}
else if ($res_avg > $x && $res_avg < $max) {
array_push($res,mt_rand($min, $x));
}
else if ($res_avg > $min && $res_avg < $x) {
array_push($res, mt_rand($x,$max));
}
}
}
$res = randomFromMean(22); // This function returns an array of random numbers that have a mean close to the first param.
?>
If you then var_dump($res), You get something like this:
array (size=4)
0 => int 18
1 => int 54
2 => int 22
3 => int 4
EDIT: Using a low value for $leniency (like 1 or 2) will result in huge arrays, since testing, I recommend a leniency of around 3.

Generate an array in PHP of random number not close to the X previous element

I want to generate in PHP an array of random numbers, but each number should not be the same as any of the X (for example 2 ) numbers bofore it and not even close to any of them by a define range (for example 5).
So for example:
I need numbers between 1 and 100
i've set my "range" to 5
the first two generated number are 20 and 50.
the third number will be a random number between 1 and 100, excluding all the numbers between 15 and 25, and between 45 and 55.
I can't figure out a function to achieve it. Ideally I want to call something like this:
getRandomNumbers( $min, $max, $previous, $range);
where $previous is the number of previous elements to take in consideration when generating the next one and $range is the "proximity" to those number where I don't want the next number to be.
I hope I explained in a decent way my request. :) Please, add a comment if you have any question about it.
I just came up with this:
function getRandomNumbers($min, $max, $previous, $range) {
static $generated = array();
$chunk = array_slice($generated, -$previous);
// Added this infinite loop check to save you some headache.
if (((($max - $min + 1) / (($range * 2) + 1)) + 1) <= $previous) {
die("Values set have the potential of running into an infinite loop. Min: $min, Max: $max, Previous: $previous, Range: $range");
}
while(true) {
$number = rand($min, $max);
$found = true;
foreach ($chunk as $value) {
if (in_array($number, range($value-$range, $value+$range))) {
$found = false;
}
}
if ($found) {
$generated[] = $number;
return $number;
}
}
}
Test it using this:
for ($i = 1; $i < 25; $i++) {
echo getRandomNumbers(1, 100, 5, 5) . "<br />";
}
PHPFiddle Link: http://phpfiddle.org/main/code/51ke-4qzs
Edit: Added a check to prevent a possible infinite loop. For example: if you set the following values:
$min = 1;
$max = 100;
$previous = 5;
$range = 12;
echo getRandomNumbers($min, $max, $previous, $range);
Then let's say, in a really unfortunate situation it would generate 13, 38, 63 and 88. So the 5th number cannot be anything between 1 and 25, 26 and 50, 51 and 75, 76 and 100. So it would result in an infinite loop. I've updated the PHPFiddle link as well.
getRandomNumbers( $previous, $range ) {
//I'm assuming that previous will be an array of your previous X that you don't want to be close to
$num = getRandomNumber() //However you are doing this now
foreach( $previous as $key => $value ) {
if ( ( $value - $range ) > $num && ( $value + $range ) < $num ) {
return getRandomNumbers($previous, $range);
}
}
//You need to also replace a value in previous
return num;
}

Iterate (loop) through complicated range of numbers using groups to generate Bracket Sheet

I'm trying to build an algorithm for processing bracket sheet of competitions. I need to go through a range of numbers. For each number there will be the athlete name. Numbers are assigned to athletes randomly but the number's pairing must always stay the same. There are two groups odd and even, i.e. A and B.
The only problem that I can't find the proper algorithm to iterate numbers the exact way as follows:
Group A:
--------
1
17
9
25
------
5
21
13
29
------
3
19
11
27
------
7
23
15
31
Group B:
--------
2
18
10
26
------
6
22
14
30
------
4
20
12
28
------
8
24
16
32
Could someone please help with advice or example of how to get the output above?
EDIT 1:
The example above is the bracket sheet for 32 athletes! Same logic must be applied if you use a sheet for 4,8,16,64 or 128 athletes!
EDIT 2:
Let's make it more clear with examples of the sheet for 4 athletes and then the sheet for 16 athletes.
The sheet for 4 athletes:
Group A:
--------
1
3
Group B:
--------
2
4
The sheet for 16 athletes:
Group A:
--------
1
9
5
13
------
3
11
7
15
Group B:
--------
2
10
6
14
------
4
12
8
16
EDIT 3:
The last part, is that I'm planning to have an array with athlete name and its status in it.
By status I mean that, if the athlete has been a champion previously (strong), then he/she gets 1 for status, if the athlete's previous achievements are not known or minimal (weak), then the status is 0. It's done that way, so we could separate strongest athletes into different groups and make sure that they will not fight against each other in the first fight but rather meet each other closer to the semi-final or final.
Example of PHP array:
$participants = array(
array("John", 0),
array("Gagan", 0),
array("Mike Tyson", 1),
array("Gair", 0),
array("Gale", 0),
array("Roy Johnes", 1),
array("Galip", 0),
array("Gallagher", 0),
array("Garett", 0),
array("Nikolai Valuev", 1),
array("Garner", 0),
array("Gary", 0),
array("Gelar", 0),
array("Gershom", 0),
array("Gilby", 0),
array("Gilford", 0)
);
From this example we see that those, who have status 1 must be in different groups, i.e. A and B. But we have only two groups of numbers odd and even and in this example, there are 3 strong athletes. Thus two of them will be at the same group. The final result must be, that those two strong athletes, that got in the same group, must not meet at the very first fight (it means that they will not be on the same pair of numbers and as far away from each other as possible, so they wouldn't meet on the second fight as well).
Then randomly, I'm planning to rearrange the array and send athletes to the bracket sheet - every time, with different numbers, every time, those that have a flag 1 go to different groups and/or never meet at the first fight and every time, athletes' names assigned to the same pair of numbers.
Considering the number of participants is always a power of 2, this piece of code should give you the order you're expecting.
function getOrder($numberOfParticipants) {
$order = array(1, 2);
for($i = 2; $i < $numberOfParticipants; $i <<= 1) {
$nextOrder = array();
foreach($order as $number) {
$nextOrder[] = $number;
$nextOrder[] = $number + $i;
}
$order = $nextOrder;
}
return $order; // which is for instance [1, 17, 9, 25, and so on...] with 32 as argument
}
About the way it works, let's take a look at what happens when doubling the number of participants.
Participants | Order
2 | 1 2
4 | 1 3=1+2 2 4=2+2
8 | 1 5=1+4 3 7=3+4 2 6=2+4 4 8=4+4
... |
N | 1 X Y Z ...
2N | 1 1+N X X+N Y Y+N Z Z+N ...
The algorithm I used is the exact same logic. I start with an array containing only [1, 2] and $i is actually the size of this array. Then I'm computing the next line until I reach the one with the right number of participants.
On a side note: $i <<= 1 does the same than $i *= 2. You can read documentation about bitwise operators for further explanations.
About strong athletes, as you want to keep as much randomness as possible, here is a solution (probably not optimal but that's what I first thought):
Make two arrays, one with strongs and one with weaks
If there are no strongs or a single one, just shuffle the whole array and go to 8.
If there are more strongs than weaks (dunno if it can happen in your case but better be safe than sorry), shuffle the strongs and put the last ones with weaks so both arrays are the same size
Otherwise, fill up the strongs with null elements so the array size is a power of 2 then shuffle it
Shuffle the weaks
Prepare as many groups as they are elements in the strongs array and put in each group one of the strongs (or none if you have a null element) and complete with as many weaks as needed
Shuffle each group
Return the participants, ordered the same way than previous function resulting array
And the corresponding code:
function splitStrongsAndWeaks($participants) {
$strongs = array();
$weaks = array();
foreach($participants as $participant) {
if($participant != null && $participant[1] == 1)
$strongs[] = $participant;
else
$weaks[] = $participant;
}
return array($strongs, $weaks);
}
function insertNullValues($elements, $totalNeeded)
{
$strongsNumber = count($elements);
if($strongsNumber == $totalNeeded)
return $elements;
if($strongsNumber == 1)
{
if(mt_rand(0, 1))
array_unshift($elements, null);
else
$elements[] = null;
return $elements;
}
if($strongsNumber & 1)
$half = ($strongsNumber >> 1) + mt_rand(0, 1);
else
$half = $strongsNumber >> 1;
return array_merge(insertNullValues(array_splice($elements, 0, $half), $totalNeeded >> 1), insertNullValues($elements, $totalNeeded >> 1));
}
function shuffleParticipants($participants, $totalNeeded) {
list($strongs, $weaks) = splitStrongsAndWeaks($participants);
// If there are only weaks or a single strong, just shuffle them
if(count($strongs) < 2) {
shuffle($participants);
$participants = insertNullValues($participants, $totalNeeded);
}
else {
shuffle($strongs);
// If there are more strongs, we need to put some with the weaks
if(count($strongs) > $totalNeeded / 2) {
list($strongs, $strongsToWeaks) = array_chunk($strongs, $totalNeeded / 2);
$weaks = array_merge($weaks, $strongToWeaks);
$neededGroups = $totalNeeded / 2;
}
// Else we need to make sure the number of groups will be a power of 2
else {
$neededGroups = 1 << ceil(log(count($strongs), 2));
if(count($strongs) < $neededGroups)
$strongs = insertNullValues($strongs, $neededGroups);
}
shuffle($weaks);
// Computing needed non null values in each group
$neededByGroup = $totalNeeded / $neededGroups;
$neededNonNull = insertNullValues(array_fill(0, count($participants), 1), $totalNeeded);
$neededNonNull = array_chunk($neededNonNull, $neededByGroup);
$neededNonNull = array_map('array_sum', $neededNonNull);
// Creating groups, putting 0 or 1 strong in each
$participants = array();
foreach($strongs as $strong) {
$group = array();
if($strong != null)
$group[] = $strong;
$nonNull = array_shift($neededNonNull);
while(count($group) < $nonNull)
$group[] = array_shift($weaks);
while(count($group) < $neededByGroup)
$group[] = null;
// Shuffling again each group so you can get for instance 1 -> weak, 17 -> strong
shuffle($group);
$participants[] = $group;
}
// Flattening to get a 1-dimension array
$participants = call_user_func_array('array_merge', $participants);
}
// Returned array contains participants ordered the same way as getOrder()
// (eg. with 32 participants, first will have number 1, second number 17 and so on...)
return $participants;
}
If you want the resulting array to have as indexes the number in the bracket, you can simply do:
$order = getOrder(count($participants));
$participants = array_combine($order, shuffleParticipants($participants, count($order)));
Okay, I finally managed to convert my Tcl code to PHP! I changed some things too:
<?php
// Function generating order participants will be placed in array
function getBracket($L) {
// List will hold insert sequence
$list = array();
// Bracket will hold final order of participants
$bracket = array();
// The algorithm to generate the insert sequence
for ($n = 1; $n <= $L; $n += 1) {
// If 'perfect' number, just put it (Perfect no.s: 2, 4, 8, 16, 32, etc)
if (substr(log($n)/log(2), -2) == ".0") {
$list[] = $n;
// If odd number, stuff...
} elseif ($n % 2 == 1) {
$list[] = $list[($n-1)/2];
// Else even number, stuff...
} else {
$list[] = $list[$n/2-1]+$n/2;
}
}
// Insert participant order as per insert sequence
for ($i = 1; $i <= sizeof($list); $i += 1) {
$id = $i-1;
array_splice($bracket, $list[$id], 0, $i);
}
return $bracket;
}
// Find number of participants over 'perfect' number if any
function cleanList($L) {
for ($d = 1; $L > $d; $d += 1) {
$sq = $L-pow(2,$d);
if($sq == 0) {break;}
if($sq < 0) {
$d = pow(2,$d-1);
$diff = $L-$d;
break;
}
}
return $diff;
}
$participants = array(
array(0, "John", 2),
array(1, "Gagan", 1),
array(2, "Mike Tyson", 1),
array(3, "Gair", 1),
array(4, "Gale", 0),
array(5, "Roy Johnes", 0),
array(6, "Galip", 0),
array(7, "Gallagher", 0),
array(8, "Garett", 0),
array(9, "Nikolai Valuev", 0),
array(10, "Garner", 1),
array(11, "Gary", 0),
array(12, "Gelar", 0),
array(13, "Gershom", 1),
array(14, "Gilby", 0),
array(15, "Gilford", 1),
array(16, "Arianna", 0)
);
// Extract strength of participant
foreach ($participants as $array) {
$finorder[] = $array[2];
}
// Sort by strength, strongest first
array_multisort($finorder,SORT_DESC,$participants);
$order = array();
$outside = array();
// Remove participants above 'perfect' number
$remove = cleanList(sizeof($participants));
for ($r = 1; $r <= $remove; $r += 1) {
$removed = array_shift($participants);
$outside[] = $removed;
}
// Get corresponding bracket
$res = getBracket(sizeof($participants));
foreach ($res as $n) {
$order[] = $n;
}
// Align bracket results with participant list
array_multisort($order, $participants);
$participants = array_combine($res, $participants);
echo "The final arrangement of participants\n";
print_r($participants);
print_r($outside);
?>
Codepad demo
To get the logic for the order of insertion of elements, I used this pattern.
Also, since I'm not too familiar with PHP, there might be ways to make some things shorter, but oh well, as long as it works ^^
EDIT: Fixed an issue with first participant sorting and added new ticket numbers. For results without old ticket numbers, see here.
EDIT2: Managed to move keys into arrays; see here.
EDIT3: I thought that 'extra' participants should go outside the bracket. If you want null instead in the bracket, you can use this.
EDIT4: Somehow, PHP versions on codepad broke some stuff... fixing it below and removing initial index...:
<?php
// Function generating order participants will be placed in array
function getBracket($L) {
// List will hold insert sequence
$list = array();
// Bracket will hold final order of participants
$bracket = array();
// The algorithm to generate the insert sequence
for ($n = 1; $n <= $L; $n += 1) {
// If 'perfect' number, just put it (Perfect no.s: 2, 4, 8, 16, 32, etc)
if (int(log($n)/log(2)) || $n == 1) {
$list[] = $n;
// If odd number, stuff...
} elseif ($n % 2 == 1) {
$list[] = $list[($n-1)/2];
// Else even number, stuff...
} else {
$list[] = $list[$n/2-1]+$n/2;
}
}
// Insert participant order as per insert sequence
for ($i = 1; $i <= sizeof($list); $i += 1) {
$id = $list[$i-1]-1;
array_splice($bracket, $id, 0, $i);
}
return $bracket;
}
// Find number of participants over 'perfect' number if any
function cleanList($L) {
for ($d = 1; $L > $d; $d += 1) {
$diff = $L-pow(2,$d);
if($diff == 0) {break;}
if($diff < 0) {
$diff = pow(2,$d)-$L;
break;
}
}
return $diff;
}
$participants = array(
array("John", 2),
array("Gagan", 1),
array("Mike Tyson", 1),
array("Gair", 1),
array("Gale", 0),
array("Roy Johnes", 0),
array("Galip", 0),
array("Gallagher", 0),
array("Garett", 0),
array("Nikolai Valuev", 0),
array("Garner", 1),
);
// Extract strength of participant
foreach ($participants as $array) {
$finorder[] = $array[2];
}
// Sort by strength, strongest first
array_multisort($finorder,SORT_DESC,$participants);
$order = array();
// Add participants until 'perfect' number
$add = cleanList(sizeof($participants));
for ($r = 1; $r <= $add; $r += 1) {
$participants[] = null;
}
// Get corresponding bracket
$res = getBracket(sizeof($participants));
// Align bracket results with participant list
foreach ($res as $n) {
$order[] = $n;
}
array_multisort($order, $participants);
$participants = array_combine($res, $participants);
echo "The final arrangement of participants\n";
print_r($participants);
?>
ideone
viper-7
This sketchy code might be what you want:
<?php
class Pair
{
public $a;
public $b;
function __construct($a, $b) {
if(($a & 1) != ($b & 1))
throw new Exception('Invalid Pair');
$this->a = $a;
$this->b = $b;
}
}
class Competition
{
public $odd_group = array();
public $even_group = array();
function __construct($order) {
$n = 1 << $order;
$odd = array();
$even = array();
for($i = 0; $i < $n; $i += 4) {
$odd[] = $i + 1;
$odd[] = $i + 3;
$even[] = $i + 2;
$even[] = $i + 4;
}
shuffle($odd);
shuffle($even);
for($i = 0; $i < count($odd); $i += 2) {
$this->odd_group[] = new Pair($odd[$i], $odd[$i+1]);
$this->even_group[] = new Pair($even[$i], $even[$i+1]);
}
echo "Odd\n";
for($i = 0; $i < count($this->odd_group); ++$i) {
$pair = $this->odd_group[$i];
echo "{$pair->a} vs. {$pair->b}\n";
}
echo "Even\n";
for($i = 0; $i < count($this->even_group); ++$i) {
$pair = $this->even_group[$i];
echo "{$pair->a} vs. {$pair->b}\n";
}
}
}
new Competition(5);
?>

Categories