Разделить значение в 24 части случайного размера с использованием С#

У меня есть значение, скажем, 20010. Я хочу случайным образом разделить это значение в течение 24 часов. Таким образом, в основном разделить значение на 24-сегментный большой массив, где все слоты случайным образом большие.

Что может быть хорошим способом решить это с помощью С#?

Ответ 1

Нарисуйте 23 (не 24) числа в случайном порядке (без дубликатов) в диапазоне от 1 до 20009. Добавьте 0 и 20010 список и упорядочите эти цифры, разница между двумя последовательными номерами даст вам одно значение слота.

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

Ответ 2

Здесь существует функциональное решение с использованием алгоритма mjv:

static int[] GetSlots(int slots, int max)
{
    return new Random().Values(1, max)
                       .Take(slots - 1)
                       .Append(0, max)
                       .OrderBy(i => i)
                       .Pairwise((x, y) => y - x)
                       .ToArray();
}

public static IEnumerable<int> Values(this Random random, int minValue, int maxValue)
{
    while (true)
        yield return random.Next(minValue, maxValue);
}

public static IEnumerable<TResult> Pairwise<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TSource, TResult> resultSelector)
{
    TSource previous = default(TSource);

    using (var it = source.GetEnumerator())
    {
        if (it.MoveNext())
            previous = it.Current;

        while (it.MoveNext())
            yield return resultSelector(previous, previous = it.Current);
    }
}

public static IEnumerable<T> Append<T>(this IEnumerable<T> source, params T[] args)
{
    return source.Concat(args);
}

Ответ 3

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

  • Создайте список из 24 случайных значений, созданных, как вам нравится, в любом масштабе
  • Найдите сумму этого списка
  • Создайте свой окончательный список, масштабируя 24 случайных значения по сравнению с вашим общим

Примечания

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

Ответ 4

Это весело. Вдохновленный Дэвидом, здесь реализовано решение mjv с использованием только операторов, предоставляемых LINQ. Поскольку ключ David Dictionary является просто индексом, мы можем использовать массив для функции Pairwise:

var r = new Random();
var a = Enumerable.Repeat(null, n - 1)        // Seq with (n-1) elements...
                  .Select(x => r.Next(1, m))  // ...mapped to random values
                  .Concat(new [] { 0, m })
                  .OrderBy(x => x)
                  .ToArray();

return a.Skip(1).Select((x,i) => x - a[i]);

Ответ 5

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

Bevan:  [751, 845, 809, 750, 887, 886, 838, 868, 837, 902, 841, 812, 818, 774, 815, 857, 752, 815, 896, 872, 833, 864, 769, 894]
Gregory:  [9633, 5096, 2623, 1341, 766, 243, 159, 65, 21, 19, 16, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2]
mjv:  [895, 632, 884, 837, 799, 722, 853, 749, 915, 756, 814, 863, 842, 642, 820, 805, 659, 862, 742, 812, 768, 816, 721, 940]
peterallenwebb:  [832, 833, 835, 829, 833, 832, 837, 835, 833, 827, 833, 832, 834, 833, 836, 833, 838, 834, 834, 833, 834, 832, 836, 830]

И вот код python: import random

N = 20010;

def mjv():
    gaps = [ random.randrange(0, N) for i in range(0, 24) ]
    gaps = gaps + [0, N]
    gaps.sort()
    value = [ gaps[i+1] - gaps[i] for i in range(0, 24) ]
    return value

def gregory():
    values = []
    remainingPortion = N

    for i in range(0, 23):
        val = random.randrange(1, remainingPortion - (23 - i))
        remainingPortion = remainingPortion - val
        values.append(val)

    values.append(remainingPortion)

    return values


def peterallenwebb():
    values = [0  for i in range(0, 24) ]

    for i in range(0, N):
        k = random.randrange(0, 24)
        values[k] = values[k] + 1 

    return values

def bevan():
    values = [];
    sum = 0.0

    for i in range(0, 24):
        k = random.random()
        sum = sum + k
        values.append(k);

    scaleFactor = N / sum

    for j in range(0, 24):
        values[j] = int(values[j] * scaleFactor)

    return values


def averageBucketSizes(method):
    totals = [0 for i in range(0, 24)]
    trials = 100

    for i in range(0,trials):
        values = method()

        for j in range(0, 24):
            totals[j] = totals[j] + values[j]      

    for j in range(0, 24):
        totals[j] = totals[j] / trials

    return totals;


print 'Bevan: ', averageBucketSizes(bevan)
print 'Gregory: ', averageBucketSizes(gregory)
print 'mjv: ', averageBucketSizes(mjv)
print 'peterallenwebb: ', averageBucketSizes(peterallenwebb)

Сообщите мне, видите ли вы какие-либо ошибки. Я снова запустил.

Ответ 6

Если вы хотите быть уверенным, что вы не смещаете процесс без особого анализа, вы можете просто создать массив из 24 элементов, инициализировать каждый элемент до 0 и затем добавить один к одному из элементов произвольно 20010 раз.

Все зависит от вида дистрибутивов, которые вы хотите увидеть, но я не думаю, что какие-либо другие рекомендации, рекомендуемые до сих пор, приведут к тому, что часовые "ведра" будут статистически неразличимы.

Ответ 7

Другой вариант - создать случайное число между 0 и целевым номером. Затем добавьте каждую "кусок" в список. Выберите самый большой "кусок" и разделите его на две части, используя другое случайное число. Выберите самый большой из списка (теперь с тремя частями) и продолжайте, пока не получите желаемое количество штук.

List<int> list = new List<int>();
list.Add(2010);
Random random = new Random();
while (list.Count() < 24)
{
    var largest = list.Max();
    var newPiece = random.Next(largest - 1);
    list.Remove(largest);
    list.Add(newPiece);
    list.Add(largest - newPiece);
}

Ответ 8

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

public static IEnumerable<int> Split(int n, int m)
{
    Random r = new Random();
    int i = 0;

    var dict = Enumerable.Range(1, m - 1)
        .Select(x => new { Key = r.NextDouble(), Value = x })
        .OrderBy(x => x.Key)
        .Take(n - 2)
        .Select(x => x.Value)
        .Union(new[] { 0, m })
        .OrderBy(x => x)
        .ToDictionary(x => i++);

    return dict.Skip(1).Select(x => x.Value - dict[x.Key - 1]);
}

Ответ 9

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

int initialValue = 20010;
var values = new List<int>();

Random rnd = new Random();
int currentRemainder = initialValue;

for (int i = 0; i < 21; i++)
{
    //get a new value;
    int val = rnd.Next(1, currentRemainder - (21 - i));

    currentRemainder -= val;
    values.Add(val);
}

values.Add(currentRemainder);

//initialValue == values.Sum()

Ответ 10

class Numeric
  def n_rands(n)
    raw = (1..n).map { |x| rand } 
    raw.map { |x| x * to_f / raw.sum.to_f }.map { |x| x.to_i }.tap do |scaled|
      scaled[-1] = self - scaled[0..-2].sum
    end
  end
end

puts 1000.n_rands(10).inspect # [22, 70, 180, 192, 4, 121, 102, 179, 118, 12]

Ответ 11

Я попробовал решения от Дэвида и Далбыка, но не повезло. Итак, вот что я придумал после прочтения ответа от mjv:

public static class IntExtensions
{
    public static IEnumerable<int> Split(this int number, int parts)
    {
        var slots = Enumerable.Repeat(0, parts).ToList();
        var random = new Random();

        while (number > 0)
        {
            var slot = random.Next(0, parts);
            slots[slot]++;
            number--;
        }
        return slots;
    }
}