Campionamento casuale senza sostituzione


10

Creare una funzione che produrrà un insieme di numeri casuali distinti estratti da un intervallo. L'ordine degli elementi nell'insieme non è importante (possono anche essere ordinati), ma deve essere possibile che il contenuto dell'insieme sia diverso ogni volta che viene chiamata la funzione.

La funzione riceverà 3 parametri nell'ordine desiderato:

  1. Conteggio dei numeri nel set di output
  2. Limite inferiore (incluso)
  3. Limite superiore (incluso)

Supponiamo che tutti i numeri siano numeri interi compresi nell'intervallo da 0 (incluso) a 2 31 (esclusivo). L'output può essere restituito nel modo desiderato (scrivere sulla console, come array, ecc.)

A giudicare

I criteri includono le 3 R

  1. Runtime : testato su un computer Windows 7 quad-core con qualsiasi compilatore disponibile liberamente o facilmente (fornire un collegamento se necessario)
  2. Robustezza - la funzione gestisce casi angolari o cadrà in un ciclo infinito o produrrà risultati non validi - è valida un'eccezione o un errore sull'input non valido
  3. Casualità - dovrebbe produrre risultati casuali che non sono facilmente prevedibili con una distribuzione casuale. L'uso del generatore di numeri casuali incorporato va bene. Ma non dovrebbero esserci pregiudizi evidenti o schemi prevedibili evidenti. Deve essere migliore di quel generatore di numeri casuali utilizzato dal dipartimento contabilità di Dilbert

Se è robusto e casuale, si riduce al runtime. Non essere robusto o casuale fa molto male alla sua posizione.


L'uscita dovrebbe superare qualcosa come i test DIEHARD o TestU01 o come giudicherai la sua casualità? Oh, e il codice dovrebbe essere eseguito in modalità 32 o 64 bit? (Ciò farà una grande differenza per l'ottimizzazione.)
Ilmari Karonen,

TestU01 è probabilmente un po 'duro, immagino. Il criterio 3 implica una distribuzione uniforme? Inoltre, perché il requisito non ripetitivo ? Non è particolarmente casuale, quindi.
Joey,

@Joey, certo che lo è. È un campionamento casuale senza sostituzione. Finché nessuno afferma che le diverse posizioni nell'elenco sono variabili casuali indipendenti, non c'è problema.
Peter Taylor,

Ah davvero. Ma non sono sicuro che ci siano librerie e strumenti ben consolidati per misurare la casualità del campionamento :-)
Joey,

@IlmariKaronen: RE: Casualità: ho visto implementazioni prima che erano terribilmente non casuali. O avevano un forte pregiudizio o mancavano della capacità di produrre risultati diversi su corse consecutive. Quindi non stiamo parlando di casualità a livello crittografico, ma più casuale del generatore di numeri casuali del dipartimento contabilità di Dilbert .
Jim McKeeth,

Risposte:


6

Pitone

import random

def sample(n, lower, upper):
    result = []
    pool = {}
    for _ in xrange(n):
        i = random.randint(lower, upper)
        x = pool.get(i, i)
        pool[i] = pool.get(lower, lower)
        lower += 1
        result.append(x)
    return result

Probabilmente ho appena reinventato un noto algoritmo, ma l'idea è quella di eseguire (concettualmente) un shuffle Fisher-Yates parziale dell'intervallo lower..upperper ottenere il nprefisso di lunghezza di un intervallo mischiato uniformemente.

Naturalmente, memorizzare l'intera gamma sarebbe piuttosto costoso, quindi memorizzo solo le posizioni in cui gli elementi sono stati scambiati.

In questo modo, l'algoritmo dovrebbe funzionare bene sia nel caso in cui stai campionando numeri da un intervallo ristretto (ad esempio 1000 numeri nell'intervallo 1..1000), sia nel caso in cui stai campionando numeri da un ampio intervallo .

Non sono sicuro della qualità della casualità dal generatore integrato in Python, ma è relativamente semplice scambiare qualsiasi generatore in grado di generare numeri interi in modo uniforme da un certo intervallo.


1
Python usa Mersenne Twister , quindi è relativamente decente.
ESultanik,

1

python 2.7

import random
print(lambda x,y,z:random.sample(xrange(y,z),x))(input(),input(),input())

non sei sicuro di quale sia la tua posizione usando metodi casuali integrati, ma qui vai comunque. bello e breve

