+3  A: 

The simplest way to do this would be to provide 'bands' for where a random number should go. In your example, you have 15 players so you could have:

rand < 1/15, highest strength
1/15 < rand < 3/15, second highest
3/15 < rand < 6/15, third highest
6/15 < rand < 10/15, fourth highest
10/15 < rand < 15/15, lowest strength

You could also parameterise such a function with a 'max' number of each band that you allow and when the band is filled, it is subsumed into the next lowest existing band (apart from the bottom band, which would be subsumed into the next highest) to ensure only a certain number of each with a random distribution.

Edit adding from my comments:

To get a floating range pyramid structure the best function would most likely be a logarithm. The formula:

11 - log10(rand)

would work (with log10 being a logarithm with base 10) as this would give ranges like:

1 < rand < 10 = 9 < strength < 10
10 < rand < 100 = 8 < strength < 9
100 < rand < 1000 = 7 < strength < 8
etc.

but rand would need to range from 1 to 10^10 which would require a lot of randomness (more than most random generators can manage). To get a random number in this sort of range you could multiply some together. 3 random numbers could manage it:

11 - log10(rand1 * rand2 * rand3)

with rand1 having range 1-10000 and rand2 and rand3 having range 1-1000. This would skew the distribution away from a proper pyramid slightly though (more likely to have numbers in the centre I believe) so it may not be suitable.

workmad3
Thank you very much! This would probably work fine. But I've forgotten to mention that I actually need floating strength from 1.1 to 9.9. So a function should be better.
I think you may need to play around with logs then. log10(rand) where rand is a random number between 1 and 10^10 would work, but you may have an issue with decent random numbers in such a large range
workmad3
Sorry, the exact formula for what you'd want would really be: 11 - log10(rand) with the same range as before. If you don't mind skewing the curve slight, what you could do is multiply 3 random numbers together though. 11 - log10(rand1 * rand2 * rand3) with rand1 ranging from 1 - 10000 and rand 2 and rand3 ranging from 1 - 1000. This would give the correct range but a skewed distribution.
workmad3
Thank you! This is what I thought of. But unfortunately, the distribution of the strength values is too extreme: 20392, 21672, 6523, 1210, 187, 15, 1 - this is what I get in 50,000 runs.
Guess the distribution is extremely skewed then :)
workmad3
A: 

generate a random number between 0 and 40000, if its between 0 and 12000, assign strength 1, between 12000 and 22500 assign 2 etc.

Edit: for progressive values between 0 and 10 use the square root of a random number between 0 and 100, then substract if from 10

  • rand -> strengh
  • 0-1 -> 9.9 -> 9 (1%)
  • 2-4 -> 9 -> 8 (2%)
  • ...
  • 81 - 100 -> 1 - 0 (19%)

For results between 1.1 and 9.9 the formula would be in pseudocode)

strength = 10 - sqrt(rand(1..79))

Ben Schwehn
Thank you. Is there also a possibility to create floating, progressive strengths? I actually need strengths from 1.1 to 9.9 which should have the pyramid form.
I edited my answer. The square root should be the correct formula, as in your triangle the area is proportional to the square of the hight
Ben Schwehn
Sounds good, but your function doesn't give valid strength values up to 9.9. This function would be better I think: 10.9-sqrt(1...96)
Even the corrected function doesn't deliver good values. They're poorly distributed. In 50,000 runs I get: 8975, 8965, 7838, 6864, 5214, 4835, 3638, 2627, 1044
A: 

something like this

<?php

$rand = rand(1,10);

switch ($rand) {

case 1:
echo "band 1";
break;

case 2:
case 3:
echo "band 2";
break;

case 4:
case 5:
case 6:
echo "band 3";
break;

default:
echo "band 4";
break;

}

?>

Band 1 being the strongest, band 4 being the weakest.

Ofcourse this is basic, you would want to refactor this to use loops instead of hardcoded switches, but you get the idea :)

Ozzy
+2  A: 

workmad3 has the start of it down, I think, but there's a catch - you need to track your bucket sizes and whether or not they're full. A random number generator won't guarantee that. You'll need to assign your bucket values (strenghs) and sizes (number of people), and let your random generator tell you which bucket to drop the player into - if that one is full, 'overflow' to the next lower.

As to assigning the bucket sizes for a given strength value, that's the tricky bit (and I think what you're really working at). The characteristics of your desired distribution are critical. If you want a linear drop (which the pyramid shape hints at), a line of the form

strength = max_strength - m(number_characters)

would work. Varying the value of m would change the speed at which the line drops off, and will basically limit your max number of total characters. If you're looking for a more sophisticated way for the strength values to drop off, you could use a parabolic or hyperbolic curve - these are a bit more complex, but give you very different characteristics.

Harper Shelby
Sorry, I think my description is a bit unclear. I only want to create 1 character/player if a new user signs up. Te pyramid form should emerge from all players which together. So when 7,000 users have signed up, their strength distribution should look like a pyramid. Better described now?
A: 

It's probably easiest to use percentages in this case.

From your examples would approximately be (converted to an array for ease of use later):

$strength[1] = .3;  // start with a key of 1
$strength[2] = .26;
$strength[3] = .21;
$strength[4] = .15;
$strength[5] = .08;

That way, you can generate a random number using mt_rand() and divide by the maximum possible value to get a number between 0 and 1:

$rand = mt_rand() / mt_getrandmax(); // rand is some random value between 0 and 1

Then you can use a foreach statement to isolate each case:

$comparisonPercentage = 1;
$selectedLevel = count($strength); // covers the case where mt_rand() returns 0
foreach($strength as $level => $currentPercentage) 
{
    $comparisonPercentage -= $currentPercentage;
    if ($rand >  $comparisonPercentage) 
    {
        $selectedLevel = $level;
        break;
    }
}

// $selectedLevel contains the level you need...

If you do it this way, you only have to change the $strength array if you need to fiddle with the percentages.

John Rasch
Aha, you've edited the question - so this probably won't help since you'd have to create 100 different strength levels! There is a mathematical function for what you want I just have to find it...
John Rasch
I've added another answer that accounts for your update to the question. I would have updated this one, but someone may need this in the future for a similar problem
John Rasch
Thanks, you're right, this could be helpful in other cases.
+4  A: 

You can simulate a distribution such as the one you described using a logarithmic function. The following will return a random strength value between 1.1 and 9.9:

function getRandomStrength() 
{
    $rand = mt_rand() / mt_getrandmax();
    return round(pow(M_E, ($rand - 1.033) / -0.45), 1);
}

Distribution over 1000 runs (where S is the strength value floored):

S | Count
--+------
1 -  290
2 -  174
3 -  141
4 -  101
5 -  84
6 -  67
7 -  55
8 -  50
9 -  38

Note:

This answer was updated to include a strength value of 1.1 (which wasn't included before because of the rounding) and to fix the name of the mt_getrandmax() function

John Rasch
Thank you very very much! This is exactly what I was looking for. I started the same test with 50,000 runs and I got: 13404, 9356, 6565, 5051, 4113, 3435, 3048, 2643, 2385