M.S
M.S

Reputation: 298

Random number with fixed average

I want to generate 100 random numbers between 1 and 10. But the average of those 100 random numbers should be 7. How can I do that? I am doing as follows:

//generating random number
Random random = new Random();
int value = random.Next(1,10);

And storing each value in an array. If the average of 100 items in the array is not 7 then I need to get another 100 random numbers. Can anyone suggest a better way of doing this?

Upvotes: 13

Views: 5551

Answers (10)

Gurgen Hakobyan
Gurgen Hakobyan

Reputation: 971

class Program
{
    static void Main(string[] args)
    {
        var rnd = new Random();
        var min = 1;
        var max = 20;
        var avg = 15;

        var count = 5000;

        var numbers = new List<int>();

        for (var i = 0; i < count; i++)
        {
            var random1 = rnd.Next(min, avg + 1);
            var random2 = rnd.Next(avg + 2, max + 1);
            var randoms = new List<int>();
            randoms.AddRange(Enumerable.Repeat<int>(random2, avg - min));
            randoms.AddRange(Enumerable.Repeat<int>(random1, max - avg));

            var generatedNumber = randoms[rnd.Next(randoms.Count)];
            numbers.Add(generatedNumber);
        }

        numbers = numbers.OrderBy(x => x).ToList();
        var groups = numbers.GroupBy(x => x).OrderByDescending(x => x.Count()).ToList();
        groups.ForEach(x => Console.WriteLine($"{x.Key}: {x.Count()}"));
        Console.WriteLine($"Average: {numbers.Average(x => x)}");
        Console.WriteLine($"Count of numbers: {groups.Count}");
    }
}

Upvotes: 0

Amin Golmahalleh
Amin Golmahalleh

Reputation: 4206

this function is for get fixed average between n Records randomly. that here in my answer "n" is declared as "count". https://github.com/amingolmahalle/RandomGenerateDataBetweenTwoNumber

public void ProccessGenerateData(WorkBook workBookData, out List<double> nomreList, out int adjustmentsVal)
{
    try
    {
        nomreList = new List<double>();
        adjustmentsVal = 0;           
        int count = workBookData.NumberStudents;
        double min = workBookData.Min;
        double max = workBookData.Max;               
        double target = workBookData.FixedAvg;
        double tolerance = workBookData.Tolerance;
        double minAverage = Math.Round(target - tolerance, 2);
        double maxAverage = Math.Round(target + tolerance, 2);

        Random r = new Random(DateTime.Now.Millisecond);
        List<double> listNomre = new List<double>();
        double sum = 0;
        for (int i = 0; i < count; i++)
        {
            double d = Math.Round(RangedDouble(min, max, r), 2);
            listNomre.Add(d);
            sum += d;
            sum = Math.Round(sum, 2);
        }

        int adjustments = 0;

        while (Math.Round((sum / count), 2) < minAverage || Math.Round((sum / count), 2) > maxAverage)
        {

            if (Math.Round((sum / count), 2) < minAverage)
            {
                double oldDbl1 = listNomre.First(d => d < minAverage);
                //min<a1+x1<max --> a1 is oldDbl1 , x1 --> Unknown
                double newDbl1 = Math.Round(oldDbl1 + RangedDouble(min-oldDbl1, max - oldDbl1, r), 2);

                listNomre.Remove(oldDbl1);
                sum -= oldDbl1;
                sum = Math.Round(sum, 2);
                listNomre.Add(newDbl1);
                sum += newDbl1;
                sum = Math.Round(sum, 2);
                adjustments++;
                continue;
            }
            double oldDbl = listNomre.First(d => d > maxAverage);
            //min<a1-x1<max --> a1 is oldDbl , x1 --> Unknown
            double newDbl = Math.Round(oldDbl - RangedDouble(oldDbl-max, oldDbl - min, r), 2);
            listNomre.Remove(oldDbl);
            sum -= oldDbl;
            sum = Math.Round(sum, 2);
            listNomre.Add(newDbl);
            sum += newDbl;
            sum = Math.Round(sum, 2);
            adjustments++;
        }

        nomreList = listNomre;
        adjustmentsVal = adjustments;     
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
        throw;
    }
}