modifica: ho appena notato che a range () non piace fare grandi liste. provoca un errore di memoria. vedrà se c'è un altro modo per farlo ...

edit2: range era la funzione sbagliata, xrange funziona. L'intero massimo è effettivamente 2**31-1per Python

test:

python sample.py
10
0
2**31-1
[786475923, 2087214992, 951609341, 1894308203, 173531663, 211170399, 426989602, 1909298419, 1424337410, 2090382873]

1

C

Restituisce un array contenente x inattuali casuali tra min e max. (il chiamante deve liberare)

#include <stdlib.h>
#include <stdint.h>
#define MAX_ALLOC ((uint32_t)0x40000000)  //max allocated bytes, fix per platform
#define MAX_SAMPLES (MAX_ALLOC/sizeof(uint32_t))

int* randsamp(uint32_t x, uint32_t min, uint32_t max)
{
   uint32_t r,i=x,*a;
   if (!x||x>MAX_SAMPLES||x>(max-min+1)) return NULL;
   a=malloc(x*sizeof(uint32_t));
   while (i--) {
      r= (max-min+1-i);
      a[i]=min+=(r ? rand()%r : 0);
      min++;
   }
   while (x>1) {
      r=a[i=rand()%x--];
      a[i]=a[x];
      a[x]=r;
   }
   return a;
}

Funziona generando x numeri interi casuali sequenziali nell'intervallo, quindi mescolandoli. Aggiungi un seed(time)posto nel chiamante se non vuoi gli stessi risultati ad ogni corsa.


1

Rubino> = 1.8.7

def pick(num, min, max)
  (min..max).to_a.sample(num)
end

p pick(5, 10, 20) #=>[12, 18, 13, 11, 10]

1

R

s <- function(n, lower, upper) sample(lower:upper,n); s(10,0,2^31-2)

1

La domanda non è corretta Hai bisogno di un campionamento uniforme o no? In è necessario il campionamento caso uniforme Ho il seguente codice in R, che ha media complessità O ( s log s ), dove s è la dimensione del campione.

# The Tree growing algorithm for uniform sampling without replacement
# by Pavel Ruzankin 
quicksample = function (n,size)
# n - the number of items to choose from
# size - the sample size
{
  s=as.integer(size)
  if (s>n) {
    stop("Sample size is greater than the number of items to choose from")
  }
  # upv=integer(s) #level up edge is pointing to
  leftv=integer(s) #left edge is poiting to; must be filled with zeros
  rightv=integer(s) #right edge is pointig to; must be filled with zeros
  samp=integer(s) #the sample
  ordn=integer(s) #relative ordinal number

  ordn[1L]=1L #initial value for the root vertex
  samp[1L]=sample(n,1L) 
  if (s > 1L) for (j in 2L:s) {
    curn=sample(n-j+1L,1L) #current number sampled
    curordn=0L #currend ordinal number
    v=1L #current vertice
    from=1L #how have come here: 0 - by left edge, 1 - by right edge
    repeat {
      curordn=curordn+ordn[v]
      if (curn+curordn>samp[v]) { #going down by the right edge
        if (from == 0L) {
          ordn[v]=ordn[v]-1L
        }
        if (rightv[v]!=0L) {
          v=rightv[v]
          from=1L
        } else { #creating a new vertex
          samp[j]=curn+curordn
          ordn[j]=1L
          # upv[j]=v
          rightv[v]=j
          break
        }
      } else { #going down by the left edge
        if (from==1L) {
          ordn[v]=ordn[v]+1L
        }
        if (leftv[v]!=0L) {
          v=leftv[v]
          from=0L
        } else { #creating a new vertex
          samp[j]=curn+curordn-1L
          ordn[j]=-1L
          # upv[j]=v
          leftv[v]=j
          break
        }
      }
    }
  }
  return(samp)  
}

Naturalmente, si può riscriverlo in C per prestazioni migliori. La complessità di questo algoritmo è discussa in: Rouzankin, PS; Voytishek, AV Sul costo degli algoritmi per la selezione casuale. Metodi Monte Carlo Appl. 5 (1999), n. 1, 39-54. http://dx.doi.org/10.1515/mcma.1999.5.1.39

Puoi cercare in questo documento un altro algoritmo con la stessa complessità media.

Ma se non hai bisogno di un campionamento uniforme, richiedendo solo che tutti i numeri campionati siano diversi, la situazione cambia radicalmente. Non è difficile scrivere un algoritmo con complessità media O ( s ).

