Expanding Round-robin tournament 1v1 to a 1v1v1v1 - php

I am trying to expand and improve the round-robin algorithm from 1v1 group to a 1v1v1v1 group(something like free for all). I've made the function itself to do the schedule, but when I tried to expand it, some teams repetead. For example, I have 16 teams and I want to have 5 rounds, team 1 appears 7 times in 5 rounds and team2 appears 3 times in 5 rounds. I need them to appear 5 times at most.I really can't understand how I can do it. Any advice is welcomed and links.
function make_schedule(array $teams, int $rounds = null, bool $shuffle = true, int $seed = null): array
{
$teamCount = count($teams);
if($teamCount < 4) {
return [];
}
//Account for odd number of teams by adding a bye
if($teamCount % 2 === 1) {
array_push($teams, null);
$teamCount += 1;
}
if($shuffle) {
//Seed shuffle with random_int for better randomness if seed is null
srand($seed ?? random_int(PHP_INT_MIN, PHP_INT_MAX));
shuffle($teams);
} elseif(!is_null($seed)) {
//Generate friendly notice that seed is set but shuffle is set to false
trigger_error('Seed parameter has no effect when shuffle parameter is set to false');
}
$quadTeamCount = $teamCount / 4;
if($rounds === null) {
$rounds = $teamCount - 1;
}
$schedule = [];
for($round = 1; $round <= $rounds; $round += 1) {
$matchupPrev = null;
foreach($teams as $key => $team) {
if($key >= $quadTeamCount ) {
break;
}
$keyCount = $key + $quadTeamCount;
$keyCount2 = $key + $quadTeamCount + 1;
$keyCount3 = $key + $quadTeamCount + 2;
$team1 = $team;
$team2 = $teams[$keyCount];
$team3 = $teams[$keyCount2];
$team4 = $teams[$keyCount3];
//echo "<pre>Round #{$round}: {$team1} - {$team2} - {$team3} - {$team4} == KeyCount: {$keyCount} == KeyCount2: {$keyCount2} == KeyCount3: {$keyCount3}</pre>";
//Home-away swapping
$matchup = $round % 2 === 0 ? [$team1, $team2, $team3, $team4 ] : [$team2, $team1, $team4, $team3];
$schedule[$round][] = $matchup ;
}
rotate($teams);
}
return $schedule;
}
Rotate function:
function rotate(array &$items)
{
$itemCount = count($items);
if($itemCount < 3) {
return;
}
$lastIndex = $itemCount - 1;
/**
* Though not technically part of the round-robin algorithm, odd-even
* factor differentiation included to have intuitive behavior for arrays
* with an odd number of elements
*/
$factor = (int) ($itemCount % 2 === 0 ? $itemCount / 2 : ($itemCount / 2) + 1);
$topRightIndex = $factor - 1;
$topRightItem = $items[$topRightIndex];
$bottomLeftIndex = $factor;
$bottomLeftItem = $items[$bottomLeftIndex];
for($i = $topRightIndex; $i > 0; $i -= 1) {
$items[$i] = $items[$i - 1];
}
for($i = $bottomLeftIndex; $i < $lastIndex; $i += 1) {
$items[$i] = $items[$i + 1];
}
$items[1] = $bottomLeftItem;
$items[$lastIndex] = $topRightItem;
}
For example:
If I set rounds to 5, every team play 5 matches.
Array example Screenshot
Dealing with the 5th round:
Well, after I thought for a bit, maybe there isn't a way for them to play without repeatence, but if it is lowered to minumum, like every team should play 5 times only - this means once a round. That's what I meant. And what I meant under 'they repeat' is that there are like: 16 teams, 5 rounds and some teams are going like 7 times for all these rounds and other teams are going 3 times for these 5 rounds. I want to avoid this and to make every team play 5 rounds at most.

Your foreach() with the selection of the other 3 teams is wrong. One of them have to make steps with a multiple of 4. If you don't, you will select the teams at the beginning more than one and don't select the teams at the end of the array at all. This will result in wrong team matchups like this (teams are letters here):
abcd
bcde
cdef
defg
And then your break; hits.
Instead it should look something like this:
for ($i=0; $i<4; $i++) {
$matchup = array();
for ($j=0; $j<4; $j++) {
$matchup[] = $teams[4*$i+$j];
}
$schedule[$round][] = $matchup ;
}
This way you get the following pairing (again, using letters as teams):
abcd
efgh
ijkl
mnop
This algorithm will split the team list in four groups:
abcd|efgh|ijkl|mnop
Keep in mind that depending on the shuffling of the $teams array for the next round you might get the same opponent twice.
adei|klnf|gjmc|pobh
Here the teams ad, kl and op will face again.

Related

PHP - Find the number of groups given some constraints

