PHP - Find the number of groups given some constraints - php

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

Related

Expanding Round-robin tournament 1v1 to a 1v1v1v1

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.

Selection sort best swapping case is not O(N) It is O(1) !!?

I seen in the selection sort if inner equals to minimum value it will be swapping them. but why you do it?
I just added the if statement for swapping if $inner = $min so why swapping them because they are the same index!. So why you do it?!!
The condition is : if $inner = $minimum (Don't swap). else they are not euqal (swap).
this is the code.
<?php
$a = array(10,9,8,7,6,5,4,3,2,1);
$num = sizeof($a);
$comp = 0;
$swap = 0;
for ($i=0; $i < $num; $i++) {
echo "$a[$i] | ";
}
echo "<br>";
for ($in=0; $in < $num; $in++) {
$min = $in;
for ($i=$in+1; $i < $num; $i++) {
if ($a[$i] < $a[$min]) {
$min = $i;
}
$comp++;
}
if ($in != $min) {
$past = $a[$min];
$a[$min] = $a[$in];
$a[$in] = $past;
$swap++;
}
}
for ($i=0; $i < $num; $i++) {
echo "$a[$i] | ";
}
echo "<br> comp : $comp , swap : $swap";
?>
No. Time complexity doesn't depend upon number of swaps you did but on number of iterations for checking.
Lets take an example.,
1 4 9 3 0 8 5
You're saying that you'll iterate the whole array and find the lowest(minimum) number and you'll swap it with current index if both are not equal.
That means,
Your sorted array: 0 4 9 3 1 8 5
But it's not sorted. It means it is sorted upto 0th [1st number] index only.
You've to repeat it the same from 2nd index.
Now, lets find out the actual time complexity.
As you iterate for every index (let's take as 'i'), it is O(N) for outer loop and you'll iterate inner loop from i+1 (let's take as 'j') and it's O(N) for inner loop.
So O(N)*O(N)=O(N^2)
If you still have any doubt, lets take iterations of j that is,
for i=0, j iterates from 1 -> N
for i=1, j iterates from 2 -> N
for i=2, j iterates from 3 -> N and so on...
so = (N-1) + (N-2) + (N-3) + (N-4) ......+ 1
=> (N (N-1)) / 2
= O(N^2)

Possible combinations of binary

The problem statement is as following:
A particular kind of coding which we will refer to as "MysteryCode" is a binary system of encoding in which two successive values, differ at exactly one bit, i.e. the Hamming Distance between successive entities is 1. This kind of encoding is popularly used in Digital Communication systems for the purpose of error correction.
LetMysteryCodes(N)represent the MysteryCode list for N-bits.
MysteryCodes(1) = 0, 1 (list for 1-bitcodes,in this order)
MysteryCodes(2) = 00, 01, 11, 10 (list for 2-bitcodes,in this order)
MysteryCodes(3) =000, 001, 011, 010,110, 111, 101, 100 (list for 3-bitcodes,in this order)
There is a technique by which the list of (N+1) bitcodescan be generated from (N)-bitcodes.
Take the list of N bitcodesin the given order and call itList-N
Reverse the above list (List-N), and name the new reflected list: Reflected-List-N
Prefix each member of the original list (List-N) with 0 and call this new list 'A'
Prefix each member of the new list (Reflected-List-N) with 1 and call this new list 'B'
The list ofcodeswith N+1 bits is the concatenation of Lists A and B.
A Demonstration of the above steps: Generating the list of 3-bitMysteryCodesfrom 2-BitMysteryCodes
2-bit list ofcodes:00, 01, 11, 10
Reverse/Reflect the above list:10, 11, 01, 00
Prefix Old Entries with 0:000, 001, 011, 010
Prefix Reflected List with 1:110, 111, 101, 100
Concatenate the lists obtained in the last two steps:000, 001, 011, 010, 110, 111, 101, 100
Your Task
Your task is to display the last N "MysteryCodes" from the list of MysteryCodes for N-bits. If possible, try to identify a way in which this list can be generated in a more efficient way, than iterating through all the generation steps mentioned above.
More efficient or optimized solutions will receive higher credit.
Input Format
A single integer N.
Output Format
N lines, each of them with a binary number of N-bits. These are the last N elements in the list ofMysteryCodesfor N-bits.
Input Constraints 1 = N = 65
Sample Input 1
1
Sample Output 1
1
Explanation for Sample 1
Since N = 1, this is the (one) last element in the list ofMysteryCodesof 1-bit length.
Sample Input 2
2
Sample Output 2
11
10
Explanation for Sample 2 Since N = 2, these are the two last elements in the list ofMysteryCodesof 2-bit length.
Sample Input 3
3
Sample Output 3
111
101
100
$listN = 25;
$bits = array('0','1');
//check if input is valid or not
if(!is_int($listN))
{
echo "Input must be numeric!";
}
if($listN >= 1 && $listN <=65){
if($listN == 1){
echo '1'; exit;
}
ini_set('memory_limit', -1);
for($i=1; $i<=($listN - 1); $i++){
$reverseBits = array_reverse($bits);
$prefixBit = preg_filter('/^/', '0', $bits);
$prefixReverseBits = preg_filter('/^/', '1', $reverseBits);
$bits = array_merge($prefixBit, $prefixReverseBits);
unset($prefixBit, $prefixReverseBits, $reverseBits);
}
$finalBits = array_slice($bits, -$listN);
foreach($finalBits as $k=>$v){
echo $v."\n";
}
}
else{
echo "Invalid input!";
}
I have tried above solution, but didnt worked for input greater than 20.
for eg. If the input is 21, I got "Couldnt allocate memory" error.
It will be great if somebody figure out the optimized solutions...
The numbers follow a pattern which I transformed to below code.
Say given number is N
then create a N x N matrix and fill it's first column with 1's
and all other cells with 0's
Start from rightmost column uptil 2nd column.
For any column X start from bottom-most row and fill values like below:
Fill 2^(N - X + 1)/2 rows with 0's.
Fill 2^(N - X + 1) rows with 1's and then 0's alternatively.
Repeat step 2 till we reach topmost row.
Print the N x N matrix by joining the values in each row.
<?php
$listN = 3;
$output = [];
for ($i = 0; $i < $listN; $i++) {
$output[$i] = [];
for ($j = 0; $j < $listN; $j++) {
$output[$i][$j] = 0;
}
}
$output[$listN - 1][0] = 1;
for ($column = 1; $column < $listN; $column++) {
$zeroFlag = false;
for ($row = $listN - 1; $row >= 0;) {
$oneZero = 1;
if (!$zeroFlag) {
for ($k = 1; $k <= pow(2, $column) / 2 && $row >= 0; $k++) {
$output[$row][$listN - $column] = 0;
$row--;
$zeroFlag = true;
}
}
for ($k = 1; $k <= pow(2, $column) && $row >= 0; $k++) {
$output[$row][$listN - $column] = $oneZero;
$row--;
}
$oneZero = 0;
for ($k = 1; $k <= pow(2, $column) && $row >= 0; $k++) {
$output[$row][$listN - $column] = $oneZero;
$row--;
}
}
}
for ($i = 0; $i < $listN; $i++) {
$output[$i][0] = 1;
}
for ($i = 0; $i < $listN; $i++) {
print(join('', $output[$i]));
print("\n");
}

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

How to do a special shuffle function in php

I need a function that randomizes an array similar to what shuffle does, with the difference that each element has different chances.
For example, consider the following array:
$animals = array('elephant', 'dog', 'cat', 'mouse');
elephant has an higher chance of getting on the first index than dog. Dog has an higher chance than cat and so on. For example, in this particular example elephant could have a chance of 40% in getting in the 1st position, 30% of getting on the 2nd position, 20% on getting on 3rd and 10% getting on last.
So, after the shuffling, the first elements in the original array will be more likely (but not for sure) to be in the first positions and the last ones in the last positions.
Normal shuffle may be implemented just as
dropping items randomly at some range
picking them up from left to right
We can adjust dropping step, drop every element not into whole range, but at some sliding window. Let N would be amount of elements in array, window width would be w and we'll move it at each step by off. Then off*(N-1) + w would be total width of the range.
Here's a function, which distorts elements' positions, but not completely at random.
function weak_shuffle($a, $strength) {
$len = count($a);
if ($len <= 1) return $a;
$out = array();
$M = mt_getrandmax();
$w = round($M / ($strength + 1)); // width of the sliding window
$off = ($M - $w) / ($len - 1); // offset of that window for each step.
for ($i = 0; $i < $len; $i++) {
do {
$idx = intval($off * $i + mt_rand(0, $w));
} while(array_key_exists($idx, $out));
$out[$idx] = $a[$i];
}
ksort($out);
return array_values($out);
}
$strength = 0 ~normal shuffle.
$strength = 0.25 ~your desired result (40.5%, 25.5%, 22%, 12% for elephant)
$strength = 1 first item will never be after last one.
$strength >= 3 array is actually never shuffled
Playground for testing:
$animals = array( 'elephant', 'dog', 'cat', 'mouse' );
$pos = array(0,0,0,0);
for ($iter = 0; $iter < 100000; $iter++) {
$shuffled = weak_shuffle($animals, 0.25);
$idx = array_search('elephant', $shuffled);
$pos[$idx]++;
}
print_r($pos);
Try to use this algorithm:
$animals = [ 'elephant', 'dog', 'cat', 'mouse' ]; // you can add more animals here
$shuffled = [];
$count = count($animals);
foreach($animals as $chance => $animal) {
$priority = ceil(($count - $chance) * 100 / $count);
$shuffled = array_merge($shuffled, array_fill(0, $priority, $animal));
}
shuffle($shuffled);
$animals = array_unique($shuffled);
You have an array, let's say of n elements. The probability that the i'th element will go to the j'th position is P(i, j). If I understood well, the following formula holds:
(P(i1, j1) >= P(i2, j2)) <=> (|i1 - j1| <= |j1 - i1|)
Thus, you have a Galois connection between the distance in your array and the shuffle probability. You can use this Galois connection to implement your exact formula if you have one. If you don't have a formula, you can invent one, which will meet the criteria specified above. Good luck.

Categories