Генерация серии случайных чисел, которые складываются в N в c # - PullRequest
14 голосов
/ 23 января 2009

Как мне сгенерировать 30 случайных чисел от 1 до 9, которые в сумме дают до 200 (или несколько произвольных N) в C #?

Я пытаюсь сгенерировать строку цифр, которая может складываться вместе, чтобы быть N.

Ответы [ 16 ]

10 голосов
/ 23 января 2009

Я не уверен, что статистика по этому вопросу, но проблема здесь в том, что вы не хотите случайным образом выбирать число, которое делает невозможным суммирование N с M количеством записей, либо путем перехвата, либо из-за выстрела. Вот как бы я это сделал:

static void Main()
{
    int count = 30;
    int[] numbers = getNumbers(count, 155);
    for (int index = 0; index < count; index++)
    {
        Console.Write(numbers[index]);
        if ((index + 1) % 10 == 0)
            Console.WriteLine("");
        else if (index != count - 1)
            Console.Write(",");
    }
    Console.ReadKey();
}
static int[] getNumbers(int count, int total)
{
    const int LOWERBOUND = 1;
    const int UPPERBOUND = 9;

    int[] result = new int[count];
    int currentsum = 0;
    int low, high, calc;

    if((UPPERBOUND * count) < total ||
        (LOWERBOUND * count) > total ||
        UPPERBOUND < LOWERBOUND)
        throw new Exception("Not possible.");

    Random rnd = new Random();

    for (int index = 0; index < count; index++)
    {
        calc = (total - currentsum) - (UPPERBOUND * (count - 1 - index));
        low = calc < LOWERBOUND ? LOWERBOUND : calc;
        calc = (total - currentsum) - (LOWERBOUND * (count - 1 - index));
        high = calc > UPPERBOUND ? UPPERBOUND : calc;

        result[index] = rnd.Next(low, high + 1);

        currentsum += result[index];
    }

    // The tail numbers will tend to drift higher or lower so we should shuffle to compensate somewhat.

    int shuffleCount = rnd.Next(count * 5, count * 10);
    while (shuffleCount-- > 0)
        swap(ref result[rnd.Next(0, count)], ref result[rnd.Next(0, count)]);

    return result;
}
public static void swap(ref int item1, ref int item2)
{
    int temp = item1;
    item1 = item2;
    item2 = temp;
}

У меня не было много времени, чтобы проверить это, поэтому извиняюсь, если где-то есть ошибки в моей логике.

РЕДАКТИРОВАТЬ:

Я провел некоторое тестирование, и все кажется надежным. Если вы хотите хороший красивый спред, похоже, что вы хотите что-то вроде Total = Count * ((UPPER + LOWER) / 2). Хотя я вполне уверен, что по мере увеличения разницы между UPPER и LOWER она становится более гибкой.

7 голосов
/ 23 января 2009

Проблема в том, что мы хотим, чтобы все числа были ограничены 1-9 и суммировались с N. Таким образом, мы должны сгенерировать каждое число одно за другим и определить реальные границы для следующего числа.

Это, конечно, приведет к статистическому смещению к концу списка, поэтому я рекомендую перетасовать массив один раз после генерации.

Чтобы определить границы следующего числа, сделайте следующее: Верхняя граница = взять оставшуюся сумму минус (количество оставшихся элементов * мин). Нижняя граница = взять оставшуюся сумму минус (количество оставшихся элементов * max).

Что-то вроде (не проверено):

public static List<int> RandomList(int digitMin, int digitMax, 
                                   int targetSum, int numDigits)
{
    List<int> ret = new List<int>(numDigits);

    Random random = new Random();
    int localMin, localMax, nextDigit;
    int remainingSum = targetSum;

    for(int i=1; i<=numDigits; i++)
    {
          localMax = remainingSum - ((numDigits - i) * min);
          if(localMax > max)
              localMax = max;

          localMin = remainingSum - ((length - i) * max);
          if(localMin > min)
              localMin = min;

          nextDigit = random.Next(localMin, localMax);
          ret.Add(nextDigit);
          remainingSum -= nextDigit;
    }

    return ret;
}

Идея здесь заключается в том, что при генерации чисел диапазон возможных значений для оставшихся чисел уменьшается, как предельная функция, сосредоточенная на целевой сумме. Вроде.

Редактировать: мне пришлось изменить цикл for на 1, потому что мы хотим, чтобы количество элементов осталось ПОСЛЕ генерации этого.