private static double RangedDouble(double min, double max, Random r)
{
    //Function RangedDouble => Random Number Between 2 Double Numbers
    //Random.NextDouble => returns a double between 0 and 1
    return Math.Round( r.NextDouble() * (max - min) + min,2);
}

Upvotes: 0

Warbo
Warbo

Reputation: 2736

Like the other answers have posted, since we know the length we can get the average by just focusing on the total sum.

I'd solve it recursively. In the base case, we need to generate a list of length 1 which sums to some number s. That's easy: the list just contains s:

rand 1 s = [s]

Now we can solve the recursive case rand n s where n is the desired list length and s is the desired sum. To do this, we'll generate two lists x and y and concatenate them together, subject to the given constraints:

length x + length y = n
sum x + sum y = s
1  * length x <= sum x  -- Minimum value is 1
10 * length x >= sum x  -- Maximum value is 10
1  * length y <= sum y
10 * length y >= sum y

These equations/inequalities can't be solved yet, so the first thing we do is choose the length of the lists. To keep down the level of recursion we can choose lx = round (n / 2) then set the following:

length x = lx
length y = n - lx = ly

Hence:

sum x + sum y = s
1  * lx <= sum x
10 * lx >= sum x
1  * ly <= sum y
10 * ly >= sum y

We use the first equation to rewrite the inequalities:

1  * lx <= sum x
10 * lx >= sum x
1  * ly <= s - sum x
10 * ly >= s - sum x

We can rearrange the bottom two to make sum x the subject:

sum x + 1  * ly <= s
sum x + 10 * ly >= s

sum x <= s - 1  * ly
sum x >= s - 10 * ly

We know ly and s so these give us definite bounds for sum x, which we combine by taking the largest lower bound and the smallest upper bound:

max (1  * lx) (s - 10 * ly) <= sum x
min (10 * lx) (s - 1  * ly) >= sum x

These bounds make sense: they take into account the cases where every element in x is 1 or 10 and they ensure that the remainder can be handled by sum y. Now we just generate a random number B between these bounds, then set:

sum x = B
sum y = s - B

From that, we can perform our recursion (assuming some random number function randInt):

rand n s = let lx    = round (n / 2)
               ly    = n - lx
               lower = max (1  * lx) (s - 10 * ly)
               upper = min (10 * lx) (s - 1  * ly)
               b     = randInt lower upper
           in rand lx b ++ rand ly (s - b)

Now your list can be generated by calling:

myList = rand 100 700

I've written this in Haskell for brevity, but it's just arithmetic so should translate to C# easily. Here's a Python version if it helps:

def rand(n, s):
    if n == 1:
        return [s]
    lx    = int(n / 2)
    ly    = n - lx
    lower = max(1  * lx, s - 10 * ly)
    upper = min(10 * lx, s - 1  * ly)
    b     = randint(lower, upper)
    result = rand(lx, b)
    result.extend(rand(ly, s - b))
    return result

Please point out any mistakes I've made!

Edit: Although I doubt it's the case for C#, in some languages we could make this simpler and more efficient by using tail-recursion. First we switch to generating one element at a time:

-- Generate one number then recurse
rand 1 s = [s]
rand n s = let ly    = n - 1
               lower = max 1  (s - 10 * ly)
               upper = min 10 (s - 1  * ly)
               x     = randInt lower upper
            in x : rand (n - 1) s

Then we accumulate the result rather than building up unfinished continuations:

rand' xs 1 s = s:xs
rand' xs n s = let ly    = n - 1
                   lower = max 1  (s - 10 * ly)
                   upper = min 10 (s - 1  * ly)
                   x     = randInt lower upper
                in rand' (x:xs) (n-1) s
rand = rand' []

Upvotes: 0

Bardo
Bardo

Reputation: 2523

OK, it could be tricky to do something like that.

If you need to obtain 100 different numbers and you need that they average will be 7, you'll need them to sum 700.

You'll need to keep track of each number and their total sum. While 700 minus the sum of your obtained so far values is lesser than 10 * the amount of numbers you haven't obtained yet, you can continue obtaining pure random values.

When the moment comes that your obtained values sum is lesser than the values you need to obtain, then you change that last number by a 10, put a 10 in the rest of numbers you need to the end of your list and, on the last number, you get the difference between 700 and the sum of your 99 previous pseudo-random values.