Vedi anche per campionamento uniforme: P. Gupta, GP Bhattacharjee. (1984) Un algoritmo efficiente per il campionamento casuale senza sostituzione. International Journal of Computer Mathematics 16: 4, pagine 201-209. DOI: 10.1080 / 00207168408803438

Teuhola, J. e Nevalainen, O. 1982. Due algoritmi efficienti per il campionamento casuale senza sostituzione. / IJCM /, 11 (2): 127–140. DOI: 10.1080 / 00207168208803304

Nell'ultimo documento gli autori usano le tabelle hash e affermano che i loro algoritmi hanno complessità O ( s ). Esiste un altro algoritmo di tabella hash veloce, che sarà presto implementato in pqR (R abbastanza veloce): https://stat.ethz.ch/pipermail/r-devel/2017-October/075012.html


1

APL, 18 22 byte

{⍵[0]+(1↑⍺)?⍵[1]-⍵[0]}

Dichiara una funzione anonima che accetta due argomenti e . è il numero di numeri casuali che desideri, è un vettore contenente i limiti inferiore e superiore, in quell'ordine.

a?bseleziona anumeri casuali tra 0 e bsenza sostituzione. Prendendo ⍵[1]-⍵[0]otteniamo le dimensioni dell'intervallo. Quindi selezioniamo i numeri (vedi sotto) da quell'intervallo e aggiungiamo il limite inferiore. In C, questo sarebbe

lower + rand() * (upper - lower)

volte senza sostituzione. Le parentesi non sono necessarie perché APL opera da destra a sinistra.

Supponendo di aver compreso correttamente le condizioni, questo non soddisfa i criteri di "robustezza" perché la funzione fallirà se vengono forniti argomenti non corretti (ad es. Passare un vettore anziché uno scalare come ).

Nel caso in cui sia un vettore anziché uno scalare, 1↑⍺accetta il primo elemento di . Per uno scalare, questo è lo scalare stesso. Per un vettore, è il primo elemento. Ciò dovrebbe far sì che la funzione soddisfi i criteri di "robustezza".

Esempio:

Input: 100 {⍵[0]+⍺?⍵[1]-⍵[0]} 0 100
Output: 34 10 85 2 46 56 32 8 36 79 77 24 90 70 99 61 0 21 86 50 83 5 23 27 26 98 88 66 58 54 76 20 91 72 71 65 63 15 33 11 96 60 43 55 30 48 73 75 31 13 19 3 45 44 95 57 97 37 68 78 89 14 51 47 74 9 67 18 12 92 6 49 41 4 80 29 82 16 94 52 59 28 17 87 25 84 35 22 38 1 93 81 42 40 69 53 7 39 64 62

2
Questo non è un codice golf ma una cosa più veloce, quindi l'obiettivo è produrre il codice più veloce per eseguire l'attività piuttosto che il più breve. Ad ogni modo, non hai davvero bisogno di scegliere gli oggetti dagli argomenti del genere e puoi determinarne l'ordine, quindi {⍵+⍺?⎕-⍵}dovrebbe bastare, dove il prompt è per il limite superiore e l'arg destro è il limite inferiore
Uriel

0

Scala

object RandSet {
  val random = util.Random 

  def rand (count: Int, lower: Int, upper: Int, sofar: Set[Int] = Set.empty): Set[Int] =
    if (count == sofar.size) sofar else 
    rand (count, lower, upper, sofar + (random.nextInt (upper-lower) + lower)) 
}

object RandSetRunner {

  def main (args: Array [String]) : Unit = {
    if (args.length == 4) 
      (0 until args (0).toInt).foreach { unused => 
      println (RandSet.rand (args (1).toInt, args (2).toInt, args (3).toInt).mkString (" "))
    }
    else Console.err.println ("usage: scala RandSetRunner OUTERCOUNT COUNT MIN MAX")
  }
}

compilare ed eseguire:

scalac RandSetRunner.scala 
scala RandSetRunner 200 15 0 100

La seconda riga eseguirà 200 test con 15 valori compresi tra 0 e 100, poiché Scala produce un bytecode veloce ma richiede alcuni tempi di avvio. Quindi 200 inizia con 15 valori da 0 a 100 consumerebbero più tempo.

Esempio su un single core da 2 Ghz:

time scala RandSetRunner 100000 10 0 1000000 > /dev/null

real    0m2.728s
user    0m2.416s
sys     0m0.168s

Logica:

Usando il build-in casuale e selezionando ricorsivamente i numeri nell'intervallo (max-min), aggiungendo min e controllando, se la dimensione del set è la dimensione prevista.

Critica:

  • Sarà veloce per piccoli campioni di grandi intervalli, ma se il compito è quello di selezionare quasi tutti gli elementi di un campione (999 numeri su 1000) selezionerà ripetutamente i numeri, già nel set.
  • Dalla domanda, non sono sicuro, se devo disinfettare contro richieste insoddisfacenti come Prendi 10 numeri distinti da 4 a 8. Questo ora porterà a un ciclo infinito, ma può essere facilmente evitato con un controllo preventivo che aggiungerò se richiesto.

0

schema

Non sono sicuro del motivo per cui hai bisogno di 3 parametri passati né perché devo assumere qualsiasi intervallo ...

(import srfi-1) ;; for iota
(import srfi-27) ;; randomness
(import srfi-43) ;; for vector-swap!

(define rand (random-source-make-integers
               default-random-source))

;; n: length, i: lower limit
(define (random-range n i)
  (let ([v (list->vector (iota n i))])
    (let f ([n n])
      (let* ([i (rand n)] [n (- n 1)])
        (if (zero? n) v
            (begin (vector-swap! v n i) (f n)))))))

0

R

random <- function(count, from, to) {
  rand.range <- to - from

  vec <- c()

  for (i in 1:count) {
    t <- sample(rand.range, 1) + from
    while(i %in% vec) {
      t <- sample(rand.range, 1) + from
    }
    vec <- c(vec, t)
  }

  return(vec)
}

0

C ++

Questo codice è il migliore quando si prelevano molti campioni dall'intervallo.

#include <exception>
#include <stdexcept>
#include <cstdlib>

template<typename OutputIterator>
 void sample(OutputIterator out, int n, int min, int max)
{
  if (n < 0)
    throw std::runtime_error("negative sample size");
  if (max < min)
    throw std::runtime_error("invalid range");
  if (n > max-min+1)
    throw std::runtime_error("sample size larger than range");

  while (n>0)
  {
    double r = std::rand()/(RAND_MAX+1.0);
    if (r*(max-min+1) < n)
    {
      *out++ = min;
      --n;
    }
    ++min;
  }
}

Questo può facilmente rimanere bloccato in un ciclo infinito a meno che non max-minsia molto più grande di n. Inoltre, la sequenza di output sta aumentando monotonicamente, quindi stai ottenendo una casualità di qualità molto bassa ma stai ancora pagando il costo della chiamata rand()più volte per risultato. Uno shuffle casuale dell'array varrebbe probabilmente il tempo di esecuzione extra.
Peter Cordes,

0

Q (19 caratteri)

f:{(neg x)?y+til z}

Quindi utilizzare f [x; y; z] come [conteggio dei numeri nel set di output; punto iniziale; dimensione dell'intervallo]

es. f [5; 10; 10] produrrà 5 numeri casuali distinti tra 10 e 19 inclusi.

q)\ts do[100000;f[100;1;10000]]
2418 131456j

I risultati sopra mostrano prestazioni a 100.000 iterazioni di selezione di 100 numeri casuali tra 1 e 10.000.


0

R, 31 o 40 byte (a seconda del significato della parola "intervallo")

Se l'input ha 3 numeri, a[1], a[2], a[3]e per "intervallo" intendi "una sequenza intera da un [2] a un [3]", allora hai questo:

a=scan();sample(a[2]:a[3],a[1])

Se hai un array nda cui stai per ricampionare, ma sotto la limitazione dei limiti inferiore e superiore, come "ricampiona i valori dell'array dato ndall'intervallo a[1]...a[2]", usa questo:

a=scan();sample(n[n>=a[2]&n<=a[3]],a[1])