Edit2: поместите его в метод для полноты и измените length на numDigits для удобочитаемости.

4 голосов
/ 23 января 2009

Мое оригинальное заявление:

Вы можете генерировать только 29 случайных чисел. 30-е число будет определяться другими 29 и суммой. Это статистически важно ...

Я хотел бы добавить некоторые пояснения, подумав об этом, и пинговать сообщество ...

Теперь я верю, что мое первоначальное утверждение было ложным. Это было слишком снисходительно (на что указал lc). Вы даже не можете сгенерировать 29 действительно случайных чисел. Когда вы подходите ближе и ближе к 30, последние цифры не случайны так же, как случайное число [1..9]. Я пытался смягчить это, чтобы найти решение, но я считаю, что решение, которое он придумал (и Спенсер), отвечает на совершенно другой вопрос. Этот вопрос звучит так: «Из всех наборов из 30 цифр от 1 до 9, которые составляют до 200, строят один случайным образом».

Я полагаю, что дело в том, что заявленный вопрос неразрешим, что, как я полагаю, может быть доказано с помощью принципа голубиного отверстия (также используемого Кнутом для демонстрации того, что некоторые «случайные» тасовки не были действительно случайно), но я не сделал математику.

Хороший разговор всем.

3 голосов
/ 23 января 2009

Эта программа попытается дать вам ответ. Но поскольку вы имеете дело со случайными числами, есть вероятность, что это никогда не даст вам ответа.

public static IEnumerable<int> GetRandom()
{
    var rand = new Random();
    while (true)
    {
        yield return
        rand.Next(1, 9);
    }
}

public static List<int> GetThirtyThatAddToTwoHundred()
{
    do
    {
        var current = GetRandom().Take(30);
        if (200 == current.Sum())
        {
            return current.ToList();
        }
    } while (true);
}
1 голос
/ 21 июня 2009

Итак, я должен спросить: Есть ли для этого реальная цель, или это просто упражнение или домашнее задание? Продолжается большая работа по предотвращению "предвзятости". Это фактическое требование, или подойдет какое-нибудь довольно случайное решение? Не зная требований, очень легко тратить много времени. Если это реальная проблема, пожалуйста, объясните, каковы фактические требования.

1 голос
/ 23 января 2009

После всех обсуждений здесь есть еще один способ создать список, который не вносит смещения. Да, он отличается от того, что задает вопрос, но вместо случайного выбора цифр вы можете произвольно увеличивать цифры, пока не достигнете суммы. Как следующее (снова непроверенное):

public static List<int> RandomListByIncrementing(int digitMin, int digitMax, 
                                                 int targetSum, int numDigits)
{
    if(targetSum < digitMin * numDigits || targetSum > digitMax * numDigits)
        throw new ArgumentException("Impossible!", "targetSum");

    List<int> ret = new List<int>(Enumerable.Repeat(digitMin, numDigits));
    List<int> indexList = new List<int>(Enumerable.Range(0, numDigits-1));

    Random random = new Random();
    int index;

    for(int currentSum=numDigits * digitMin; currentSum<targetSum; currentSum++)
    {
        //choose a random digit in the list to increase by 1
        index = random.Next(0,indexList.Length-1);

        if(++ret[indexList[index]] == digitMax)
        {
            //if you've increased it up to the max, remove its reference
            //because you can't increase it anymore
            indexList.RemoveAt(index);
        }
    }

    return ret;
}

Идея в том, что вы ведете список ссылок на ваш список номеров. Выберите ссылку случайным образом и увеличьте соответствующий номер. Если вы больше не можете увеличивать его, удалите ссылку, чтобы не выбирать ее в следующий раз.

Теперь в конце дня перестановки делать нельзя, хотя, возможно, это все равно даст один из доступных наборов ответов на вопрос, и вопрос в том, какой из них «лучше» или быстрее запустить .

0 голосов
/ 20 марта 2015

Это старый вопрос, но я обнаружил, что искал решение этой проблемы для практического использования в приложении для генерации случайных данных.

Основываясь на идеях в этом посте, я предложил два возможных решения.

