Случайные числа с фиксированной суммой

32

Ваша задача - написать программу или функцию, которая выводит n случайные числа из интервала [0,1] с фиксированной суммой s.

вход

n, n≥1, количество случайных чисел для генерации

s, s>=0, s<=n, сумма чисел, которые будут сгенерированы

Выход

Случайный nнабор чисел с плавающей запятой со всеми элементами из интервала [0,1] и суммой всех элементов, равных s, выводится любым удобным однозначным способом. Все допустимые nкортежи должны быть одинаково вероятными в пределах ограничений чисел с плавающей запятой.

Это равносильно равномерной выборке из пересечения точек внутри n-мерного единичного куба и n-1-мерной гиперплоскости, которая проходит (s/n, s/n, …, s/n)и перпендикулярна вектору (1, 1, …, 1)(см. Красную область на рисунке 1 для трех примеров).

Примеры с n = 3 и суммами 0,75, 1,75 и 2,75

Рисунок 1: Плоскость действительных выходов с n = 3 и суммами 0,75, 1,75 и 2,75

Примеры

n=1, s=0.8 → [0.8]
n=3, s=3.0 → [1.0, 1.0, 1.0]
n=2, s=0.0 → [0.0, 0.0]
n=4, s=2.0 → [0.2509075946818119, 0.14887693388076845, 0.9449661625992032, 0.6552493088382167]
n=10, s=9.999999999999 → [0.9999999999999,0.9999999999999,0.9999999999999,0.9999999999999,0.9999999999999,0.9999999999999,0.9999999999999,0.9999999999999,0.9999999999999,0.9999999999999]

правила

  • Ваша программа должна завершиться менее чем за секунду на вашем компьютере, по крайней мере, с n≤10любым допустимым s
  • Если вы хотите, ваша программа может быть эксклюзивной на верхнем конце, то есть s<nи выходных чисел из полуоткрытого интервала [0,1) (ломая второй пример)
  • Если ваш язык не поддерживает числа с плавающей запятой, вы можете подделать вывод не менее десяти десятичных цифр после запятой.
  • Стандартные лазейки запрещены, и стандартные методы ввода / вывода разрешены.
  • Это , поэтому выигрывает самая короткая запись в байтах.
Angs
источник
Связанный.
Мартин Эндер
Когда вы говорите This is equal to uniformly sampling from the intersection- я вижу программу, выбирающую случайным образом только из углов этого пересечения. Это будет действительным?
JayCe
2
@KevinCruijssen Нет, это верно только для s==0 or s==3. Для всех других значений sплоскость имеет ненулевую площадь, и вы должны случайным образом выбрать точку на этой плоскости.
user202729
3
Требование, чтобы интервал был закрыт или полузакрыт (в отличие от открытого) - теоретически ненаблюдаемое требование. Многие генераторы случайных чисел выдают результат в (0,1). Как проверить, что выходной интервал равен [0,1), а не (0,1)? Значение 0 «никогда» не встречается в любом случае
Луис Мендо
2
Разве это нормально, если в нашем коде используется выборка отклонения, и поэтому для тестовых примеров это занимает много времени s=2.99999999999, n=3? Можем ли мы генерировать случайные реальные числа, например, кратные 1e-9?
xnor

Ответы:

1

Wolfram Language (Mathematica) , 92 90 байт