Sono abbastanza sorpreso dal fatto che il risultato precedente non sia stato golfato considerando il campione incorporato con strutture di sostituzione! Creiamo un vettore che soddisfa la condizione dell'intervallo e lo ricampiona.

  • Robustezza: le custodie angolari (sequenze della stessa lunghezza dell'intervallo da cui campionare) sono gestite per impostazione predefinita.
  • Run-time: estremamente veloce perché integrato.
  • Casualità: il seme viene automaticamente cambiato ogni volta che viene invocato l'RNG.

almeno sulla mia macchina, 0:(2^31)provoca unError: cannot allocate a vector of size 16.0 Gb
Giuseppe

@Giuseppe Di recente, ho lavorato con problemi di memoria, e la soluzione è in realtà ... eseguirlo su una macchina migliore. Le restrizioni nella formulazione dell'attività riguardano il processore, non la memoria, quindi è ... abuso di regole? Ah, sono un asino. Ho pensato che fosse una sfida al golf del codice , ma in realtà è ... il codice più veloce. Perdo, credo?
Andreï Kostyrka,

0

Javascript (utilizzando una libreria esterna) (64 byte / 104 byte ??)

(a,b,n)=>_.Range(0,n).Select(x=>Math.random()*(b-a)+a).ToArray()

Link alla lib: https://github.com/mvegh1/Enumerable/

Spiegazione del codice: l'espressione Lambda accetta min, max, conta come args. Crea una raccolta di dimensioni n e associa ogni elemento a un numero casuale che soddisfi i criteri min / max. Converti in array JS nativo e restituiscilo. Ho eseguito questo anche su un input di dimensioni 5.000.000 e dopo aver applicato una trasformazione distinta ho ancora mostrato 5.000.000 di elementi. Se si concorda sul fatto che questo non è abbastanza sicuro di una garanzia di distinzione, aggiornerò la risposta

Ho incluso alcune statistiche nell'immagine qui sotto ...

inserisci qui la descrizione dell'immagine

EDIT: L'immagine sotto mostra il codice / prestazioni che garantisce che ogni elemento sarà distinto. È molto più lento (6,65 secondi per 50.000 elementi) rispetto al codice originale sopra per gli stessi argomenti (0,012 secondi)

inserisci qui la descrizione dell'immagine


0

K (oK) , 14 byte

Soluzione:

{y+(-x)?1+z-y}

Provalo online!

Esempio:

> {y+(-x)?1+z-y}. 10 10 20      / note: there are two ways to provide input, dot or
13 20 16 17 19 10 14 12 11 18
> {y+(-x)?1+z-y}[10;10;20]      / explicitly with [x;y;z]
12 11 13 19 15 17 18 20 14 10

Spiegazione:

Accetta 3 input impliciti per specifica:

  • x, conteggio dei numeri nel set di output,
  • y, limite inferiore (incluso)
  • z, limite superiore (compreso)

{y+(-x)?1+z-y} / the solution
{            } / lambda function with x, y and z as implicit inputs
          z-y  / subtract lower limit from upper limit
        1+     / add 1
   (-x)?       / take x many distinct items from 0..(1+z=y)
 y+            / add lower limit

Appunti:

Anche un poliglotta q/kdb+con un ulteriore set di parentesi: {y+((-)x)?1+z-y}(16 byte).


0

Axiom + la sua biblioteca

f(n:PI,a:INT,b:INT):List INT==
    r:List INT:=[]
    a>b or n>99999999 =>r
    d:=1+b-a
    for i in 1..n repeat
          r:=concat(r,a+random(d)$INT)
    r

La precedente funzione f () restituisce come errore l'elenco vuoto, nel caso f (n, a, b) con a> b. In altri casi di input non valido, non viene eseguito con un messaggio di errore nella finestra di Axiom, poiché l'argomento non sarà del tipo giusto. Esempi

(6) -> f(1,1,5)
   (6)  [2]
                                                       Type: List Integer
(7) -> f(1,1,1)
   (7)  [1]
                                                       Type: List Integer
(10) -> f(10,1,1)
   (10)  [1,1,1,1,1,1,1,1,1,1]
                                                       Type: List Integer
(11) -> f(10,-20,-1)
   (11)  [- 10,- 4,- 18,- 5,- 5,- 11,- 15,- 1,- 20,- 1]
                                                       Type: List Integer
(12) -> f(10,-20,-1)
   (12)  [- 4,- 5,- 3,- 4,- 18,- 1,- 2,- 14,- 19,- 8]
                                                       Type: List Integer
(13) -> f(10,-20,-1)
   (13)  [- 18,- 12,- 12,- 19,- 19,- 15,- 5,- 17,- 19,- 4]
                                                       Type: List Integer
(14) -> f(10,-20,-1)
   (14)  [- 8,- 11,- 20,- 10,- 4,- 8,- 11,- 3,- 10,- 16]
                                                       Type: List Integer
(15) -> f(10,9,-1)
   (15)  []
                                                       Type: List Integer
(16) -> f(10,0,100)
   (16)  [72,83,41,35,27,0,33,18,60,38]
                                                       Type: List Integer
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.