Given n = 3 dogs and m = 3 pairs of enemies, a = [1, 2, 3] and b = [3, 3, 1], dog 1 is the enemy of dog 3, and dog 3 is the enemy of dogs 1 and 2. Because 3 is an enemy of both 1 and 2, it must be in its own container. dogs 1 and 2 can be together or separately. There are 4 possible groups: {1, 2} ,{1}, {2}, {3}. Note that the intervals are along the original line of dogs numbered consecutively from 1 to n, i.e. [1, 2, 3] in this case. The dogs cannot be reordered and dogs cannot be skipped, e.g. {2, 1} and {1, 3} are invalid.
So given the following:
case #1:
n = 5
m = 2
a = (1,2)
b = (3,5)
Result is: Total of 11 groups can be formed.
case #2
n = 8
m = 4
a = (2,3,4,3)
b = (8,5,6,4)
Result is: Total of 18 groups can be formed.
Here's my code:
function countSubstrings($n, $a, $b) {
$tokenArr = array();
$x = 1;
while ($x <= $n){
$tokenArr[] = $x;
$x++;
}
$first = 0;
$last = $n - 1;
$outArr = array();
$pointer = 0;
/* generate groups left to right */
for ($i = $first; $i <= $last; $i++) {
$outArr[$pointer][] = $tokenArr[$i];
$tokenString = $tokenArr[$i];
$pointer++;
for ($j = $i + 1; $j <= $last; $j++) {
$tokenString .= $tokenArr[$j];
$outArr[$pointer] = str_split($tokenString);
$pointer++;
}
}
/* find the enemeies */
$intersects = array();
for($k = 0; $k < count($outArr); $k++){
if (count(array_intersect($outArr[$k], $a)) > 1 || count(array_intersect($outArr[$k], $b)) > 1) {
$intersects[] = $outArr[$k];
}
}
/* remove first and last items which are basically equal to $a and $b */
$intersects = array_slice($intersects, 1, -1);
/* remove the enemeies from generated groups */
foreach ($outArr as $keya => $valuea) {
if (in_array($valuea, $intersects)) {
unset($outArr[$keya]);
}
}
return count($outArr);
}
So far my code works in case: #1 but fails on #2.
The intersect logic seems to be incorrect to me as we have to check if the relationship formed by [a , b], for example, [1,2] exists in $outArr or not. Current check of count(array_intersect($outArr[$k], $a)) > 1 does not care about that. It rather checks if any element in $outArr[$k] is present in $a or not.
So, change the current logic from:
/* find the enemeies */
$intersects = array();
for($k = 0; $k < count($outArr); $k++){
if (count(array_intersect($outArr[$k], $a)) > 1 || count(array_intersect($outArr[$k], $b)) > 1) {
$intersects[] = $outArr[$k];
}
}
/* remove first and last items which are basically equal to $a and $b */
$intersects = array_slice($intersects, 1, -1);
to
$intersects = array();
foreach($a as $index => $val1){
$val2 = $b[$index];
foreach($outArr as $current_group){
if(in_array($val1,$current_group) && in_array($val2,$current_group)){ // check if both exist as they are enemies
$intersects[] = $current_group;
}
}
}
Demo: https://3v4l.org/Q2rnP
In the above code, we:
loop through all elements of $a and simultaneously with $b with the help of $index in foreach.
Check if for the current group in $outArr, whether both $a[$index](a.k.a $val1) and $b[$index](a.k.a $val2) exist in the group or not.
If both exist in current group, we put them under intersect as they are enemies. Your rest of the logic is correct.
Efficient Solution:
We have to exploit this line:
A group is defined as an interval (x, y) such that all dogs in the range from x to y form a group.
This means that we need to look at subarrays(as you correctly judged) instead of subsequences.
Now, we loop from 1 to N and if we find a number which has an enemy on the left, we can only form the next groups from that number + 1 onwards. Anything before them can't be included anyway since we are looking at subarrays.
For example, let's assume 5 is an enemy of 3 in a line of 1 to 5 and no other enemies are present. So, group formations would look like below.
Representation:
1 2 3 4 5
-1 -1 5 -1 3
|___|
|___|___|
|___|___|___|
|___|
|___|___|
|___|
|___| // the connection/group (4,5) remains and breaks everything before 4 since 3 is an enemy of 5 and we are looking for subarrays. So everything before 4 is disconnected anyway.
So, our next starting animal/dog to look from is 4.
For each enemy/animal, we maintain the nearest enemy on the left if present. If present, we update the next animal to look from for groups as proved above. In the below code, $prev_start is the variable that maintains the next animal to look from.
In order to get nearest enemy on the left for each animal, we preprocess the enemy details as follows:
Preprocessing:
$enemies = array_combine(range(1,$n),array_fill(0,$n,-1)); // nothing tricky, just generates an array filled with sequential numbers as keys and sets it's value as -1
foreach($a as $index => $enemy_1){
$enemy_2 = $b[$index];
if($enemy_1 < $enemy_2){
$enemies[$enemy_2] = max($enemies[$enemy_2],$enemy_1);
}else if($enemy_2 < $enemy_1){
$enemies[$enemy_1] = max($enemies[$enemy_1],$enemy_2);
}
}
Computation:
$prev_start = 1;
$count = 0;
for($i=1;$i<=$n;++$i){
if($enemies[$i] !== -1){
$prev_start = max($enemies[$i] + 1,$prev_start);
}
$count += ($i - $prev_start + 1);
}
Since we preprocessed enemy details, we update $prev_start accordingly from where we have to start counting for groups again.
$count += ($i - $prev_start + 1); simply counts the number of groups(subarrays) to consider for counting.
Time complexity: O(m + n) where m is number of pairs and n is the number of dogs/animals.
Space complexity: O(n) where n is the number of dogs/animals.
Full Code:
<?php
function countSubarrays($n, $a, $b) {
$enemies = array_combine(range(1,$n),array_fill(0,$n,-1)); // nothing tricky, just generates an array filled with sequential numbers as keys and sets it's value as -1
foreach($a as $index => $enemy_1){
$enemy_2 = $b[$index];
if($enemy_1 < $enemy_2){
$enemies[$enemy_2] = max($enemies[$enemy_2],$enemy_1);
}else if($enemy_2 < $enemy_1){
$enemies[$enemy_1] = max($enemies[$enemy_1],$enemy_2);
}
}
$prev_start = 1;
$count = 0;
for($i=1;$i<=$n;++$i){
if($enemies[$i] !== -1){
$prev_start = max($enemies[$i] + 1,$prev_start);
}
$count += ($i - $prev_start + 1);
}
return $count;
}
Demo: https://3v4l.org/1W26C