Shuffle your array et voilá, you have a 100 pseudo-random array with numbers from 1 to 10 whose average is 7. Surely it will have more 10s than it'll be desired, but sure you'll be able to fine tune this "algorithm" to make it a bit less 10 prone.

Mmmm, wait a moment, what if you get random values that have an average above 7? You'll need to track also that the sum of your current values is lesser than the numbers you have yet to obtain. If you surpass this value in any moment you'll need to convert your last number to a 1, put a 1 on the rest of your needed values and obtain again your last number as the difference between 700 and your 99 earlier values.

Upvotes: 0

Timothy Shields
Timothy Shields

Reputation: 79541

  1. Initialize A[0], ..., A[99] to 1.
  2. Initialize I = {0, 1, ..., 99}.
  3. Repeat steps 4-6 600 times.
  4. Pick random i uniformly from I.
  5. Increment A[i].
  6. If A[i] == 10, then remove i from I.

This will guarantee sum(A) is 700 and thus avg(A) is 7.

Note however that this does not give a uniform distribution over all such arrays of 100 integers in {1, ..., 10} such that they sum to 700. To devise an algorithm for uniformly sampling would be a much more challenging exercise.

Upvotes: 3

Steven Liekens
Steven Liekens

Reputation: 14113

edit: changing the code to always result in an average of exactly 7.

This is basically an optimized version of what you were doing already. Instead of generating another 100 numbers, it generates only 10 before doing the check.

using System.Collections.Generic;
using System.Linq;

var r = new Random();
var numbers = new List<int>();

while (numbers.Count < 100)
{
    var stack = new Stack<int>();
    for (int i = 0; i < 10; i++)
    {
        stack.Push(r.Next(10));
    }

    if (stack.Sum() == 70)
    {
        numbers.AddRange(stack);
    }
}

Console.WriteLine(numbers.Average());

Upvotes: 0

James
James

Reputation: 9985

My 2 cents

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Stopwatch watch = Stopwatch.StartNew();

            int count = 100;
            Double min = 0;
            Double max = 10;
            Double target = 7;
            Double tolerance = 0.00000001;
            Double minAverage = target - tolerance;
            Double maxAverage = target + tolerance;

            Random r = new Random();
            List<Double> numbers = new List<double>();
            Double sum = 0;
            for (int i = 0; i < count; i++)
            {
                Double d = RangedDouble(min, max, r);
                numbers.Add(d);
                sum += d;
            }


            int Adjustments = 0;

            while((sum / count < minAverage || (sum / count) > maxAverage))
            {
                while ((sum / count) < minAverage)
                {
                    Double oldDbl = numbers.First(d => d < minAverage);
                    Double newDbl = oldDbl + RangedDouble(minAverage - oldDbl, 10 - oldDbl, r);

                    numbers.Remove(oldDbl);
                    sum -= oldDbl;
                    numbers.Add(newDbl);
                    sum += newDbl;
                    Adjustments++;
                }

                while ((sum / count) > maxAverage)
                {
                    Double oldDbl = numbers.First(d => d > maxAverage);
                    Double newDbl = oldDbl - RangedDouble(oldDbl - maxAverage, oldDbl, r);

                    numbers.Remove(oldDbl);
                    sum -= oldDbl;
                    numbers.Add(newDbl);
                    sum += newDbl;
                    Adjustments++;
                }
            }
            watch.Stop();

            int x = 0;
            while (x < count)
            {
                Console.WriteLine("{0:F7}  {1:F7}  {2:F7}  {3:F7}", numbers.Skip(x).Take(1).First(), numbers.Skip(x + 1).Take(1).First(), numbers.Skip(x + 2).Take(1).First(), numbers.Skip(x + 3).Take(1).First());
                x += 4;
            }

            Console.WriteLine();
            Console.WriteLine(watch.ElapsedMilliseconds);
            Console.WriteLine(numbers.Average());
            Console.WriteLine(Adjustments);
            Console.ReadKey(true);
        }

        private static double RangedDouble(Double min, Double max, Random r)
        {
            return (r.NextDouble() * (max - min) + min);
        }
    }
}