Первый метод:

  1. Определите наименьшее целочисленное значение, которое можно повторить, чтобы сложить число рядом с желаемой суммой. По сути, просто делите целочисленное деление.
  2. Инициализировать массив со всеми значениями, равными числу, найденному на шаге 1.
  3. Если есть остаток (как правило, будет), случайным образом добавьте его к элементам в массиве и уменьшайте его до тех пор, пока остаток не станет равным 0. В этот момент у нас есть массив, который будет равен желаемому итогу, но он будет быть очень неслучайным.
  4. Для ряда итераций случайным образом складывайте и вычитайте из двух мест в массиве. Пример: добавьте 1 в позицию 0 и вычтите 1 из позиции 4. При этом выполните проверку границ (все числа должны быть не менее 0, а все числа не должны превышать верхнюю границу).

Второй метод намного проще, но приводит к менее случайному распределению:

  1. Инициализировать массив из 0 требуемой длины.
  2. Выберите случайный индекс в массиве и добавьте 1. Если значение этого индекса превысит верхнюю границу, проигнорируйте его и выберите другой индекс.
  3. Повторите шаг 2 количество раз, указанное нужной суммой.

Вот код:

public static int[] getRandomsWithTotalA(int desiredTotal, int desiredNumbers, int upperBound)
{
    Random r = new Random();

    // Is this even a possible feat?
    if (desiredNumbers * upperBound < desiredTotal) throw new ArgumentException("This is not possible!", "desiredTotal");

    // Start by figuring out the closest number we can get to by repeating the initial number.
    int lowestRepeating = desiredTotal / desiredNumbers;

    // Determine the remainder
    int lowestRepeatingRemainder = desiredTotal % desiredNumbers;

    // Initialize and populate an array of numbers with the lowest repeater.
    int[] results = Enumerable.Repeat(lowestRepeating, desiredNumbers).ToArray();

    // We will perform (n*desiredTotal) shuffles.
    int shuffles = (desiredTotal * desiredNumbers);

    while (shuffles > 0)
    {
        int a = r.Next(desiredNumbers);
        int b= r.Next(desiredNumbers);
        if (a==b) continue; // do nothing if they're equal - try again.

        // Test bounds.
        if (results[a]+1>upperBound) continue;
        if (results[b]-1<0) continue;

        // Add one to the first item.
        results[a]++;

        // Do we still have a remainder left? If so, add one but don't subtract from
        // somewhere else.
        if (lowestRepeatingRemainder>0)
        {
            lowestRepeatingRemainder--;
            continue;
        }
        // Otherwise subtract from another place.
        results[b]--;
        // decrement shuffles
        shuffles--;
    }

    return results;
}

public static int[] getRandomsWithTotalB(int desiredTotal, int desiredNumbers, int upperBound)
{
    Random r = new Random();

    // Is this even a possible feat?
    if (desiredNumbers * upperBound < desiredTotal) throw new ArgumentException("This is not possible!", "desiredTotal");

    // Initialize and populate an array of numbers with the lowest repeater.
    int[] results = new int[desiredNumbers];

    while (desiredTotal > 0)
    {
        int a = r.Next(desiredNumbers);

        // Test bounds.
        if (results[a] + 1 > upperBound) continue;

        // Add one to the first item.
        results[a]++;

        desiredTotal--;
    }

    return results;
}

Пример прогона:

static void Main(string[] args)
{
    foreach (int i in getRandomsWithTotalA(200, 30, 9))
    {
        Console.Write("{0}, ", i);
    }
    Console.WriteLine("\n");
    foreach (int i in getRandomsWithTotalB(200, 30, 9))
    {
        Console.Write("{0}, ", i);
    }
}

3, 8, 7, 5, 9, 9, 8, 9, 9, 6, 8, 7, 4, 8, 7, 7, 8, 9, 2, 7, 9, 5, 8, 1, 4, 5, 4, 8, 9, 7,

6, 8, 5, 7, 6, 9, 9, 8, 5, 4, 4, 6, 7, 7, 8, 4, 9, 6, 6, 5, 8, 9, 9, 6, 6, 8, 7, 4, 7, 7, 

Понятно, что эти методы распределены не так равномерно, как хотелось бы. Это имело бы смысл, особенно со вторым методом; если у вас есть случайный источник, который действительно равномерно распределен, то ваш выбор элементов для увеличения должен иметь равную вероятность по всем возможным значениям. Первый также может быть немного лучше, но он все еще страдает от того факта, что случайный источник также идеально распределен равномерно.