Check for poker straight

I have managed to create an algorithm to check the rank of a poker hand. It works 100% correctly, but it's very slow. I've been analysing the code, and the check straight function is one of the slowest parts of it.
So my question is, is there a better way of calculating whether a hand make a straight?
Here is some details:
7 cards, 2 from holder, 5 from board. A can be high or low.
Each card is assigned a value:
2 = 2
3 = 3
..
9 = 9
T = 10
J = 11
Q = 12
K = 13
A = 14
The script has an array of all 7 cards:
$cards = array(12,5,6,7,4,11,3);
So now I need to be able to sort this into an array where it:
discards duplicates
orders the card from lowest to highest
only returns 5 consecutive cards I.e. (3,4,5,6,7)
It needs to be fast; loops and iterations are very costly. This is what I currently use and when it tries to analyse say 15000 hands, it takes its toll on the script.
For the above, I used:
discard duplicates (use array_unique)
order cards from lowest to highest (use sort())
only return 5 consecutive cards (use a for loop to check the values of cards)
Does anyone have any examples of how I could improve on this? Maybe even in another language that I could perhaps look at and see how it's done?
Instead of working with array deduping and sorting, consider using a bitmask instead, and setting bits to 1 where the card value is set. A bitmask works like a Set datastructure and comes with additional advantages when it comes to detecting contiguous elements.
for ($i = 0; $i < count($cards); $i++) {
$card = $cards[$i];
// For each card value, set the bit
if ($card == 14) {
// If card is an ace, also set bit 1 for wheel
$cardBitmask |= 0x2;
}
$cardBitmask |= (1 << $card);
}
// To compare, you simply write a for loop checking for 5 consecutive bits
for($i = 10; $i > 0; $i--)
{
if ($cardBitmask & (0x1F << $i) == (0x1F << $i)) {
// Straight $i high was found!
}
}
Consider the Java implementation at this link. I've included it here:
public static boolean isStraight( Card[] h )
{
int i, testRank;
if ( h.length != 5 )
return(false);
sortByRank(h); // Sort the poker hand by the rank of each card
/* ===========================
Check if hand has an Ace
=========================== */
if ( h[4].rank() == 14 )
{
/* =================================
Check straight using an Ace
================================= */
boolean a = h[0].rank() == 2 && h[1].rank() == 3 &&
h[2].rank() == 4 && h[3].rank() == 5 ;
boolean b = h[0].rank() == 10 && h[1].rank() == 11 &&
h[2].rank() == 12 && h[3].rank() == 13 ;
return ( a || b );
}
else
{
/* ===========================================
General case: check for increasing values
=========================================== */
testRank = h[0].rank() + 1;
for ( i = 1; i < 5; i++ )
{
if ( h[i].rank() != testRank )
return(false); // Straight failed...
testRank++; // Next card in hand
}
return(true); // Straight found !
}
}
A quick Google search for "check for poker straight (desired_lang)" will give you other implementations.
You could just sort the cards and loop over them in an array - saving always the last card and compare them with the current one.
$cards = array(12,5,6,7,4,11,3);
sort($cards);
$last = 0;
$count = 0;
$wheel = false;
foreach ($cards as $card) {
if ($card == $last) {
continue;
} else if ($card == ++$last) {
$count++;
} else {
if ($last == 6) $wheel = true;
$count = 1;
$last = $card;
}
if ($count == 5 || ($card == 14 && $wheel)) {
echo "straight $last";
$straight = range($last - 4, $last);
break;
}
}
You may go like this, you don't need to sort or anything (assuming that 2 is 2 and 14 is ace):
$cards = [12,5,6,7,4,11,3];
function _inc(&$i) {
if ($i == 14)
$i = 2;
else
$i++;
return $i;
}
$straight = false;
for($i = 2; $i <= 14; $i++) {
$ind = $i;
if (!in_array($ind, $cards)) continue;
$s = [$ind, _inc($ind), _inc($ind), _inc($ind), _inc($ind)];
$straight = count(array_intersect($s, $cards)) == count($s);
if ($straight) break;
}
print $straight;

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.

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);
?>