And the output:

8.1510368 7.2103030 7.9909210 9.6693311
8.2275382 7.2839244 8.8634567 7.9751014
7.8643791 7.2262462 9.8914455 9.6875690
8.4396683 8.4308401 7.5380218 8.6147181
8.2760663 7.7399011 7.4312152 9.2115622
9.7850111 9.1061378 9.8672965 9.5610411
7.0415607 8.8446195 9.3562218 8.5279759
7.5227340 9.3572417 9.8927997 9.5880645
9.0908564 7.0918394 9.6213258 8.6528169
9.3803283 9.6869223 1.4006790 3.3310691
7.0719214 2.6370854 9.7558776 8.9180391
3.0486700 5.0082988 8.8624504 5.0497899
0.9692377 7.7140550 9.8495115 6.4933865
4.4939760 9.3187625 5.4353003 6.5405668
9.5693118 5.0339998 6.9644440 4.6902072
0.5241568 9.7748420 0.1406617 8.4731427
9.8064604 6.3113773 0.8628048 9.2417028
8.9148867 9.3111336 3.2424080 9.6710544
4.3794982 5.1687718 9.8207783 0.3283217
9.8321869 2.8093698 7.4377070 4.1130959
5.9840738 9.2560763 3.6691865 2.5498863
7.3242246 7.0179332 5.8906831 9.3340545
0.3735044 7.2442886 0.4409532 9.0749754
9.6716409 8.4097246 2.8069123 7.2970794
2.4964238 8.2826350 9.1115787 3.7373927

1
6.99992266645471
729

Upvotes: 0

Aron
Aron

Reputation: 15772

public int RandomNumberThatAveragesToSeven()
{
    //Chosen by fair dice roll
    //Guaranteed to be random
    return 7;
}

Without additional parameters, this above algorithm satisfies each and every requirement.

  1. Return must be between 1 and 10
  2. Average of multiple calls must tend to 7 as n tends to inf.

EDIT Since there was so much controversy on this answer...I added this answer...which is definitely random.

public List<int> ProduceRandom100NumbersWithAverageOfSeven()
{
    var rand = new Random();
    var seed = rand.Next();
    if(seed > 0.5)
    {
        return new List(Enumerable.Concat(
                 Enumerable.Repeat(6, 50),
                 Enumerable.Repeat(8, 50)));
    }
    else
    {
        return new List(Enumerable.Concat(
                 Enumerable.Repeat(8, 50),
                 Enumerable.Repeat(6, 50)));

    }
}

Upvotes: 4

Stephen Walker
Stephen Walker

Reputation: 574

Something like this might do it:

public static void Main(string[] args)
    {
        var randomList = new List<int>();
        var random = new Random();
        var avg = 0;
        while (avg != 7)
        {
            randomList = new List<int>();
            GenerateList(randomList, random);
            avg = (int) randomList.Average();
        }

        for (var i = 0; i < randomList.Count; i++)
        {
            Console.WriteLine(string.Format("Index: {0}, Number: {1}", i, randomList.ElementAt(i)));
        }
    }

    private static void GenerateList(List<int> refList, Random random)
    {
        for (var i = 0; i < 100; i++)
        {
            refList.Add(random.Next(1, 10));
        }
    }

Upvotes: 0

SimonPJ
SimonPJ

Reputation: 766

This method generates a random number sequence then keeps adding/subtracting until we get the correct total (700), so long as the number we are altering is still in the range of 1-10

List<int> randomNumbers = new List<int>();
for (int i = 0; i < 100; i++) {
    numbers.Add(r.Next(1, 10));
}

int total = randomNumbers.Sum();

// Now fiddle until we get the correct total (700)

if (total < 700) {
    while (total < 700) {
        for (int i = 0; i < 100; i++) {
            if (numbers[i] < 10) {
                numbers[i]++;
                total = randomNumbers.Sum();
                if (total == 700) break;
            }
        }
    }
}

else if (total > 700) {
    while (total > 700) {
        for (int i = 99; i >= 0; i--) {
            if (numbers[i] > 0) {
                numbers[i]--;
                total = randomNumbers.Sum();
                if (total == 700) break;
            }
        }
    }
}

Upvotes: -1

Related Questions