I want to create a rank system for users on my website. The ranks will be decided by a number of factors like how long they've been a member, how many posts they created etc. Every data item is also divided by a "weight" determined by me, so that it is more representative of the actual user activity - I don't want 1 post be as significant as 1 day as a member. After the weighing all stats are added together to a total.
Then, I have to normalize the totals so that they are assigned to the ranks which range from 1 to 20, since some members have just a few points of activity and some veteran members have thousands of points. I do this by normalizing the data and scaling it down to the 1-20 rank range with this function:
function normalize($userTotal, $minOriginalRange, $maxOriginalRange, $minNewRange, $maxNewRange){
return $minNewRange + ((($maxNewRange - $minNewRange) * ($originalValue - $minOriginalRange)) / ($maxOriginalRange - $minOriginalRange));
}
This is usually called like so:
normalize(getUserTotal(), 0, getHighestTotalOfAllMembers(), 1, 20);
And so I got this as a result, key is rank and value is number of members who would get that rank:
Array
(
[1] => 7418
[2] => 1918
[3] => 289
[4] => 102
[5] => 62
[6] => 28
[7] => 21
[8] => 14
[9] => 1
[10] => 8
[11] => 6
[12] => 5
[13] => 1
[14] => 1
[17] => 1
[20] => 1
)
As you can see there are tons of users who are ranked low and very few who get assigned the mid and high ranks. I'd like to fix this by calculating the rank assigned using a logarithmic scale, so that it is easy to climb the ranks in the lower tiers and gets harder and harder the higher you go. That way it should spread out more evenly and more users will have ranks in the middle.
I don't know how to approach this however, I have never used logarithmic scales and always resorted to simple arithmetic in my code.
You would use a php math logarithm function and map it over the final array, for example:
function logfunction($v){
return round(log1p($v),2);
}
$simple_array = [7418, 1918, 289, 102, 62, 28, 21, 14, 1, 8, 6, 5, 1, 1, 1];
$logarithmic_array = array_map(logfunction, $simple_array);
print_r($logarithmic_array);
In the above, I use the log1p() function whichreturns log(1 + number) computed in a way that is accurate even when the value of number is close to zero (See: http://php.net/manual/en/function.log1p.php). Then I round the result to 2 decimal places for the sake of readability. The resulting $logarithmic_array output is:
Array
(
[0] => 8.91
[1] => 7.56
[2] => 5.67
[3] => 4.63
[4] => 4.14
[5] => 3.37
[6] => 3.09
[7] => 2.71
[8] => 0.69
[9] => 2.2
[10] => 1.95
[11] => 1.79
[12] => 0.69
[13] => 0.69
[14] => 0.69
)
I am trying to calculate the winning order of golfers when they are tied in a competition.
These golf competitions are using the "stableford" points scoring system, where you score points per hole with the highest points winning. Compared to normal golf "stroke play" where the lowest score wins (though this also has the countback system, only calculating the lowest score in the event of a tie...)
The rules are to use a "countback". i.e., if scores are tied after 9 holes, the best placed of the ties is the best score from the last 8 holes. then 7 holes, etc.
The best I can come up with is 2 arrays.
An array with all the players who tied in a given round. ($ties)
One which has the full score data in (referencing the database playerid) for all 9 holes. ($tie_perhole)
I loop through array 1, pulling data from array 2 and using the following formula to create a temporary array with the highest score:
$max = array_keys($array,max($array));
If $max only has 1 item, this player is the highest scorer. the loop through the first array is "by reference", so on the next iteration of the loop, his playerid is now longer in the array, thus ignored. this continues until there is only 1 playerid left in the first array.
However, it only works if a single player wins in each iteration. The scenario that doesn't work is if a sub-set of players tie on any iterations / countbacks.
I think my problem is the current structure I have will need the original $ties array to become split, and then to continue to iterate through the split arrays in the same way...
As an example...
The $ties array is as follows:
Array
(
[18] => Array
(
[0] => 77
[1] => 79
[2] => 76
[3] => 78
)
)
The $tie_perhole (score data) array is as follows:
Array
(
[18] => Array
(
[77] => Array
(
[9] => 18
[8] => 16
[7] => 14
[6] => 12
[5] => 10
[4] => 8
[3] => 6
[2] => 4
[1] => 2
)
[79] => Array
(
[9] => 18
[8] => 17
[7] => 15
[6] => 14
[5] => 11
[4] => 9
[3] => 7
[2] => 5
[1] => 3
)
[76] => Array
(
[9] => 18
[8] => 16
[7] => 14
[6] => 12
[5] => 10
[4] => 8
[3] => 6
[2] => 4
[1] => 2
)
[78] => Array
(
[9] => 18
[8] => 17
[7] => 15
[6] => 13
[5] => 11
[4] => 9
[3] => 7
[2] => 5
[1] => 3
)
)
)
So in this competition, player's 78 and 79 score highest on the 8th hole countback (17pts), so 1st and 2nd should be between them. Player 79 should then be 1st on the 6th hole countback (14pts, compared to 13pts). The same should occur for 3rd and 4th place with the 2 remaining other players.
There are other scenarios that can occur here, in that within a competition, there will likely be many groups of players (of different amounts) on different tied points through the leaderboard.
Also note, there will be some players on the leaderboard who are NOT tied and stay in their current outright position.
The basics of the working code I have is:
foreach ($ties as $comparekey => &$compareval) {
$tie_loop = 0;
for ($m = 9; $m >= 1; $m--) {
$compare = array();
foreach ($compareval as $tie) {
$compare[$tie] = $tie_perhole[$comparekey][$tie][$m];
}
$row = array_keys($compare,max($compare));
if (count($row) == 1) {
$indexties = array_search($row[0], $ties[$comparekey]);
unset($ties[$comparekey][$indexties]);
// Now update this "winners" finishing position in a sorted array
// This is a multidimensional array too, with custom function...
$indexresults = searchForId($row[0], $comp_results_arr);
$comp_results_arr[$indexresults][position] = $tie_loop;
$tie_loop++;
}
// I think I need conditions here to filter if a subset of players tie
// Other than count($row) == 1
// And possibly splitting out into multiple $ties arrays for each thread...
if (empty($ties[$comparekey])) {
break;
}
}
}
usort($comp_results_arr, 'compare_posn_asc');
foreach($comp_results_arr as $row) {
//echo an HTML table...
}
Thanks in advance for any helpful insights, tips, thoughts, etc...
Robert Cathay asked for more scenarios. So here is another...
The leaderboard actually has more entrants (player 26 had a bad round...), but the code i need help with is only bothered about the ties within the leaderboard.
Summary leaderboard:
Points Player
21 48
21 75
20 73
20 1
13 26
This example produces a $tie_perhole array of:
Array
(
[21] => Array
(
[75] => Array
(
[9] => 21
[8] => 19
[7] => 16
[6] => 14
[5] => 12
[4] => 9
[3] => 7
[2] => 5
[1] => 3
)
[48] => Array
(
[9] => 21
[8] => 19
[7] => 16
[6] => 13
[5] => 11
[4] => 9
[3] => 8
[2] => 5
[1] => 3
)
)
[20] => Array
(
[73] => Array
(
[9] => 20
[8] => 18
[7] => 16
[6] => 13
[5] => 11
[4] => 8
[3] => 6
[2] => 5
[1] => 3
)
[1] => Array
(
[9] => 20
[8] => 17
[7] => 16
[6] => 14
[5] => 12
[4] => 9
[3] => 7
[2] => 4
[1] => 2
)
)
)
In this example, the array shows that players 75 and 48 scored 21 points that player 75 will eventually win on the 6th hole countback (14pts compared to 13pts) and player 48 is 2nd. In the next tied group, players 73 and 1 scored 20 points, and player 73 will win this group on the 8th hole countback and finishes 3rd (18 pts compared to 17 pts), with player 1 in 4th. player 26 is then 5th.
Note, the $tie_loop is added to another array to calculate the 1st to 5th place finishing positions, so that is working.
Hopefully that is enough to help.
Ok, so I don't understand golf at all... hahaha BUT! I think I got the gist of this problem, so heres my solution.
<?php
/**
* Author : Carlos Alaniz
* Email : Carlos.glvn1993#gmail.com
* Porpuse : Stackoverflow example
* Date : Aug/04/2015
**/
$golfers = [
"A" => [1,5,9,1,1,2,3,4,9],
"B" => [2,6,4,2,4,4,1,9,3],
"C" => [3,4,9,8,1,1,5,1,3],
"D" => [1,5,1,1,1,5,4,5,8]
];
//Iterate over scores.
function get_winners(&$golfers, $hole = 9){
$positions = array(); // The score numer is the key!
foreach ($golfers as $golfer=>$score ) { // Get key and value
$score_sub = array_slice($score,0,$hole); // Get the scores subset, first iteration is always all holes
$total_score = (string)array_sum($score_sub); // Get the key
if(!isset($positions[$total_score])){
$positions[$total_score] = array(); // Make array
}
$positions[$total_score][] = $golfer; // Add Golpher to score.
}
ksort($positions, SORT_NUMERIC); // Sort based on key, low -> high
return array(end($positions), key($positions)); // The last shall be first
}
//Recursion is Awsome
function getWinner(&$golfers, $hole = 9){
if ($hole == 0) return;
$winner = get_winners($golfers,$hole); // Get all ties, if any.
if(count($winner[0]) > 1){ // If theirs ties, filter again!
$sub_golfers =
array_intersect_key($golfers,
array_flip($winner[0])); // Only the Worthy Shall Pass.
$winner = getWinner($sub_golfers,$hole - 1); // And again...
}
return $winner; // We got a winner, unless they really tie...
}
echo "<pre>";
print_R(getWinner($golfers));
echo "</pre>";
Ok... Now ill explain my method...
Since we need to know the highest score and it might be ties, it makes no sense to me to maintain all that in separate arrays, instead I just reversed the
golfer => scores
to
Tota_score => golfers
That way when we can sort the array by key and obtain all the golfers with the highest score.
Now total_score is the total sum of a subset of the holes scores array. So... the first time this function runs, it will add all 9 holes, in this case theres 3 golfers that end up with the same score.
Array
(
[0] => Array
(
[0] => A
[1] => B
[2] => C
)
[1] => 35
)
Since the total count of golfers is not 1 and we are still in the 9th hole, we run this again, but this time only against those 3 golfers and the current hole - 1, so we are only adding up to the 8th hole this time.
Array
(
[0] => Array
(
[0] => B
[1] => C
)
[1] => 32
)
We had another tie.... this process will continue until we reach the final hole, or a winner.
Array
(
[0] => Array
(
[0] => C
)
[1] => 31
)
EDIT
<?php
/**
* Author : Carlos Alaniz
* Email : Carlos.glvn1993#gmail.com
* Porpuse : Stackoverflow example
**/
$golfers = [
"77" => [2,4,6,8,10,12,14,16,18],
"79" => [3,5,7,9,11,14,15,17,18],
"76" => [2,4,6,8,10,12,14,16,18],
"78" => [3,5,7,9,11,13,15,17,18]
];
//Iterate over scores.
function get_winners(&$golfers, $hole = 9){
$positions = array(); // The score numer is the key!
foreach ($golfers as $golfer => $score) { // Get key and value
//$score_sub = array_slice($score,0,$hole); // Get the scores subset, first iteration is always all holes
$total_score = (string)$score[$hole-1]; // Get the key
if(!isset($positions[$total_score])){
$positions[$total_score] = array(); // Make array
}
$positions[$total_score][] = $golfer; // Add Golpher to score.
}
ksort($positions, SORT_NUMERIC); // Sort based on key, low -> high
return [
"winner"=> end($positions),
"score" => key($positions),
"tiebreaker_hole" => [
"hole"=>$hole,
"score"=> key($positions)],
]; // The last shall be first
}
//Recursion is Awsome
function getWinner(&$golfers, $hole = 9){
if ($hole == 0) return;
$highest = get_winners($golfers,$hole); // Get all ties, if any.
$winner = $highest;
if(count($winner["winner"]) > 1){ // If theirs ties, filter again!
$sub_golfers =
array_intersect_key($golfers,
array_flip($winner["winner"])); // Only the Worthy Shall Pass.
$winner = getWinner($sub_golfers,$hole - 1); // And again...
}
$winner["score"] = $highest["score"];
return $winner; // We got a winner, unless they really tie...
}
echo "<pre>";
print_R(getWinner($golfers));
echo "</pre>";
Result:
Array
(
[winner] => Array
(
[0] => 79
)
[score] => 18
[tiebreaker_hole] => Array
(
[hole] => 6
[score] => 14
)
)
I'm working with multi-dimesional arrays and am stumped trying to create a dynamic compatibility chart.
Given Data :
4 instances of Value 1
7 instances of Value 2
Ideal End Result Examples
If there are 4 value 1's and 7 value 2's
[0] Value 2
[1] Value 1
[2] Value 2
[3]
[4] Value 2
[5] Value 1
[6] Value 2
[7]
[8] Value 2
[9] Value 1
[10] Value 2
[11] Value 1
[12] Value 2
If there are 4 value 1's and 4 value 2's (try to evenly space them out without overlapping)
[0] Value 1
[1] Value 2
[2]
[3] Value 1
[4] Value 2
[5]
[6]
[7] Value 1
[8] Value 2
[9]
[10]
[11] Value 1
[12] Value 2
If there are 2 instances of 1 and three of 2.
[0] Value 1
[1] Value 2
[2]
[3]
[4]
[5]
[6] Value 2
[7]
[8]
[9]
[10]
[11] Value 1
[12] Value 2
I have bucket sizes of 12-24 for an array. In the example I use 12. If number of instances don't fit in 12 buckets, can move up all the way to 24. If it doesn't fit in 24, give an error.
Any loop that I've tried to make by using array_pop and reversing arrays creates empty holes in the middle of the array or it doesn't spread out the instances evenly.
Edit:
Here is what I've tried.
$table_array = range(0,12);
// Method 1
for ($i = 0; sizeof($table_array); $i++)
{
$ready_array[] = ($i % 2) ? array_pop($table_array) : array_shift($table_array);
}
// Method 2
for ($i = 0; $i < sizeof($table_array); $i++)
{
$index = ($i % 2) ? sizeof($table_array) - ceil($i / 2) : ceil($i / 2);
$ready_array[$index] = $table_array[$i];
}
ksort($ready_array);
Project Details and Goal
I have a list of compatible and incompatible pills.
I let the users select which pills and how many they're taking.
Based on that, I search the database and match up pills based on compatibility creating a lists known as Value 1 and Value 2.
I have a minimum 12 hour time frame to take the pills.
I'm supposed to space out lets say 4 of Calcium and 3 of Iron evenly across 12 hour period with 1 hour intervals and not let them overlap. If they choose 12 pills of Calcium and 12 pills of Iron, I can push up to 24 hours to make them take those pills.
Some ideas for your question :
Use array_chunk() and array_count_values()
<?php
$myarray = array(2,1,2,'',2,1,2,'',2,1,2,1,1,2,1,2,'',2,1,2,'',2,1,2,1,2);
$tests = array_chunk($myarray, 13);
//print_r($tests);
function mytest($tests)
{
foreach($tests as $test)
{
// play your test !
print_r(array_count_values($test));
}
}
// add your argument for your test
echo mytest($tests);
?>
output :
Array
(
[2] => 6
[1] => 5
[] => 2
)
Array
(
[2] => 7
[1] => 4
[] => 2
)
See for test : http://codepad.org/M9u5G0je
I am facing a problem i am not capable to solve. I have a string consisting of not needed text and 10 digit numbers who always start with "2" or "6". I need to get those in 10digit numbers into an array. I thought of regex and found this article Regular Expression for matching a numeric sequence? which is pretty close to what i need (except the descending/ascending thing) yet, as i could never and will NEVER be able to understand regex, i cant modify to my needs. If anyone could help me out here i would highly appreciate it!
Here is a sample of my string:
".........693 7098469 - ZQH X Bop. Hrtepou 50 flerpoUrroXn ........210 5014166 - 0E000PA E KapaoAn Anpn-rPou 21
EAArivtg .....................................................210 9618677 - MAPIA KapaoAri Arpn-rptou 21 Elanvolo .. 210 9643623 - MAPIA E ...................................................... 210 9643887 - MAPIA 0 loucrrivou 8 HX.toOrran ..............210 9914534 AIPITAKHE APTEMIOE n Avrtnopou 22
Reptcrrept ....._.........._......._................697 7440896 , -10AN."
Thank you very much in advance!
Greetings from Greece!
As I see your string your digits have an space between, and if you want strictly make your selections this is the regex:
[62]\d{2}\s*\d{7}
Explanation:
[62] # Start with 6 or 2
\d{2} # 2 more digits
\s* # any number of white spaces
\d{7} # 7 more digits
Live demo
and PHP code which has preg_match_all to match all occurrences of those strings:
preg_match_all("/[62]\d{2}\s*\d{7}/", $text, $matches);
Output:
Array
(
[0] => 693 7098469
[1] => 210 5014166
[2] => 210 9618677
[3] => 210 9643623
[4] => 210 9643887
[5] => 210 9914534
[6] => 697 7440896
)
PHP live demo
Maybe like this:
<?php
$x=
".........693 7098469 - ZQH X Bop. Hrtepou 50 flerpoUrroXn ........210 5014166 - 0E000PA E KapaoAn Anpn-rPou 21 EAArivtg ....................................................210 9618677 - MAPIA KapaoAri Arpn-rptou 21 Elanvolo .. 210 9643623 - MAPIA E ...................................................... 210 9643887 - MAPIA 0 loucrrivou 8 HX.toOrran ..............210 9914534 AIPITAKHE APTEMIOE n Avrtnopou 22
Reptcrrept ....._.........._......._................697 7440896 , -10AN.";
$x=str_replace(' ','',$x);
preg_match_all('/((2|6)\d{9})/',$x,$matches);
print_r($matches[0]);
And the result:
Array
(
[0] => 6937098469
[1] => 2105014166
[2] => 2109618677
[3] => 2109643623
[4] => 2109643887
[5] => 2109914534
[6] => 6977440896
)
there is a pretty cool page, that visualize the regex code for better understading:
https://www.debuggex.com/
this should work
((?:2|6)[0-9]{2} [0-9]{7})
I have this TXT file, which each line I would to insert into array, like this:
Array ( [0] => [1] => 8100 [2] => 623 [3] => 09:04 [4] => AM [5] => 00:26 [6] => L [7] => S-ED [8] => 768 [9] => #4506856439 [10] => 00:01 [11] => )
CO USER TIME DURATION TYPE ACCOUNT_CODE CALLED_NUM RING_TIME
8100 623 09:04 AM 00:26 L S-E 250821613987
8021 8816 09:06 AM 00:20 I S-E D 768 #4506856439 00:01
8020 09:06 AM I N D 603 #45424044499 00:30
8011 09:07 AM 00:11 I S-E D 727 #7546355292 00:02
" 8817 00:11
" 00:02 H
8100 623 09:07 AM 00:01 L S-E 5542204034
8007 8818 09:08 AM 00:13 I S-E D 618 #45269021726 00:01
8013 8811 09:09 AM 00:01 I S-E D 770 #436217227 00:01
8014 09:10 AM 00:16 I S-E D 652 #4523859922 00:01
I'd like to insert it into an array with all parameters, even if empty,
but I can't get it work.
I tried this:
$fh = fopen('captures/131210.txt','r');
while ($line = fgets($fh)) {
$tempexp = preg_split('/\s+/', $line);
print_r($tempexp);
echo "<br />";
}
fclose($fh)
But it gives out weird outputs for some values, like this:
Array ( [0] => [1] => " [2] => 8812 [3] => 00:16 [4] => )
Help would be much appreciated!
If the layout of your text file is purely based on spaces you can used the exact columns to extract the data. Since the columns are not of equal width you cannot simply write it in one loop.
Use the substr function to get part of the line (first number is index, second number is length to read)
$fh = fopen('captures/131210.txt','r');
while ($line = fgets($fh)) {
$co = substr($line, 0, 7);
$user = substr($line, 7, 6);
... etc ...
echo "<br />";
}
fclose($fh)
You might need to parse the separate parts further down dependent on the content and what you want to do with it - parse as integer, date, time etc.