Мне кажется, что возможно улучшить хотя бы первый метод, введя некоторую форму смещения в выбор индекса, или, возможно, рандомизировать то, сколько мы добавляем и вычитаем (не всегда 1), или рандомизировать, мы на самом деле делаем сложение / вычитание или нет. Кажется, простое изменение количества итераций меняет распределение, но через некоторое время мы начинаем отдавать предпочтение внешним границам. (Возможно, невозможно получить по-настоящему равномерный дистрибутив!)

В любом случае, вы идете ... Хорошее место, чтобы начать хотя бы.

0 голосов
/ 24 января 2009

Я думал, что попробую подход «разделяй и властвуй». Кажется, работает довольно хорошо. Я уверен, что результаты не являются действительно случайными из-за ограничивающих элементов алгоритма, но это близко. По сути, я делю список на две части и целевую сумму пополам и повторяю до тех пор, пока не получу списки из 3 элементов или менее. Затем я использую итерацию случайных цифр методом грубой силы до тех пор, пока не будут получены эти меньшие суммы. Вот код с примером запуска под ним.

using System;
using System.Collections.Generic;

namespace AddUpClient {
    class Program {

        static void Main() {
            AddUpWorker worker = new AddUpWorker();
            int MinDigit = 1;
            int MaxDigit = 9;
            int ItemsToSum = 30;
            int TargetSum = 150;

            try {
                //Attempt to get a list of pseudo-random list of integers that add up to the target sum
                IList<int> Results = worker.AddUp(MinDigit, MaxDigit, ItemsToSum, TargetSum);

                EvaluateResults(TargetSum, Results);
                Console.ReadLine();
            }
            catch (Exception E) {
                Console.Out.WriteLine("Error: {0}", E.Message);
                return;
            }

        }

        private static void EvaluateResults(int TargetSum, IList<int> Results)
        {
            Console.Out.WriteLine("Results have {0} items.", Results.Count);

            int Sum = 0;
            foreach (int Result in Results) {
                Sum += Result;
                Console.Out.WriteLine("Result: {0} Running total: {1}", Result, Sum);
            }
            Console.Out.WriteLine();
            Console.Out.WriteLine("Result = {0}", (Sum == TargetSum ? "SUCCESS" : "FAIL"));
        }
    }

    internal class AddUpWorker {

        Random RGenerator = new Random();

        public IList<int> AddUp(int MinDigit, int MaxDigit, int ItemsToSum, int TargetSum) {

            Console.Out.WriteLine("AddUp called to sum {0} items to get {1}", ItemsToSum, TargetSum);
            if (ItemsToSum > 3) {

                int LeftItemsToSum = ItemsToSum/2;
                int RightItemsToSum = ItemsToSum - LeftItemsToSum;

                int LeftTargetSum = TargetSum/2;
                int RightTargetSum = TargetSum - LeftTargetSum;


                IList<int> LeftList = AddUp(MinDigit, MaxDigit, LeftItemsToSum, LeftTargetSum);
                IList<int> RightList = AddUp(MinDigit, MaxDigit, RightItemsToSum, RightTargetSum);

                List<int> Results = new List<int>();
                Results.AddRange(LeftList);
                Results.AddRange(RightList);
                return Results;
            }

            // 3 or less

            int MinSumWeCanAchieve = ItemsToSum*MinDigit;
            int MaxSumWeCanAchieve = ItemsToSum*MaxDigit;


            if (TargetSum < MinSumWeCanAchieve)
                throw new ApplicationException("We added up too fast");

            if (TargetSum > MaxSumWeCanAchieve)
                throw new ApplicationException("We added up too slow");

            //Now we know we can achieve the result -- but it may not be too efficient...

            int[] TrialNumbers = new int[ItemsToSum];
            int MaxIteration = 100000;
            int IterationPrintInterval = 1000;
            int TrialSum;
            bool PrintIteration;

            for (int Iteration = 1; Iteration <= MaxIteration; ++Iteration) {

                PrintIteration = ((Iteration % IterationPrintInterval) == 0);

                if (PrintIteration)
                    Console.Out.WriteLine("Iteration {0} attempting to sum {1} numbers to {2}",
                        Iteration, ItemsToSum, TargetSum);

                TrialSum = 0;
                for (int j=0; j < ItemsToSum; ++j) {
                    TrialNumbers[j] = RGenerator.Next(MinDigit, MaxDigit + 1);
                    TrialSum += TrialNumbers[j];
                }
                if (PrintIteration)
                    ShowArray(string.Format("Iteration: {0}", Iteration), TrialNumbers);

                if (TrialSum == TargetSum) {    //Yay
                    ShowArray(string.Format("Success in {0} iterations: ", Iteration), TrialNumbers);
                    return new List<int>(TrialNumbers);
                }
                //try again....
            }

            throw new ApplicationException(string.Format("Maximum of {0} trials exceeded", MaxIteration));
        }

