tags:

views:

536

answers:

6

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
+29  A: 

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); }
    }
}
plinth
Ah, you beat me to it with this answer. +1.
Splash
This is fine for relatively small numbers, but if you want a distribution centered on 1000000, you need a really big array... I think it would be better to use a distribution function.
Thomas Levesque
8 was an example, in fact we i have :int deep instead of 10, and nothing instead of 8 (i mean it be near to 8 or 7 or 9)
Vahid.m
You should go ahead and write that - there's room for more answers.
plinth
Wow! It is so simple that I doubtfully to hit that =)
Rorick
I would also suggest moving the r=new Random() declaration out of the method because dot net has a habit of being less random than we'd expect with a newly instantiated random object.
grenade
Thanks, but using 10*cos(random number) , helps me.
Vahid.m
Same answer as me. Though I agree with the comment on large arrays
zebrabox
@Vahid - cosine is an odd choice - it will weight more at 0 (since cos(0) is 1). You probably would want to use sine and scale the theta between 0 and pi/2.
plinth
I think you still need to add a "tensor" parameter between the min and max that acts as the desired center of distribution. I imagine using it could be as "simple" as (using `R = (max - min)`) : 'min + (((random + ((tensor - min) / R)) * R ) % R)'. Something like that will shift your distribution curve over the desired number.
Erich Mirabal
A: 

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)

Splash
No , it's not fair, but it's important.
Vahid.m
+2  A: 

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

Thomas Levesque
WTF is a polynom? Methinks you went for the "Post your answer" button too quickl
paxdiablo
a polynom is an algebraic function written in the form:f(x) = an*x^n + (an-1)*x^(x-1) + ... a2*x^2 + a1*x + a0;
AZ
No, that would be a *polynomial*. I see no-one here appreciates my (oft-strange, according to she who must be obeyed) humor. I think I'll just wander off to bed.
paxdiablo
Pax, I laughed at your 'too quickl' in-joke. You may be a fool, but at least you are not the only one.
Erich Mirabal
@Pax : I meant a polynomial... sorry, my english is not so good ;)
Thomas Levesque
in some languages the concept is named "polinom" and it's easy for non native english speakers to use it instead of polynomial
AZ
+2  A: 

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).

schnaader
+1  A: 

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.

balpha
+1  A: 

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:

  • Rolling 4 dice and discarding the lowest. This skews the distribution towards higher numbers.
  • Averaging the scores rather than adding them. This makes the output range easier to understand.

Alternatively, this is pretty close...

Generic Error