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
)
Related
I have a multidimensional array in PHP, and want to concatenate a string onto each string element using recursion. The array is as follows:
$array = Array
(
[p] => Array
(
[0] => This Porsche 993 Carrera Cabriolet represents a great opportunity to acquire an open-top variant of one of the most coveted 911 models.
[1] => First registered on 5 August 1994, M912 SGY displays 10,630 miles on the odometer with a clock change at 66,244 miles in 2014.
[2] => The car’s Aventura Green metallic paintwork is reported to be in good condition, presenting well for its age and mileage.
[3] => The Marble Grey leather interior is believed to be entirely original.
[4] => Serviced by Porsche specialist Portiacraft in July 2020 at 76,598 miles, this consisted of an annual oil and filter service.
[5] => The last MOT was undertaken on 6 July 2020 at 76,598 miles.
[6] => It is supplied with a Porsche Club Great Britain folder with records of main dealer and specialist service history.
[7] => This Porsche 911 Carrera Cabriolet presents in highly original and well-maintained condition.
[8] => Summary of maintenance history:
[9] => Array
(
[strong] => The description of this auction lot is, to the best of the seller's knowledge, accurate and not misleading.
)
[10] => Array
(
[strong] => All UK-registered cars and motorbikes on Collecting Cars are run through an online HPI check. This vehicle shows no insurance database markers for damage or theft, and has no finance owing.
)
)
[ul] => Array
(
[li] => Array
(
[0] => 04/11/1996 – 16,120 miles
[1] => 18/11/1998 – 25,086 miles
[2] => 09/09/1999 – 28,769 miles
[3] => 21/02/2000 – 31,469 miles
[4] => 22/06/2001 – 36,055 miles
[5] => 29/10/2002 – 40,781 miles
[6] => 02/03/2005 – 46,238 miles
[7] => 24/03/2006 – 49,459 miles
[8] => 03/07/2007 – 53,051 miles
[9] => 17/12/2008 – 56,582 miles
[10] => 20/05/2010 – 57,385 miles
[11] => 08/06/2011 – 61,653 miles
[12] => 15/05/2012 – 64,425 miles
[13] => 17/04/2013 – 66,026 miles
[14] => 07/06/2014 – 66,244 miles
[15] => 14/09/2015 – 68,411 miles
[16] => 27/02/2018 – 74,856 miles
[17] => 06/08/2019 – ~76,400 miles
[18] => 06/07/2020 – 76,598 miles
)
)
)
Ideally, the result should look like this:
$array = Array
(
[p] => Array
(
[0] => This Porsche 993 Carrera Cabriolet represents a great opportunity to acquire an open-top variant of one of the most coveted 911 models. checked
[1] => First registered on 5 August 1994, M912 SGY displays 10,630 miles on the odometer with a clock change at 66,244 miles in 2014. checked
[2] => The car’s Aventura Green metallic paintwork is reported to be in good condition, presenting well for its age and mileage. checked
[3] => The Marble Grey leather interior is believed to be entirely original. checked
[4] => Serviced by Porsche specialist Portiacraft in July 2020 at 76,598 miles, this consisted of an annual oil and filter service. checked
[5] => The last MOT was undertaken on 6 July 2020 at 76,598 miles. checked
[6] => It is supplied with a Porsche Club Great Britain folder with records of main dealer and specialist service history. checked
[7] => This Porsche 911 Carrera Cabriolet presents in highly original and well-maintained condition. checked
[8] => Summary of maintenance history: checked
[9] => Array
(
[strong] => The description of this auction lot is, to the best of the seller's knowledge, accurate and not misleading. checked
)
[10] => Array
(
[strong] => All UK-registered cars and motorbikes on Collecting Cars are run through an online HPI check. This vehicle shows no insurance database markers for damage or theft, and has no finance owing. checked
)
)
[ul] => Array
(
[li] => Array
(
[0] => 04/11/1996 – 16,120 miles checked
[1] => 18/11/1998 – 25,086 miles checked
[2] => 09/09/1999 – 28,769 miles checked
[3] => 21/02/2000 – 31,469 miles checked
[4] => 22/06/2001 – 36,055 miles checked
[5] => 29/10/2002 – 40,781 miles checked
[6] => 02/03/2005 – 46,238 miles checked
[7] => 24/03/2006 – 49,459 miles checked
[8] => 03/07/2007 – 53,051 miles checked
[9] => 17/12/2008 – 56,582 miles checked
[10] => 20/05/2010 – 57,385 miles checked
[11] => 08/06/2011 – 61,653 miles checked
[12] => 15/05/2012 – 64,425 miles checked
[13] => 17/04/2013 – 66,026 miles checked
[14] => 07/06/2014 – 66,244 miles checked
[15] => 14/09/2015 – 68,411 miles checked
[16] => 27/02/2018 – 74,856 miles checked
[17] => 06/08/2019 – ~76,400 miles checked
[18] => 06/07/2020 – 76,598 miles checked
)
)
)
I have tried the following:
$addedChecked = $this->addCheckedRecursive($array);
private function addCheckedRecursive($array)
{
if(!is_array($array)) {
return $array . ' checked';
}
foreach($array as $v) {
$this->addCheckedRecursive($v);
}
}
and also
$addedChecked = array_walk_recursive($array, function (&$value) {
$value .= ' checked';
});
The latter simply returned true.
For info, every element of each array will always be a string, and I would also like to preserve the current array structure. Any help is appreciated.
There is an in-built function that you can use to achieve what you want.
If you use array_walk_recursive as follows:
// Say you have your array $xmlArray
array_walk_recursive($xmlArray, function (&$value) {
$value .= ' checked';
});
// Since $xmlArray is now modified (in place)
echo '<pre>';
print_r($xmlArray);
echo '</pre>';
In case you would not want $xmlArray to be changed as a side effect, could assign a value copy of the array to a new variable.
$addedChecked = $xmlArray;
array_walk_recursive($addedChecked, function (&$value) {
$value .= ' checked';
});
// Since $addedChecked is now modified (in place)
echo '<pre>';
print_r($addedChecked);
echo '</pre>';
The function takes in the array by reference and will thus modify the array directly as a side effect of the function. This is one important thing to note, it does not return an array, but only whether the function was successfully executed on the array you gave it.
This will loop over each key => value pair and do so recursively if the value is of type array. You can simply concatenate to the value (where we pass value by reference) to update it with checked.
why dont you try array_walk_recursive or array_replace_recursive ?
the docs can be found here
Try it like this:
$addedChecked = $this->addCheckedRecursive($array);
private function addCheckedRecursive($array)
{
if (!is_array($array)) {
return $array . ' checked';
}
else
{
for ($i = 0; $i < count($array); $i++) {
$array[$i] = $this->addCheckedRecursive($array[$i]);
}
return $array;
}
}
I have a column pack_size in a table called product_master_test. The problem that I am facing is that the pack_size is in mixed formats, there is no uniformity to it.
For example:
4 x 2kg (pack size should be 4)
48-43GM (pack size should be 48)
12 x 1BTL (pack size should be 12)
1 x 24EA (pack size should be 24)
I've been thinking about different approaches, but I can't think of anything that would work without having a lot of IF statements in the query/PHP code. Is there a solution that I am missing?
I do have the file in Excel, if there is an easier way to process it using PHP.
I am not including any code, as I'm not entirely sure where to start with this problem.
Using a regex to split the pack size could at least give you the various components which you can then (possibly) infer more from...
$packs = ["4 x 2kg","48-43GM","12 x 1BTL","1 x 24EA", "12 X 1 EA"];
foreach ( $packs as $size ) {
if ( preg_match("/(\d*)(?:\s+)?[xX-](?:\s+)?(\d+)(?:\s+)?(\w*)/", $size, $match) == 1 ) {
print_r($match);
}
else {
echo "cannot determine - ".$size.PHP_EOL;
}
}
(regex can probably be optimised, not my area of expertise). It basically splits it to be a number, some space with either a x or a - and then another number followed by the units (some text). The above with the test cases gives...
Array
(
[0] => 4 x 2kg
[1] => 4
[2] => 2
[3] => kg
)
Array
(
[0] => 48-43GM
[1] => 48
[2] => 43
[3] => GM
)
Array
(
[0] => 12 x 1BTL
[1] => 12
[2] => 1
[3] => BTL
)
Array
(
[0] => 1 x 24EA
[1] => 1
[2] => 24
[3] => EA
)
Array
(
[0] => 12 X 1 EA
[1] => 12
[2] => 1
[3] => EA
)
With the else part it should also give you the ones it cannot determine and perhaps allow you to change it accordingly.
You could present an associative array of all the strings from the table as keys corresponding with correct pack_size you desire.
$packsize = ["4 x 2kg" => 4, "48-43GM" => 48, "12 x 1BTL" => 12, "1 x 24EA" => 24]; //add all pack_sizes here
echo $packsize["4 x 2kg"]; // Output: 4
Now you could get the acutal pack size via the key of associative array. It could save some time you would spend making if/else conditions or switching the input. I'm not sure if there is something wrong with this approach, so correct me if so.
I want to store sub category name and products related to that particular subcategory in array.
Below is my code,
f ($sub_category->num_rows > 1) {
while ($row = mysqli_fetch_array($sub_category)) {
$subcategory_id = $row['sub_cat_id'];
$subcategory_name['sub_category_name'][] = $row['sub_cat_name'];
$sql_subrec = "SELECT es_product_description.name AS prod_name FROM es_product_to_category LEFT JOIN es_product_description ON es_product_description.product_id=es_product_to_category.product_id LEFT JOIN es_product ON es_product_description.product_id = es_product.product_id WHERE es_product_to_category.category_id = $subcategory_id AND es_product.status=1";
//echo "<pre>";print_r($sql_subrec);
$sub_product = $conn->query($sql_subrec);
while ($prow = mysqli_fetch_assoc($sub_product)) {
$subcategory_name['sub_category_name']['products_name'][] = $prow['prod_name'];
}
}
//$subcategory_name[] = array('product_data' => $subcategory_name,);
//echo "<pre>";print_r($subcategory_name);
echo "<pre>";print_r($subcategory_name);
}
Result is not coming as expected.
I want to get products inside related subcategory array but it is coming everything in first array.
Array
(
[sub_category_name] => Array
(
[0] => Weighing Scale
[products_name] => Array
(
[0] => BS-250 Baby Scale
[1] => DS-415N Bench Scale
[2] => DS-415N Platform Scale
[3] => DS-65 Counter Scale
[4] => DS-852 Weighing Scale
[5] => DS-252 Weighing Scale
[6] => DS-215N Platform Scale
[7] => DS-75 Counter Scale
[8] => DS-415 Smile
[9] => DS-215SS Platform Scale
[10] => DS-450 Table Top Weighing Scale
[11] => DS-450SS Bench Scale
[12] => DS-451HP Weighing Scale
[13] => DS-215 Hanging Scale
[14] => DS-215N Trolley Scale
[15] => DS-673 Weighing Scale
[16] => DS-315 Series Crane Scale
[17] => DS-515 Weighing Scale
[18] => DS-450CW Check Weigher Scale
[19] => DC-810 Counting, Barcode Printing Scale
[20] => DC-85 Counting Scale
[21] => DS-252C Counting Scale
[22] => DC-810 Counting, Barcode Printing Scale
[23] => SI-810 Label Printing Scale
[24] => SI-810PR Receipt Printing
[25] => DS-252PR Receipt Printing Scale
[26] => Barcode Label Printer Scales
[27] => Receipt Printer Scales
[28] => SI-810PR Barcode Label Printer Scale
[29] => SM - 100EV+ Barcode Label Printer Scale
[30] => SI-810PRSS Receipt Printer Scale
[31] => SI-810PR Receipt Printer Scale
[32] => DS-252PR Receipt Printer Scale
[33] => DC-810 Counting, Barcode Printing Scale
[34] => SI-810 Label Printing Scale
[35] => DS-451TW Tank/Vessel Weighing
[36] => DS-451 Milk Weighing
[37] => SI-810 System Scale Bench Type
[38] => SI-810 System Scale Platform Type
[39] => HT-HTR Series Analytical Balance
[40] => MX-50 Moisture Analyzer
[41] => AJ Series Precision Balance
[42] => PG/FB Precision Balance
[43] => DS-852G Precision Balance
[44] => LF-225DR Semi-micro balance
)
[1] => Industrial Counting Scale
[2] => Retail Printer Scale
[3] => Weighing System & Solutions
[4] => Weighing Balance
)
)
As far as I understand from you limited explanation you might just need to do something like this in your second while:
$subcategory_name['sub_category_name'][$row['sub_cat_name']]['products_name'][] = $prow['prod_name'];
But this is just a guess... please post the expected output and the actual one.
Try this. 'Sub Category Name' & 'Product Name' comes under single 'Sub Category ID' keyindex
if ($sub_category->num_rows > 1){
while ($row = mysqli_fetch_array($sub_category)){
$subcategory_id = $row['sub_cat_id'];
$subcategory_name[$subcategory_id]['sub_category_name'][] = $row['sub_cat_name'];
$sql_subrec = "SELECT es_product_description.name AS prod_name FROM es_product_to_category LEFT JOIN es_product_description ON es_product_description.product_id=es_product_to_category.product_id LEFT JOIN es_product ON es_product_description.product_id = es_product.product_id WHERE es_product_to_category.category_id = $subcategory_id AND es_product.status=1";
//echo "<pre>";print_r($sql_subrec);
$sub_product = $conn->query($sql_subrec);
while ($prow = mysqli_fetch_assoc($sub_product)){
$subcategory_name[$subcategory_id]['products_name'][] = $prow['prod_name'];
}
}
echo "<pre>";print_r($subcategory_name);
}
I have a grid of img squares that can be dragged into any order using the sortable library. Each img is a visual representation of a result from a mySQL db query that selects any image that shares an 'imageparent' identifier. The order they're presented in the grid is taken from the 'imageorder' column in the database and starts at 0 and works in sequence up to the nth number of images returned.
The purpose of dragging the img grid is to be able to change the 'imageorder' index. On completion of the drag, the sortable library POSTS an 'imageorder' var by ajax to service.php and is received correctly. So rather than the original 0,1,2,3,4,5,6,7 order of the original, it sends a string like 2,1,0,3,4,5,7,6. Not too hard to grasp. After I switch the order the orderList var sent to service.php is always correct, but the array I end up sending to the db and setting as my session var becomes a little garbled in order after the second or third drag and I'm not quite sure why.
Code Examples and Comments
$_SESSION['selectedCsImages'] Array structure:
[0] => Array
(
[imagename] => "Title"
[imageorder] => 0
[imageid] => 43
)
[1] => Array
(
[imagename] => "Title"
[imageorder] => 1
[imageid] => 21
)
[2] => Array
(
[imagename] => "Title"
[imageorder] => 2
[imageid] => 3
)
etc...
Services.php extract:
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
// Turn the orderList posted into an array
$removeChars = array('"','[',']');
$orderList = str_replace($removeChars, "", $_POST['order']); // POST received fine.
$listArray = explode(",",$orderList);
// Retrieve the session array
$sorting = $_SESSION['selectedCsImages'];
/* My logic is that I compare the $sorting array to $listArray and reorder $sorting by 'imageorder' to match $listarray */
usort($sorting, function($a, $b) use ($listArray) {
return array_search($a['imageorder'], $listArray) - array_search($b['imageorder'], $listArray);
});
/* I now have a $sorting array that (sometimes, hence the problem) matches the order that the images had just been dragged into by the user. Typically, as I mentioned above, it's correct after the first drag, but not always after the second or third where it creates a new order that I can't see a pattern or logic in. */
/* Had there not been errors with the usort function, I (would) have a $sorting array in the order I want but with imageorder values referring to pre-sorting. I iterate through the array and set each key to 0, 1, 2, etc. so that I have an array in the correct order and with each imageorder correctly stating its place.*/
$i = 0;
foreach ($sorting as $key => $value) {
$sorting[$key]['imageorder'] = $i;
$i++;
}
/* The information is attempted to be sent to the db and, on success I update the session var */
// Database code (runs succesfully and updates the db as per the image orders found in the $sorting array)
$_SESSION['selectedCsImages'] = $sorting;
Debugging:
From debugging, it appears that something happens with the usort function when I call this page from ajax for the second or third time. Everything after this follows through fine and processes the correct or incorrect order as per expectations. The orderList var posted by sortable is correct each time. I'd provide a sample of the $sorted var after usort each time but it's as simple to describe it as the above array example in an order I didn't specify after dragging and I can't see a pattern in the seemingly random order it outputs.
From researching, I had thought that it was an issue with session vars being retained until the page is refreshed but it appears that the ajax call to services.php should refresh the $_SESSION['selectedCsImages'] var. I had also read that, perhaps, I was unknowingly using referenced array values and - as I source from a session var to a new array and, ultimately, save back to this session var from this array - I may have created some messy referencing feedback. However, I tried using $sorted = (array)clone(object)$_SESSION['selectedCsImages']; before attempting usort and the results didn't change.
PHP error logs are showing nothing.
Updates:
Per the suggestion of #Ayaou, I've checked the output of $listArray and am getting some unexpected results. I'd wrongly assumed that as the posted $orderList was correct, that the exploded array would not be a culprit.
Here's the output of print_r($listArray) after completing the following order swaps of 16 img elements: 1st with 2nd, 2nd last with last,6th with 7th:
1st and 2nd:
(
[0] => 1
[1] => 0
[2] => 2
[3] => 3
[4] => 4
[5] => 5
[6] => 6
[7] => 7
[8] => 8
[9] => 9
[10] => 10
[11] => 11
[12] => 12
[13] => 13
[14] => 14
[15] => 15
)
last and 2nd last:
(
[0] => 1
[1] => 0
[2] => 2
[3] => 3
[4] => 4
[5] => 5
[6] => 6
[7] => 7
[8] => 8
[9] => 9
[10] => 10
[11] => 11
[12] => 12
[13] => 13
[14] => 15
[15] => 14
)
6th with 7th:
(
[0] => 1
[1] => 0
[2] => 2
[3] => 3
[4] => 4
[5] => 6
[6] => 5
[7] => 7
[8] => 8
[9] => 9
[10] => 10
[11] => 11
[12] => 12
[13] => 13
[14] => 15
[15] => 14
)
I was progressing with the idea that $listArray would show a sequential 0,1,2,3,etc. each time with only the two swapped items showing order changes. As it's not, I'll look back again at $orderList and check if my sortable library is updating the orders it's obtaining correctly from the updated session var. Older order swaps are being retained somewhere along the chain where they shouldn't.
The solution is on your sortable form (on the front end), so instead of sending the imageorder on your 'order' post data, send the imageid index.
Then change your sort callback like this
//Use imageid index instead of imageorder
usort($sorting, function($a, $b) use ($listArray) {
return array_search($a['imageid'], $listArray) - array_search($b['imageid'], $listArray);
});
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
)
)