Випадкові числа з фіксованою сумою


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) (порушуючи другий приклад)
  • Якщо ваша мова не підтримує числа з плаваючою комою, ви можете підробити висновок принаймні десятьма десятковими цифрами після десяткової крапки.
  • Стандартні лазівки заборонені, а стандартні методи введення / виводу дозволені.
  • Це , тому найкоротший запис, виміряний в байтах, виграє.


Коли ви говорите This is equal to uniformly sampling from the intersection- я бачу програму, що вибирає випадковим чином лише з кутів цього перехрестя. Чи було б це дійсно?
JayCe

2
@KevinCruijssen Ні, це справедливо лише для s==0 or s==3. Для всіх інших значень s, площина має ненульову область, і вам доведеться рівномірно обрати випадкову точку на цій площині.
користувач202729

3
Вимога інтервалу бути закритим або напівзакритим (на відміну від відкритого) є теоретично невидимою вимогою. Багато генераторів випадкових чисел дають вихід у (0,1). Як перевірити, що інтервал виходу є [0,1), а не (0,1)? Значення 0 "ніколи" не виникає в будь-якому випадку
Луїс Мендо

2
Чи нормально, якщо наш код використовує вибірку відхилень, і так потрібно дуже багато часу для тестових випадків s=2.99999999999, n=3? Чи можемо ми генерувати випадкові результати в множинах, скажімо 1e-9,?
xnor

Відповіді:


1

Мова Вольфрама (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 байтах, але поки що (версія Mathematica 12) обмежена тим, 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 байт, завдяки Кевіну Крейсейну

@LuisMendo Слід виправити зараз
TFeld

вони все ще здаються не рівномірними
l4m2

@ l4m2 я бігав g(4, 2.0)1000 разів, щоб отримати 4000 очок, і результати виглядають так, що виглядають досить рівномірно.
Інженер Тост



4

Java 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


Я думаю, ви можете прибрати пробіл між <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 знаків після коми.


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~={w1,,wn:i,0<wi<1;wi=s}wisA={w1,,wn:i,0<wi<1s;wi=1}

s=1Dirichlet(1,1,,1) s1<1/ss

s>n/2


2

С, 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);}

Спробуйте в Інтернеті!


На жаль, ця відповідь не відповідає специфікації виклику. Вихідні випадкові числа не обмежуються [0,1], і їх спільний розподіл не є рівномірним.
Нітродон

@Nitrodon Гей, чи можете ви надати введення, для якого вихід не обмежений [0,1]? Я спробував кілька різних прикладів, і всі вони, здавалося, були правильними, якщо я неправильно зрозумів мету.
РозгінSanic

Коли стан RNG працює на TIO та зберігає ваші n=4, значення s=3.23та s=0.89результати виходять за межі діапазону. Більше того, розподіл X-s/nмає залежати s, але це не залежить .
Нітродон

@Nitrodon На жаль, мені погано. Я перетворював деякі частини з відповіді С ++ вище і забув додати щось. Це слід виправити зараз? Також у процесі гри було кілька байтів.
РозгінSanic

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

EDIT: Дякуємо @ michi7x7, що вказав на недолік моєї рівномірності. Я думаю, що я вирішив це з перетасуванням, але повідомте мені, чи є інша проблема

EDIT2: покращена кількість байтів плюс обмеження фіксованого типу


3
Зв'язування рівномірних зразків ніколи не призведе до рівномірного розподілу (остання координата у вашому прикладі майже завжди більша за 0,99)
michi7x7

@ michi7x7 Я бачу вашу думку. Що робити, якщо я змішував порядок списку після його створення? Я повинен був взяти більше уроків статистики
користувач1472751

Цифри виглядають не дуже однаково. Тут 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

Безголівки:

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

Спробуйте в Інтернеті!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.