Reputation: 185
I'd like to have a random number like this:(in C#)
Random r = new Random();
r.next (0,10)
BUT it's important to the random number be more near 8,(or it be usually big), I mean if we use a for:
for (int i =0; i<...;i++)
{
write: r.next (0,10)
}
the result be like this;
8 7 6 9 1 0 5 3 2
2 3 8 9 7 7 6 2 3
8 8 9 7 2 8 2 8 4
3
Upvotes: 7
Views: 1397
Reputation: 49729
Instead of using the array variant, you could also have a look at this SO answer which has a link to Math.NET Iridium that implements non-uniform random generators.
The advantages to the array variant are that you get a more dynamic approach without having to rewrite the array all the time. You could also do some things that would be practically impossible with the array variant (big non-uniform random numbers).
Upvotes: 2
Reputation: 292345
You need a distribution function that takes a number between 0 and 1 and converts it to a number in the range you want, with a higher weight on a specific number. You could create such a function with trigonometric functions (sin, cos, ...), exponential, or maybe a polynomial.
UPDATE: Have a look at this page for more information on probability distribution
Upvotes: 2
Reputation: 49179
You need to weight your results. You can do that with something like this:
private int[] _distribution = new int[] { 0, 1, 2, 3, 4, 5, 6, 6, 7, 7, 8, 8, 8, 9, 9 };
Random _r = new Random();
public int GetWeightedRandom()
{
return _distribution[_r.Next(0, _distribution.Length)];
}
If I knew my range was small and consistent, I'd use the table - it's trivial to make it its own class.
For completeness, I'll also add this class in. This class borrows from image processing and uses the gamma correction function: a value between 0 and 1 raised to gamma, which returns a value between 0 and 1 but distributed more to the low end if gamma < 1.0 and more to the high end if gamma > 1.0.
public class GammaRandom {
double _gamma;
Random _r;
public GammaRandom(double gamma) {
if (gamma <= 0) throw new ArgumentOutOfRangeException("gamma");
_gamma = gamma;
_r = new Random();
}
public int Next(int low, int high) {
if (high <= low) throw new ArgumentOutOfRangeException("high");
double rand = _r.NextDouble();
rand = math.Pow(rand, _gamma);
return (int)((high - low) * rand) + low;
}
}
(from comments, moved r out of GetWeightedRandom(). Also added range checking to Next())
OK, let's really go to town here. I'm channeling John skeet for this - it's an abstract class with a template property that returns a transform function that maps the range [0..1) to [0..1) and scales the random number to that range. I also reimplemented gamma in terms of it and implemented sin and cos as well.
public abstract class DelegatedRandom
{
private Random _r = new Random();
public int Next(int low, int high)
{
if (high >= low)
throw new ArgumentOutOfRangeException("high");
double rand = _r.NextDouble();
rand = Transform(rand);
if (rand >= 1.0 || rand < 0) throw new Exception("internal error - expected transform to be between 0 and 1");
return (int)((high - low) * rand) + low;
}
protected abstract Func<double, double> Transform { get; }
}
public class SinRandom : DelegatedRandom
{
private static double pihalf = Math.PI / 2;
protected override Func<double, double> Transform
{
get { return r => Math.Sin(r * pihalf); }
}
}
public class CosRandom : DelegatedRandom
{
private static double pihalf = Math.PI / 2;
protected override Func<double, double> Transform
{
get { return r => Math.Cos(r * pihalf); }
}
}
public class GammaRandom : DelegatedRandom
{
private double _gamma;
public GammaRandom(double gamma)
{
if (gamma <= 0) throw new ArgumentOutOfRangeException("gamma");
_gamma = gamma;
}
protected override Func<double, double> Transform
{
get { return r => Math.Pow(r, _gamma); }
}
}
Upvotes: 29
Reputation: 4477
Not exactly what you are looking for but a very simple way to approximate a normal distribution of numbers is by adding multiple generations together.
A classic example of this technique is in the game Dungeons and Dragons where a characters strength might be determined by rolling three six sided dice and adding the results. This gives a range of 3 to 18 with numbers around 10 the most likely. Variants include:
Alternatively, this is pretty close...
Upvotes: 1
Reputation: 50898
With some kind of additional weighting that should be possible. Depends on how you specify "near eight". A very simple way to do it is this:
for (int i =0; i<...;i++)
{
n = r.next (0,100);
write: (n*n) / 1000
}
The squaring will weigh the numbers towards the low end, i.e. in this case, 33% of the time you'll get a 0
, while you'll get a 9
only about 5% of the time.
This method of course be adapted to fit the particular case.
Upvotes: 1
Reputation: 12821
It looks to me like you want your random numbers to be weighted towards the high end - would this be a fair assessment?
Something like this may help you (it's Java, but the principles apply)
Upvotes: 0