If[2#2>#,1-#0[#,#-#2],While[Max[v=Differences@Sort@Join[{0,#2},RandomReal[#2,#-1]]]>1];v]&

Попробуйте онлайн!

Негольфированный код:

R[n_, s_] := Module[{v},
  If[s <= n/2,             (* rejection sampling for s <= n/2:                        *)
    While[
      v = Differences[Sort[
            Join[{0},RandomReal[s,n-1],{s}]]];         (* trial randoms that sum to s *)
      Max[v] > 1           (* loop until good solution found                          *)
    ];
    v,                     (* return the good solution                                *)
    1 - R[n, n - s]]]      (* for s > n/2, invert the cube and rejection-sample       *)

Вот решение, которое работает в 55 байтах, но на данный момент (версия 12 Mathematica) ограничено, n=1,2,3потому что RandomPointотказывается рисовать точки из гиперплоскостей с большей размерностью (в версии 11.3 TIO это также не удается n=1) Это может работать для более высокого уровня nв будущем, хотя:

RandomPoint[1&~Array~#~Hyperplane~#2,1,{0,1}&~Array~#]&

Попробуйте онлайн!

Негольфированный код:

R[n_, s_] :=
  RandomPoint[                           (* draw a random point from *)
    Hyperplane[ConstantArray[1, n], s],  (* the hyperplane where the sum of coordinates is s *)
    1,                                   (* draw only one point *)
    ConstantArray[{0, 1}, n]]            (* restrict each coordinate to [0,1] *)
Римский
источник
6

Python 2 , 144 128 119 байтов

from random import*
def f(n,s):
 r=min(s,1);x=uniform(max(0,r-(r-s/n)*2),r);return n<2and[s]or sample([x]+f(n-1,s-x),n)

Попробуйте онлайн!


  • -20 байт, благодаря Кевину Круйссену
TFeld
источник
@LuisMendo Должно быть исправлено сейчас
TFeld
они все еще кажутся неоднородными
l4m2
@ l4m2 Я побежал g(4, 2.0)1000 раз, чтобы получить 4000 очков, и результаты выглядят примерно так, что выглядит довольно равномерно.
Тост инженера
122 байта?
Джузеппе
4

Ява 8, 194 188 196 237 236 байт

n->s->{double r[]=new double[n+1],d[]=new double[n],t;int f=0,i=n,x=2*s>n?1:0;for(r[n]=s=x>0?n-s:s;f<1;){for(f=1;i-->1;)r[i]=Math.random()*s;for(java.util.Arrays.sort(r);i<n;d[i++]=x>0?1-t:t)f=(t=Math.abs(r[i]-r[i+1]))>1?0:f;}return d;}

+49 байтов (188 → 196 и 196 → 237), чтобы зафиксировать скорость тестовых случаев, близких к 1, а также исправить алгоритм в целом.

Попробуйте онлайн

Объяснение:

Использует подход в этом ответе StackoverFlow , внутри цикла, пока один из элементов все еще больше 1.
Также, если 2*s>n, sбудет изменен на n-s, и установлен флаг, чтобы указать, что мы должны использовать 1-diffвместо diffв массиве результатов (спасибо за отзыв @soktinpk и @ l4m2 ).

n->s->{              // Method with integer & double parameters and Object return-type
  double r[]=new double[n+1]
                     //  Double-array of random values of size `n+1`
         d[]=new double[n],
                     //  Resulting double-array of size `n`
         t;          //  Temp double
  int f=0,           //  Integer-flag (every item below 1), starting at 0
      i=n,           //  Index-integer, starting at `n`
      x=             //  Integer-flag (average below 0.5), starting at:
        2*s>n?       //   If two times `s` is larger than `n`:
         1           //    Set this flag to 1
        :            //   Else:
         0;          //    Set this flag to 0
  for(r[n]=s=        //  Set both the last item of `r` and `s` to:
       x>0?          //   If the flag `x` is 1:
        n-s          //    Set both to `n-s`
       :             //   Else:
        s;           //    Set both to `s`
      f<1;){         //  Loop as long as flag `f` is still 0
    for(f=1;         //   Reset the flag `f` to 1
        i-->1;)      //   Inner loop `i` in range (n,1] (skipping the first item)
      r[i]=Math.random()*s;
                     //    Set the i'th item in `r` to a random value in the range [0,s)
    for(java.util.Arrays.sort(r);
                     //   Sort the array `r` from lowest to highest
        i<n;         //   Inner loop `i` in the range [1,n)
        ;d[i++]=     //     After every iteration: Set the i'th item in `d` to:
          x>0?       //      If the flag `x` is 1:
           1-t       //       Set it to `1-t`
          :          //      Else:
           t)        //       Set it to `t`
      f=(t=Math.abs( //    Set `t` to the absolute difference of:
            r[i]-r[i+1])) 
                     //     The i'th & (i+1)'th items in `r`
        >1?          //    And if `t` is larger than 1 (out of the [0,1] boundary)
         0           //     Set the flag `f` to 0
        :            //    Else:
         f;}         //     Leave the flag `f` unchanged
  return d;}         //  Return the array `d` as result
Кевин Круйссен
источник
Тайм-аут дляtest(10, 9.99);
l4m2
@ l4m2 Да, заметил то же самое с 10, 9.0сразу после того, как я отредактировал исправление n=10, s=9.999999999999тестового примера. Не уверен, есть ли исправление в Java, все еще сохраняя его равномерную случайность. Придется подумать об этом некоторое время. Сейчас я отредактирую это, чтобы заявить, что это истекло.
Кевин Круйссен
Если n-s<1вы можете позвонить f(n,n-s)и перевернуть каждый номер 1/2(т.е. заменить xна 1-x), как это сделал l4m2. Это может решить проблему с номерами, sрядом с которыми n.
soktinpk
@soktinpk Спасибо за совет. Это на самом деле s+s>nвместо n-s<1, но когда я посмотрел на другие ответы JavaScript, это действительно имело смысл. Теперь все исправлено, включая еще одну ошибку, которая все еще присутствовала. Количество байтов возросло, но теперь все работает. Будет работать отсчет байтов отсюда. :)
Кевин Круйссен
Я не знаю общего доказательства, но я считаю, что этот алгоритм работает, потому что N-мерный гиперкуб может быть разрезан на N N-мерных гиперпирамид.
Нил
3

C ++ 11, 284 267 байт

-17 байт благодаря
случайной библиотеке Zacharý Uses C ++, вывод на стандартный вывод

#include<iostream>
#include<random>
typedef float z;template<int N>void g(z s){z a[N],d=s/N;int i=N;for(;i;)a[--i]=d;std::uniform_real_distribution<z>u(.0,d<.5?d:1-d);std::default_random_engine e;for(;i<N;){z c=u(e);a[i]+=c;a[++i]-=c;}for(;i;)std::cout<<a[--i]<<' ';}

Чтобы позвонить, нужно просто сделать это:

g<2>(0.0);

Где параметр шаблона (здесь, 2) равен N, а фактический параметр (здесь 0,0) равен S

HatsuPointerKun
источник
Я думаю, что вы можете удалить пространство между <z>иu
Zacharý
Я получил его дальше вниз: typedef float z;template<int N>void g(z s){z a[N],d=s/N;int i=N;for(;i;)a[--i]=d;std::uniform_real_distribution<z>u(.0,d<.5?d:1-d);std::default_random_engine e;for(;i<N;){z c=u(e);a[i]+=c;a[++i]-=c;}for(;i;)std::cout<<a[--i]<<' ';}. Новая строка не должна быть разделителем между элементами
Zacharý
1
Предложите dполностью избавиться , переключившись d=s/Nна s/=NПредлагать переработку второго цикла for(z c;i<N;a[++i%N]-=c)a[i]+=c=u(e);вместо for(;i<N;){z c=u(e);a[i]+=c;a[++i]-=c;}(обратите внимание на добавленное %N, чтобы программа правильно вычислила первое число)
Макс Ехлаков
2

Чисто , 221 201 байт

Чисто, код-гольф или случайные числа. Выбери два.

import StdEnv,Math.Random,System._Unsafe,System.Time
g l n s#k=toReal n
|s/k>0.5=[s/k-e\\e<-g l n(k-s)]
#r=take n l
#r=[e*s/sum r\\e<-r]
|all((>)1.0)r=r=g(tl l)n s

g(genRandReal(toInt(accUnsafe time)))

Попробуйте онлайн!

Частичная функция буквальная :: (Int Real -> [Real]). Новые результаты будут появляться только раз в секунду.
Точность не менее 10 знаков после запятой.

Οurous
источник
2

R , 99 байт (с gtoolsпакетом)

f=function(n,s){if(s>n/2)return(1-f(n,n-s))
while(any(T>=1)){T=gtools::rdirichlet(1,rep(1,n))*s}
T}

Попробуйте онлайн!

Мы хотим сделать выборку равномерно из множества A~знак равно{вес1,...,весN:я,0<веся<1;Σвесязнак равноs}весяsAзнак равно{вес1,...,весN:я,0<веся<1s;Σвесязнак равно1}

Если sзнак равно1DярясчасLеT(1,1,...,1) s1<1/ss

s>N/2

Робин Райдер
источник
2

C 132 127 125 118 110 107 байт

-2 байта благодаря @ceilingcat

i;f(s,n,o,d)float*o,s,d;{for(i=n;i;o[--i]=d=s/n);for(;i<n;o[++i%n]-=s)o[i]+=s=(d<.5?d:1-d)*rand()/(1<<31);}

Попробуйте онлайн!

OverclockedSanic
источник
К сожалению, этот ответ не соответствует спецификации задачи. Выходные случайные числа не ограничены [0,1], и их совместное распределение не является равномерным.
Нитродон
@Nitrodon Эй, не могли бы вы предоставить вход, для которого выход не ограничен [0,1]? Я попробовал пару разных примеров, и все они казались правильными, если я не понял цель.
разогнанный
С состоянием RNG на TIO и сохранением ваших n=4значений s=3.23и s=0.89выводов за пределы диапазона. Более того, распределение X-s/nдолжно зависеть от s, но это не так.
Нитродон
@Nitrodon Ой, мой плохой. Я конвертировал некоторые части из ответа C ++ выше и забыл добавить что-то. Это должно быть исправлено сейчас? Также в процессе игры в гольф несколько байтов.
Разогнанный
1

Haskell , 122 217 208 байт

import System.Random
r p=randomR p
(n#s)g|n<1=[]|(x,q)<-r(max 0$s-n+1,min 1 s)g=x:((n-1)#(s-x)$q)
g![]=[]
g!a|(i,q)<-r(0,length a-1)g=a!!i:q![x|(j,x)<-zip[0..]a,i/=j]
n%s=uncurry(!).(n#s<$>).split<$>newStdGen

Попробуйте онлайн!

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

Основная идея состоит в том, чтобы сгенерировать число, xзатем вычесть его sи повторять до тех пор, пока мы не получим nэлементы, а затем не перемешать их. Я генерирую xс верхней границей 1 или s(в зависимости от того, что меньше) и нижней границей s-n+1или 0 (в зависимости от того, что больше). Эта нижняя граница существует для того, чтобы на следующей итерации sона была меньше или равна n(производная: s-x<=n-1-> s<=n-1+x-> s-(n-1)<=x-> s-n+1<=x).

РЕДАКТИРОВАТЬ: Спасибо @ michi7x7 за указание на недостаток в моей однородности. Я думаю, что я исправил это с тасованием, но дайте мне знать, если есть другая проблема

EDIT2: улучшено количество байтов плюс фиксированное ограничение типа

user1472751
источник
3
Цепочка однородных выборок никогда не приведет к равномерному распределению (последняя координата почти всегда больше 0,99 в вашем примере)
michi7x7
@ michi7x7 Я понимаю твою точку зрения. Что если я перетасовал порядок списка после его генерации? Я должен был взять больше классов статистики
user1472751
Числа не выглядят очень однородными. Здесь 8 результатов> 0,99, 1 0,96, а последний 0,8.это как это выглядит.
Стьюи Гриффин
@ user1472751 здесь есть несколько хороших ответов: stackoverflow.com/q/8064629/6774250
michi7x7
1
Однородность все еще имеет некоторую проблему, смотрите здесь - генерируется слишком много нулей (график отсортированных значений от 1000% 500)
Angs
1

Haskell , 188 байт

import System.Random
import Data.List
n!s|s>n/2=map(1-)<$>n!(n-s)|1>0=(zipWith(-)=<<tail).sort.map(*s).(++[0,1::Double])<$>mapM(\a->randomIO)[2..n]>>= \a->if all(<=1)a then pure a else n!s

Ungolfed:

n!s
 |s>n/2       = map (1-) <$> n!(n-s)       --If total more than half the # of numbers, mirror calculation 
 |otherwise   = (zipWith(-)=<<tail)        --Calculate interval lengths between consecutive numbers
              . sort                       --Sort
              . map(*s)                    --Scale
              . (++[0,1::Double])          --Add endpoints
              <$> mapM(\a->randomIO)[2..n] --Calculate n-1 random numbers
              >>= \a->if all(<=1)a then pure a else n!s   --Retry if a number was too large

Попробуйте онлайн!

Angs
источник