Numeri casuali con somma fissa


32

Il tuo compito è scrivere un programma o una funzione che emetta n numeri casuali dall'intervallo [0,1] con somma fissa s.

Ingresso

n, n≥1, numero di numeri casuali da generare

s, s>=0, s<=n, somma dei numeri da generare

Produzione

Una ncoppia casuale di numeri in virgola mobile con tutti gli elementi dell'intervallo [0,1] e la somma di tutti gli elementi uguali s, viene emessa in modo conveniente e non ambiguo. Tutte le ncoppie valide devono essere ugualmente probabili entro i limiti dei numeri in virgola mobile.

Ciò equivale a campionare in modo uniforme dall'intersezione dei punti all'interno del ncubo dell'unità dimensionale e n-1dell'iperpiano dimensionale che attraversa (s/n, s/n, …, s/n)ed è perpendicolare al vettore (1, 1, …, 1)(vedere l'area rossa in Figura 1 per tre esempi).

Esempi con n = 3 e somme 0,75, 1,75 e 2,75

Figura 1: il piano delle uscite valide con n = 3 e somme 0,75, 1,75 e 2,75

Esempi

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]

Regole

  • Il tuo programma dovrebbe terminare in meno di un secondo sul tuo computer almeno con n≤10e tutti i messaggi di posta elettronica validi.
  • Se lo desideri, il tuo programma può essere esclusivo sull'estremità superiore, ovvero s<ni numeri di output dell'intervallo semi-aperto [0,1) (interrompendo il secondo esempio)
  • Se la tua lingua non supporta numeri in virgola mobile, puoi falsificare l'output con almeno dieci cifre decimali dopo il punto decimale.
  • Non sono consentite scappatoie standard e sono consentiti metodi di input / output standard.
  • Si tratta di , quindi vince la voce più breve, misurata in byte.


Quando dici This is equal to uniformly sampling from the intersection: vedo un programma scegliere casualmente solo dagli angoli di quell'intersezione. Sarebbe valido?
JayCe,

2
@KevinCruijssen No, è vero solo per s==0 or s==3. Per tutti gli altri valori di s, il piano ha un'area diversa da zero e devi scegliere in modo casuale un punto su quel piano.
user202729,

3
Richiedere che l'intervallo sia chiuso o semichiuso (anziché aperto) è un requisito teoricamente non osservabile. Molti generatori di numeri casuali forniscono l'output in (0,1). Come verificare che l'intervallo di output sia [0,1) e non (0,1)? Il valore 0 "mai" si verifica comunque
Luis Mendo

2
Va bene se il nostro codice utilizza il campionamento di rifiuto e quindi impiega molto tempo per casi di test come s=2.99999999999, n=3? Possiamo generare reali casuali in multipli di, diciamo 1e-9,?
xnor

Risposte:


1

Wolfram Language (Mathematica) , 92 90 byte

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

Provalo online!

Codice non golfato:

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

Ecco una soluzione che funziona in 55 byte, ma per ora (Mathematica versione 12) è limitata n=1,2,3perché si RandomPointrifiuta di disegnare punti da iperpiani di dimensioni superiori (nella versione 11.3 di TIO fallisce anche n=1). Potrebbe funzionare nin futuro in futuro:

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

Provalo online!

Codice non golfato:

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] *)



4

Java 8, 194 188 196 237 236 byte

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 byte (188 → 196 e 196 → 237) per fissare la velocità dei casi di test vicino a 1, nonché per correggere l'algoritmo in generale.

Provalo online

Spiegazione:

Utilizza l'approccio in questa risposta StackoverFlow , all'interno di un ciclo, purché uno degli elementi sia ancora più grande di 1.
Inoltre, se 2*s>n, sverrà modificato in n-se verrà impostato un flag per indicare che dovremmo usare 1-diffinvece che diffnella matrice dei risultati (grazie per la punta @soktinpk e @ 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

Time out pertest(10, 9.99);
l4m2

@ l4m2 Sì, ho notato la stessa cosa 10, 9.0subito dopo aver modificato per risolvere il n=10, s=9.999999999999caso di test .. Non sono sicuro che ci sia una correzione in Java pur mantenendo la sua casualità uniforme .. Dovrà pensarci per un po '. Per ora lo modificherò per dirlo in timeout.
Kevin Cruijssen,

Se n-s<1puoi chiamare f(n,n-s)e capovolgere ogni numero 1/2(cioè sostituirlo xcon 1-x) come ha fatto l4m2. Questo potrebbe risolvere il problema per i numeri nelle svicinanze n.
soktinpk,

@soktinpk Grazie per il suggerimento. In realtà è s+s>ninvece di n-s<1, ma quando ho guardato le altre risposte JavaScript aveva davvero senso. Ora tutto è stato risolto, incluso un altro bug ancora presente. I byte sono aumentati un po ', ma ora funziona. Funzionerà il conto alla rovescia da qui. :)
Kevin Cruijssen,

Non conosco una prova generale, ma credo che questo algoritmo funzioni perché un ipercubo N-dimensionale può essere tagliato in iperpiramidi N-dimensionali.
Neil,


3

C ++ 11, 284 267 byte

-17 byte grazie alla
libreria casuale C ++ di Zacharý , output sull'output standard

#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]<<' ';}

Per chiamare, devi solo farlo:

g<2>(0.0);

Dove il parametro template (qui, 2) è N e il parametro effettivo (qui, 0,0) è S


Penso che puoi rimuovere lo spazio tra <z>eu
Zacharý,

Ho capito ulteriormente verso il basso: 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]<<' ';}. Una nuova riga non deve essere il separatore tra gli elementi
Zacharý,

