Newsgroup: it.comp.lang.perl
Titolo: Numeri casuali.

Salve a tutti,
Ho bisogno di ottenere 3 numeri interi casuali diversi tra loro, compresi 
tra 1 e il contenuto di una variabile es 15.
Con:
$numeroCOD = '15';
$CODA = int(rand $numeroCOD) + 1;
$CODB= int(rand $numeroCOD) + 1;
$CODC = int(rand $numeroCOD) + 1;
ottengo che le variabili COD A,B,C spesso sono identiche mentre a me servobo 
univoce.
Come posso fare ? 

Graziano ha scritto:


Posso proporti una soluzione con dei limiti di applicabilita':
se l' intervallo dei numeri e' ridotto al punto da poterlo memorizzare
interamente in un array (quindi non 2 pow 32) puoi adottare un metodo
che io ho usato per un mescolatore di mazzo di carte.
fai un array delle 40 carte da briscola e se vuoi sceglierne 3, generi
un primo numero casuale in intervallo 0 - 39.
ogni volta fai il lookup alla posizione dell' array e prendi la carte
o numero simbolico e ELIMINI QUELL' ELEMENTO DELL' ARRAY con splice
perldoc -f splice
l' array si ridimensione e quindi la volta successiva generi un mumero
casuale in intervallo ridotto di 1 cioe' da 0 a 38.
ripeti fino a che vuoi estrarre carte. se le estrai tutte e 40 hai un
mazzo di carte mescolato dal tuo generatore di numeri random, cioe'
hai mutuato da un generatore di numeri random un generatore di sequenze
random. Ci sarebbe molto da dire: non e' detto che la qualita' delle
sequenze sia buona come la qualita' dei numeri random, ma se non
fai macchinette per giochi d' azzardo dovrebbe andare bene.
naturalmente per set non memorizzabili in array devi usare un' altra
tecnica.
Ciao
Roberto.
-- 
questo articolo e` stato inviato via web dal servizio gratuito 
http://www.newsland.it/news
 segnala gli abusi ad 
ab...@newsland.it

Graziano wrote:

una volta avevo postato un one-liner proprio per questo problema :D
googola!
comunque hai 2 scelte:
1) crei una lista di interi da 1 a $VAR, la mischi e prendi i primi 3
elementi
2) crei un intero casuale tra 1 e $VAR, e, se non è già presente, lo metti
in una lista. Il tutto finche' non hai 3 elementi distinti
-- 
Under construction

use List::Util qw( shuffle );
# Compreso tra 1 e $numeroCOD
$numeroCOD = 15;
# Genera un array con elementi mischiati
my @numbers = shuffle(1..$numeroCOD);
# Elimina i primi tre elementi e li assegna a $cod[ABC]...
($codA, $codB, $codC) = splice(@numbers, 0, 3);
Graziano ha scritto:


 Come posso fare ? 
Ci sono diversi modi per farlo e sono stati gia` tutti suggeriti, per 
cui quello che segue e` un mero compendio dei messaggi precedenti (con 
l'aggiunta del codice e di qualche considerazione).
Il metodo piu` corretto ed elegante e` probabilmente quello di
/mescolare/ i numeri dell'intervallo che ti interessa (mediante la
funzione shuffle() di List::Util) e quindi prelevarne 3 qualsiasi, ad
es. i primi 3 (e` gia` stato suggerito da Daniele, che ha anche postato 
il codice):
use List::Util qw(shuffle);
my $numero_cod = 15;
my ($cod_a, $cod_b, $cod_c) = ( shuffle(1..$numero_cod) )[0, 1, 2];
Oppure metti i numeri in una lista (non mescolata), ed ogni volta estrai
un elemento a caso e lo elimini dalla lista, in pratica simulando
l'estrazione da un'urna senza restituzione:
my $numero_cod = 15;
my @urna = 1..$numero_cod;
my @estratti;
push @estratti, splice @urna, rand @urna, 1 for 1..3;
my ($cod_a, $cod_b, $cod_c) = @estratti;
O infine puoi semplicemente continuare ad estrarre il numero random se
si verifica una collisione con i numeri estratti in precedenza, fin
quando non ne avrai estratti 3 diversi tra loro:
my $numero_cod = 15;
my %estratto;
undef $estratto{1 + int rand $numero_cod} until keys %estratto == 3;
my ($cod_a, $cod_b, $cod_c) = keys %estratto;
Sul piano dell'efficienza, per valori (molto) piccoli di $numero_cod i 
primi due metodi sono i piu` convenienti, perche` la probabilità di 
collisione tra i numeri random e` elevata. Viceversa per valori elevati 
di $numero_cod la situazione si inverte, sia perche` la probabilita` di 
collisione si riduce, sia perche` la gestione di una lista grande 
richiede piu` tempo. Infine nota che il terzo metodo potrebbe, in 
teoria, non terminare (ma in pratica non accade ;-)
Questa stessa domanda era gia` stata fatta qualche tempo fa, con diverse 
soluzioni proposte (poi degenerate in un golf).
Ciao,
Emanuele.

my ($cod_a, $cod_b, $cod_c) = @estratti;
Non è necessario manipolare l'intera lista: si possono scambiare di posto
il numero estratto e l'ultimo numero della lista (quindi il secondo numero
estratto e il penultimo, il terzo e il terzultimo, e così via).
Riducendo l'intervallo random, gli ultimi numeri non vengono comunque
ripescati, e lo scambio di due numeri dovrebbe essere meno oneroso di uno
splice (quanto meno, non dovrebbe dipendere dalle dimensioni della lista).
Alla fine di un'estrazione multipla, la lista contiene una permutazione
casuale degli stessi numeri di partenza, quindi credo che potrebbe essere
riutilizzata tal quale per un nuovo ciclo, senza inficiare sostanzialmente
la casualità delle estrazioni, risparmiandosi anche la necessità di
ricrearla.
Ciao.

 ricrearla.
Certo. In pratica si tratta dell'algoritmo standard per lo shuffle 
(Fisher-Yates), il che peraltro conferma la sua correttezza.
Non so se in un contesto didattico possa costituire un'ottimizzazione 
prematura, ma si tratta senz'altro di una buona idea, senza dubbio piů 
efficiente della splice (almeno "da un certo punto in poi"), hai 
perfettamente ragione ;-)
Ciao,
Emanuele.

Grazie a tutti per l'aiuto.
Ma perchè i programmatori non hanno fatto in modo che il comando RAND possa 
accettare delle sclusioni ?
Per il caso sarebbe stato l'ideale.
Ovvero dire a rand, genera un numero casuale da 1 a 15 escludendo $numa 
$numb ecc.
Che ne pensate ? 

 Che ne pensate ?
Uhm...
my $numa = 7;
while ($numa eq 7) {
    $numa = scegli un numero casuale da 1 a 15
}
non va bene? :-)

Graziano ha scritto:

Basta scrivere una procedura del tipo:
sub my_rand {
  my ($max, @esclusi) = @_;
  my $ret = 1 + rand $max;
  while (grep { $_ == $ret } @esclusi) {
    $ret = 1 + rand $max;
  }
 return $ret;
}
oppure si può usare una cosa del genere:
sub my_rand2 {
  my $max = shift;
  my %esclusi = map { $_ => 1 } @_;
  my $ret = 1 + rand $max;
  while (exists $eculsi{$ret}) {
    $ret = 1 + rand $max;
  }
  return $ret;
}
Ed entrambe vanno chimamati con:
my_rand($valore_massimo, @valori_gia_estratti);

Graziano wrote:

In realtà era stato già fatto: mi sono appena imbattuto in un modulo 
CPAN, di cui non sospettavo minimamente l'esistenza, che si chiama Tie::Pick
http://search.cpan.org/search?query=Tie%3A%3APick&mode=module
che fa esattamente quello che chiedevi (e in cui ci siamo esercitati in 
molti), cioè estrarre un elemento a caso da una lista, eliminandolo 
successivamente dalla lista medesima (ad ogni accesso).
Tra l'altro è implementato mediante l'algoritmo super-efficiente 
Fisher-Yates che era stato suggerito da Giovanni Zezza.
(Trattandosi di un modulo Tie) il suo uso è di una semplicità strabiliante:
-----------------------------------------------------------------
use Tie::Pick;
tie my $estratto => 'Tie::Pick' => 1..15;
print "$estratto\n"; # Stampa un numero random tra 1 e 15;
print "$estratto\n"; # ne stampa un altro, diverso dal primo;
print "$estratto\n"; # ne stampa un terzo, diverso dai primi due.
-----------------------------------------------------------------
Come al solito, conoscendo CPAN ci si risparmia un sacco di lavoro.
Ciao,
Emanuele.
-- 
What does VISTA mean?
Viruses, Infections, Spyware, Trojan horses & Adware.

 Come al solito, conoscendo CPAN ci si risparmia un sacco di lavoro.
Come dico sempre CPAN è uno dei maggiori punti a favore del Perl su altri 
linguaggi.

vabbč tanto meglio !
mi studierņ anche questa opzione...
Cmq mi pareva davvero strano che non esistesse ...
Che diamine la mia esigenza l'avrą ben gią avuta qualcuno in passato no ?
Grazie ancora