Logarithmic Scale - Ranking in PHP - php

I am currently building a system that requires ranking based on an internal actions/score. The score itself can range from anywhere within 0 to 20000.
I need the function I create to rank users to return a value between 1-100. If this is impossible it would be fine to return any result sets over 100 as just 100. I am currently having difficulties ensuring creating a logarithmic algorithm for determining scores.
I have tried the following function:
echo 1 + sqrt(500 + 2000 * $score) / 50;
However the results this return do not vary enough for lower values and increase exponentially for higher ones.
Example input scores at low/average end of the scale are:
0.15
1
7
12
Example input at high end of the scale
236.4
1211
17899.70
Any help would be greatly appreciated. Been stuck on this for a few days now. The above function is the best attempt I have so far however as advised there is not enough disparity between the low/medium results and too much between higher ones.
Thanks,
Daniel

This is an open-ended question and there is no clear answer with different tradeoffs.. I'm not a PHP programmer, but something like the following should work.
# THESE PARAMETERS CONTROL THE RANKING ALGORITHM.
$rescale = 0;
$spread = 1;
function rescore ($n) {
return log($n) + $rescale;
}
function rank ($scores) {
return 100 / (1 + exp(- $spread * array_sum(array_map("rescore", $scores))));
}
You should choose $rescale so that the average score rescores to something close to 0. And play around with $spread until you're happy with how much your scores spread out.
The idea is that log turns a wide range of scores into numbers in a comparable range that can be positive or negative. Add a bunch of rescored scores together and you get an arbitrary real number. Then throw that into a logistic function (see https://en.wikipedia.org/wiki/Logistic_function for details) to turn that into a number in your desired range.

Attempting to closely follow a definition in Wikepedia where "each mark on the [logarithmic] scale is the previous mark multiplied by a value," we set value^100 = 20000 and arrive at value = 1.104104805. Could this logarithmic scale then be implemented with a function akin to the following?
function rank($score){
return log($score,1.104104805);
}
Output:
$scores = [0.15,1,7,12,236.4,1211,17899.70];
foreach ($scores as $score){
echo "Score: " . $score . ", Rank: " . rank($score) . "\n";
}
/*
Score: 0.15, Rank: -19.156079885479
Score: 1, Rank: 0
Score: 7, Rank: 19.648736275112
Score: 12, Rank: 25.091228109202
Score: 236.4, Rank: 55.187884698844
Score: 1211, Rank: 71.683855953272
Score: 17899.7, Rank: 98.879704658993
*/

thanks for the help. Here is what I ended up with (thanks to the help of Jake L Price)
The score varied slightly after the initial question (with 120,000 being the top end of the scale) however the logic for the algorithm needed to remain the same. As you can see below we used log * 10 to get a comprehensible low number. We then multiply this again by a number which ensures 120,000 has the top level of 100.
echo $this->rank($score);
public function rank($score)
{
//-- log reg time
// 7.143963378055477 being the numberic multiplier to ensure 120,000 has the score of 100.
$res = 7.143963378055477 * log($score * 10);
return $res;
}
This is now returning
$scores = [0.15,1,7,12,236.4,1211,17899.70, 120000];
foreach ($scores as $score){
echo "Score: " . $score . ", Rank: " . $this->rank($score) . "</br>";
}
Output:
Score: 0.15, Rank: 2.896627883404
Score: 1, Rank: 16.449583579206
Score: 7, Rank: 30.351094421044
Score: 12, Rank: 34.201665683178
Score: 236.4, Rank: 55.495096060882
Score: 1211, Rank: 67.166020848577
Score: 17899.7, Rank: 86.407125230156
Score: 120000, Rank: 100

Related

How to add between two numbers +1 ticket

i have database list for users and user's coupons and i want to add +1 ticket per coupon if bigger than two between numbers.
For example:
100 between 200 = 1 ticket
200 between 300 = 2 ticket
300 between 400 = 3 ticket
.......
.......
1200 between 1300 = 12 ticket
i put photo for example:
My Code is :
$q=$db->query("SELECT DISTINCT client_id FROM kuponlar ORDER BY client_id LIMIT 20");
foreach($q as $cat){
echo '<li id="'.$cat['client_id'].'" class="files">';
echo 'User ID: '.$cat['client_id'].'';
echo '<ul class="sub-menu">';
$linkq=$db->query("SELECT * FROM kuponlar WHERE client_id='" . $cat['client_id'] . "'");
foreach($linkq as $link){
echo '<li>Coupon ID: '.$link['kuponid'].' - Coupon Price: '.$link['yatirimi'].' ₺ / Won Ticket: '.substr($link['yatirimi'], 0, 1).' </li>';
}
echo '</ul></li>';
}
As discussed in the comments;
So if the number is 12 000, you want 120 as a result? Basically divide by 100? – Qirel
Yes #Qirel this is perfect comment. i mean like this. (...) – Ismail Altunören
So put simply, you want to divide the number by 100. You must then floor it, to get a full integer and get rid of any decimal points.
floor($link['yatirimi']/100);
You would replace that with your substr(), making the full line
echo '<li>Coupon ID: '.$link['kuponid'].' - Coupon Price: '.$link['yatirimi'].' ₺ / Won Ticket: '.floor($link['yatirimi']/100).' </li>';
floor() docs
You can do this using floor():
http://php.net/manual/en/function.floor.php
floor — Round fractions down
So this should do the trick: floor($link['yatirimi'] / 100)
Use that in place of your substr.
you can user this: $wonTicketsCount = round(($link['yatirimi'] / 100 ) - 0.5); instead substr($link['yatirimi'], 0, 1).
Refering to your comments if you only use hundreds steps :
Modify this line :
substr($link['yatirimi'], 0, 1)
This line will always take the first number.
By : this one
This will take all numbers excepts the two lasts.
substr($link['yatirimi'],0,-2);
By keeping the substr, it will not work for number between 0-100,
It's better to use #Qirel's Anwser.

Weighted random with two weights

In PHP I need to pick a random number between 1 and 100 with two weights. These weights can also be between 1 and 100. If both weights are low I would need the random number weighted low, high weighted high. If one weight is high and one is low, or if they are both mid ranged, I would expect random number to be weighted random around the 50s.
I'm not sure the best way to go about this. Any advice would be great!
You would need more information for those weights: how heavy do they weigh on the probability for that index, and how much does that probability increase spread around to neighbouring indexes.
I will here assume that these parameters can be provided, and that the probabilities spread around with a normal distribution.
I would suggest to create an array with the probabilities for each of the indexes. You start out with a constant (e.g. 1) for each of them, which means all indexes have the same probability of being selected.
Then a function could apply one weight to it, given an index where the weight should be centred at, the weight itself (how much does it increase the existing "weight" at that index), and the spread (the standard deviation of the normal distribution with which the generated probability will be spread around).
Here is the code that does such a thing. It is not intended to be statistically sound, but I believe it will do the job in a satisfactory way:
function density($x, $median, $stddev) {
// See https://en.wikipedia.org/wiki/Probability_density_function#Further_details
return exp(-pow($x - $median,2)/(2*pow($stddev,2))/(2*pi()*$stddev));
}
function homogeneous_distribution($size) {
return array_fill(0, $size, 1);
}
function add_weight(&$distr, $median, $weight, $spread) {
foreach ($distr as $i => &$prob) {
$prob += $weight * density($i, $median, $spread);
}
}
function random_float() { // between 0 and 1 (exclusive)
return mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax();
}
function weighted_random($distr) {
$r = random_float() * array_sum($distr);
foreach ($distr as $i => $prob) {
$r -= $prob;
if ($r < 0) return $i;
}
}
// Example use with 20 instead of 100.
$distr = homogeneous_distribution(20); // range is 0 .. 19
add_weight($distr, 0, 4, 1); // at index 0, put a weight of 4, spreading with 1
add_weight($distr, 16, 8, 0.5); // at index 16, put a weight of 8, spreading with 0.2
// Print distribution (weights for every index):
echo "DISTRIBUTION:\n";
print_r($distr);
// Get 10 weighted random indexes from this distribution:
echo "RANDOM SAMPLES:\n";
foreach (range(0, 10) as $i) {
echo weighted_random($distr) . "\n";
}
See it run on rextester.com

PHP likely chance

Okay, so i don't really know how I go about this.
I'm currently working on a lottery system for a game.
I have a table with virtual items which I want to randomly select by a likely chance.
Table examples:
ID = 1, item_name = Sword, likely_chance = 75
ID = 2, Item_name = 10,000, likely_chance = 20
For id 2, 10,000 represents 10,000 coins.
I want to come up with an algorithm which will select a item with a higher chance of selecting a higher likely chance but also still be able to win a item with a lower likely chance rarely.
If you have items with "likely chances" of C1, C2, C3...Cn, then you can calculate the sum Ctotal.
Then, you can get a random value between 0 and Ctotal, and walk through your array (order is irrelevant) until the sum of "skipped" items exceeds this random value.
For example, in your case, Ctotal = 75 + 20 = 95. Get a random number between 0 and 95, and if it is less than 75 - give a sword; else - give 10000 coins. It will provide a fair winnings distribution according to your likely chances - 78.95% and 21.05%, respectively.
$items = ['Sword', '10000 coins'];
$chances = [70, 25];
$ctotal = array_sum($chances); echo "Total is $ctotal\n";
$rand = rand(0, $ctotal); echo "Rand is $rand\n";
$i = 0;
$currentSum = 0;
while (true)
{
$currentSum += $chances[$i];
if ($currentSum >= $rand)
{
echo "You win: ".$items[$i];
break;
}
$i++;
}
Here is the working Demo. Note that IDEOne remembers the last output and doesn't run this program again every time. The output will appear to be the same, but it is not.

How do I calculate the answer of a exponential formula?

we have to make a web game for a school project.
but im currently stuck at one point. its a mafia styled web game, you probably know one of these. When your charachter goes to the hospital to get his wounds fixed up he needs to pay a certain amount of money. this is calculated by the following code
$maxHeal = 100 - $health;
$costs =round(pow($maxHeal,1.8));
health is a number between 0 and 100 and the costs are based on exponential growth. but if the player can only afford 50 but types in 100, how do I make sure he only getw 50, and how do I make sure it are the first 50 health points, the most expensive ones and not the cheap ones, this will cause player to just type in 1 press enter to get some cheap health.
I hope my porblem is clear, if you have any questions about other parts of the codes please ask
thanks in advance
edit: to give some extra clearance,
when I am at 10 health(hp) and I want to go back to 100hp I need to get a 90 extra. there is a form where I can type how much hp I want to cure, so I type in 90 and the system requests to ad 90 to my life so it makes 100. to do this I need to check if the players can afford to pay for those 90 points. if I can not pay for 90 but can pay for 50 I want those 50 to be added anyways. but if I count from 1 to 50 and to one form 40(the remaining I need to heal another timer) it wil cost less than counting from 1 to 90 because of the exponential growth.
so I need 2 checks. I have to cure al I can afford, so if I can afford just 50 of the 90 hp I need, I will only get 50 points and pay for 50, but as this wil be cheaper how can I make sure that I pay for the 50 like I woulld pay for 90. so 50 and 40 need to be equal to one times 90
Based on your question (which is not totally clear to me, but hey, I'm in a good mood), I built the following example:
//total amount of health points
$points = 20000;
//health left
$health = 10;
//how many health do we miss? 100 = maximal
$maxHeal = 100 - $health;
//counter
$i = 0;
while($points > $cost = round(pow($maxHeal-$i,1.8))) {
//check if the user has enough points
if ($points - $cost > 0) {
$health++;
echo "Healt +1 point, total: " . $health . " (points " . $points . " - cost " . $cost . " = " . ($points - $cost) . " left)" . "\n";
} else {
echo "Can't heal anymore, not enough points left (" . $points . ")" . "\n";
}
$points -= $cost;
$i++;
}
echo "\n";
echo "Remaining points: " . $points . ", needs points for next repair: " . $cost;
With the following output:
Health now: 10
Healt +1 point, total: 11 (points 20000 - cost 3293 = 16707 left)
Healt +1 point, total: 12 (points 16707 - cost 3228 = 13479 left)
Healt +1 point, total: 13 (points 13479 - cost 3163 = 10316 left)
Healt +1 point, total: 14 (points 10316 - cost 3098 = 7218 left)
Healt +1 point, total: 15 (points 7218 - cost 3035 = 4183 left)
Healt +1 point, total: 16 (points 4183 - cost 2971 = 1212 left)
Remaining points: 1212, needs points for next repair: 2909
I hope this gets you going :)

Generating a random number but with a twist

I need to generate a random number.
But the twist is that the % of lower numbers should be greater than the higher.
For example.
rand > 1 to 100
13,
15,
12,
16,
87,
15,
27,
12,
1,
12,
98,
12,
53,
12,
14....
The bold integers will be the ones return from the 1-100 range.
The math should be like so rand = a number lower than max/2
Hope you guys can help.
Ps, How would a matrix come into this ? im not superior at maths :(
The abs answer seems to be the one.
$up = $down = 0;
while(true)
{
if(abs((rand()%150)-50) < 50)
{
$up++;
}else
{
$down++;
}
if( ($up + $down) == 500){ break;}
}
echo $up . '/' . $down;
how about
n = abs((rand()%150)-50)
$x = rand(0,1) ? rand(1,100) : rand(1,50);
Simple method: the first rand(0,1) selects between the two cases. Either 1..50 or 1..100 as random range. Since 1,100 already encompases 1,50, the latter range is selected 100% of the time, the former case only in 1 of 2 runs.
If you want a distribution where the highest numer 99 gets selected almost never, but the lower numbers 1..20 pretty frequent, then a simple rand(1,rand(1,100)) would do.
$rand = (rand() * rand()) / (getrandmax() * getrandmax());
This will give you a random number between 0 and 1 with a high probability of falling into the lower end of the range and the probability of a larger number decreasing exponentially. You can then scale this result to any range that you want.

Categories