Simple PHP program requires less time to execute

i had applied for a job recently and the requirement was to complete a test and then interview
2 questions were given for test which was very simple and i did it successfully but still i was told that i have failed the test because the script took more than 18 seconds to complete execution. here is the program i dont understand what else i could do to make it fast. although i have failed the test but still wants to know else i could do?
Program language is PHP and i had to do it using command line input
here is the question:
K Difference
Given N numbers , [N<=10^5] we need to count the total pairs of numbers that have a difference of K. [K>0 and K<1e9]
Input Format:
1st line contains N & K (integers).
2nd line contains N numbers of the set. All the N numbers are assured to be distinct.
Output Format:
One integer saying the no of pairs of numbers that have a diff K.
Sample Input #00:
5 2
1 5 3 4 2
Sample Output #00:3
Sample Input #01:
10 1
363374326 364147530 61825163 1073065718 1281246024 1399469912 428047635 491595254 879792181 1069262793
Sample Output #01:
0
Note: Java/C# code should be in a class named "Solution"
Read input from STDIN and write output to STDOUT.
and this is the solution
$fr = fopen("php://stdin", "r");
$fw = fopen("php://stdout", "w");
fscanf($fr, "%d", $total_nums);
fscanf($fr, "%d", $diff);
$ary_nums = array();
for ($i = 0; $i < $total_nums; $i++) {
fscanf($fr, "%d", $ary_nums[$i]);
}
$count = 0;
sort($ary_nums);
for ($i = $total_nums - 1; $i > 0; $i--) {
for ($j = $i - 1; $j >= 0; $j--) {
if ($ary_nums[$i] - $ary_nums[$j] == $diff) {
$count++;
$j = 0;
}
}
}
fprintf($fw, "%d", $count);
Your algorithm's runtime is O(N^2) that is approximately 10^5 * 10^5 = 10^10. With some basic observation it can be reduced to O(NlgN) which is approximately 10^5*16 = 1.6*10^6 only.
Algorithm:
Sort the array ary_nums.
for every i'th integer of the array, make a binary search to find if ary_nums[i]-K, is present in the array or not. If present increase result, skip i'th integer otherwise.
sort($ary_nums);
for ($i = $total_nums - 1; $i > 0; $i--) {
$hi = $i-1;
$low = 0;
while($hi>=$low){
$mid = ($hi+$low)/2;
if($ary_nums[$mid]==$ary_nums[$i]-$diff){
$count++;
break;
}
if($ary_nums[$mid]<$ary_nums[$i]-$diff){
$low = $mid+1;
}
else{
$hi = $mid-1;
}
}
}
}
I got the same question for my technical interview. I wonder if we are interviewing for the same company. :)
Anyway, here is my answer I came up with (after the interview):
// Insert code to get the input here
$count = 0;
sort ($arr);
for ($i = 0, $max = $N - 1; $i < $max; $i++)
{
$lower_limit = $i + 1;
$upper_limit = $max;
while ($lower_limit <= $upper_limit)
{
$midpoint = ceil (($lower_limit + $upper_limit) / 2);
$diff = $arr[$midpoint] - $arr[$i];
if ($diff == $K)
{
// Found it. Increment the count and break the inner loop.
$count++;
$lower_limit = $upper_limit + 1;
}
elseif ($diff < $K)
{
// Search to the right of midpoint
$lower_limit = $midpoint + 1;
}
else
{
// Search to the left of midpoint
$upper_limit = $midpoint - 1;
}
}
}
#Fallen: Your code failed for the following inputs:
Enter the numbers N and K: 10 3
Enter numbers for the set: 1 2 3 4 5 6 7 8 9 10
Result: 6
I think it has to do with your calculation of $mid (not accounting for odd number)

Categories