1
Suggerisci di eliminare dcompletamente cambiando d=s/Nin s/=NSuggerisci rielaborando il secondo ciclo for(z c;i<N;a[++i%N]-=c)a[i]+=c=u(e);invece di for(;i<N;){z c=u(e);a[i]+=c;a[++i]-=c;}(nota l'aggiunta %Nper fare in modo che il programma calcoli correttamente il primo numero)
Max Yekhlakov

2

Pulito , 221 201 byte

Numeri puliti, da golf o casuali. Prendine due.

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

Provalo online!

Funzione parziale letterale :: (Int Real -> [Real]). Produrrà nuovi risultati solo una volta al secondo.
Preciso fino a almeno 10 decimali.


2

R , 99 byte (con gtoolspacchetto)

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}

Provalo online!

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

C, 132 127 125 118 110 107 byte

-2 byte grazie a @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);}

Provalo online!


Sfortunatamente, questa risposta non soddisfa le specifiche della sfida. I numeri casuali di output non sono limitati a [0,1], e la loro distribuzione congiunta non è uniforme.
Nitrodon,

@Nitrodon hey, potresti fornire un input per il quale l'output non è limitato a [0,1]? Ho provato un paio di esempi diversi e sembravano tutti corretti, a meno che non avessi frainteso l'obiettivo.
OverclockedSanic

Con lo stato RNG su TIO e mantenendo i tuoi n=4valori s=3.23e s=0.89fornisce output al di fuori dell'intervallo. Più precisamente, la distribuzione di X-s/ndovrebbe dipendere s, ma non è così.
Nitrodon,

@Nitrodon Oops, mia cattiva. Stavo convertendo alcune parti dalla risposta C ++ sopra e ho dimenticato di aggiungere qualcosa. Dovrebbe essere riparato ora? Ha anche giocato a golf pochi byte nel processo.
OverclockedSanic il

1

Haskell , 122 217 208 byte

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

Provalo online!

A volte le risposte sono leggermente spente a causa, presumo, di un errore in virgola mobile. Se si tratta di un problema, posso risolverlo al costo di 1 byte. Inoltre non sono sicuro di quanto sia uniforme (abbastanza sicuro che vada bene, ma non sono poi così bravo in questo genere di cose), quindi descriverò il mio algoritmo.

L'idea di base è generare un numero, xquindi sottrarlo se ricorrere fino a quando non abbiamo nelementi, quindi mescolarli. Genero xcon un limite superiore di 1 o s(qualunque sia il più piccolo) e un limite inferiore di s-n+1o 0 (qualunque sia il maggiore). Il limite inferiore è presente in modo tale che alla successiva iterazione ssarà comunque inferiore o uguale a n(derivazione: s-x<=n-1-> s<=n-1+x-> s-(n-1)<=x-> s-n+1<=x).

EDIT: Grazie a @ michi7x7 per aver sottolineato un difetto nella mia uniformità. Penso di averlo corretto con il mescolamento, ma fammi sapere se c'è un altro problema

EDIT2: Conteggio byte migliorato più restrizione di tipo fisso


3
Concatenare campioni uniformi non porterà mai a una distribuzione uniforme (l'ultima coordinata è quasi sempre maggiore di 0,99 nel tuo esempio)
michi7x7,

@ michi7x7 Vedo il tuo punto. E se avessi mischiato l'ordine dell'elenco dopo averlo generato? Avrei dovuto prendere più classi di statistiche
user1472751

I numeri non sembrano molto uniformi. Qui , 8 risultati sono> 0,99, 1 è 0,96 e l'ultimo è 0,8. Questo è quello che sembra.
Stewie Griffin,

@ user1472751 ci sono diverse risposte buone qui: stackoverflow.com/q/8064629/6774250
michi7x7

1
L'uniformità ha ancora qualche problema, vedi qui - ci sono troppi zero generati (
grafico

1

Haskell , 188 byte

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

Provalo online!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.