        private void ShowArray(string Prefix, int[] numbers)
        {
            for (int i = 0; i < numbers.Length; ++i) {
                if (i == 0)
                    Console.Write("{0} {1}", Prefix, numbers[i]);
                else
                    Console.Write(", {0}", numbers[i]);
            }
            Console.WriteLine();
        }
    }
}

AddUp called to sum 30 items to get 150
AddUp called to sum 15 items to get 75
AddUp called to sum 7 items to get 37
AddUp called to sum 3 items to get 18
Success in 10 iterations:  7, 2, 9
AddUp called to sum 4 items to get 19
AddUp called to sum 2 items to get 9
Success in 12 iterations:  5, 4
AddUp called to sum 2 items to get 10
Success in 2 iterations:  1, 9
AddUp called to sum 8 items to get 38
AddUp called to sum 4 items to get 19
AddUp called to sum 2 items to get 9
Success in 11 iterations:  4, 5
AddUp called to sum 2 items to get 10
Success in 6 iterations:  8, 2
AddUp called to sum 4 items to get 19
AddUp called to sum 2 items to get 9
Success in 3 iterations:  8, 1
AddUp called to sum 2 items to get 10
Success in 1 iterations:  4, 6
AddUp called to sum 15 items to get 75
AddUp called to sum 7 items to get 37
AddUp called to sum 3 items to get 18
Success in 3 iterations:  4, 6, 8
AddUp called to sum 4 items to get 19
AddUp called to sum 2 items to get 9
Success in 17 iterations:  3, 6
AddUp called to sum 2 items to get 10
Success in 24 iterations:  1, 9
AddUp called to sum 8 items to get 38
AddUp called to sum 4 items to get 19
AddUp called to sum 2 items to get 9
Success in 3 iterations:  2, 7
AddUp called to sum 2 items to get 10
Success in 3 iterations:  1, 9
AddUp called to sum 4 items to get 19
AddUp called to sum 2 items to get 9
Success in 4 iterations:  5, 4
AddUp called to sum 2 items to get 10
Success in 2 iterations:  9, 1
Results have 30 items.
Result: 7 Running total: 7
Result: 2 Running total: 9
Result: 9 Running total: 18
Result: 5 Running total: 23
Result: 4 Running total: 27
Result: 1 Running total: 28
Result: 9 Running total: 37
Result: 4 Running total: 41
Result: 5 Running total: 46
Result: 8 Running total: 54
Result: 2 Running total: 56
Result: 8 Running total: 64
Result: 1 Running total: 65
Result: 4 Running total: 69
Result: 6 Running total: 75
Result: 4 Running total: 79
Result: 6 Running total: 85
Result: 8 Running total: 93
Result: 3 Running total: 96
Result: 6 Running total: 102
Result: 1 Running total: 103
Result: 9 Running total: 112
Result: 2 Running total: 114
Result: 7 Running total: 121
Result: 1 Running total: 122
Result: 9 Running total: 131
Result: 5 Running total: 136
Result: 4 Running total: 140
Result: 9 Running total: 149
Result: 1 Running total: 150

Result = SUCCESS
0 голосов
/ 24 января 2009

Чтобы ответ не был смещен в сторону меньших чисел (или любого другого отклонения), вы в идеале должны генерировать все возможные наборы чисел, которые складываются в N. После того, как у вас есть все наборы, случайным образом выберите один из наборов. После того, как вы выбрали выигрышный набор, вы можете, если необходимо, случайным образом изменить порядок чисел в этом наборе.

0 голосов
/ 23 января 2009

Если вам нужен беспристрастный алгоритм, то наивная реализация выглядит примерно так:

while (true) {
  numbers = [];
  total = 0;
  for (i = 0; i < COUNT; ++i) {
    next = rand(BOUNDS);
    total += next;
    numbers.push(next);
  }

  if (total == TARGET) {
    return numbers;
  }
}

Это не прекращается и медленно, но не смещено. Если вам нужен беспристрастный алгоритм, я не уверен, что выложенные здесь алгоритмы беспристрастны.

...