Newsgroup: it.comp.lang.c++
Titolo: Problema di design...

Il mio problema è il seguente: ho bisogno di una classe (o più classi)
che realizzino un generatore di numeri casuali con distribuzione
qualunque. Adesso mi spiego:
una prima idea poteva essere questa:
class RandomVar {
public:
   double GetNum() { double r=rand()/(double)RAND_MAX; 
			return Distribution(r); }
private:
  virtual double Distribution(float x) { return x; }
};
da cui si potrebbe derivare ad esempio una classe che restituisce
valori uniformemente distribuiti in un intervallo più ampio, oppure
con distribuzioni esponenziali negative, gaussiane etc...
semplicemente ridefinendo nella classe derivata la funzione
Distribution. Es:
class UniformRV: public RandomVar {
private:
  virtual double Distribution(float x) { return (10*x); }  
};
Ci sono però alcuni problemi:
innanzitutto ho dichiarato virtuale una classe privata. E questo non è
bello (vedere "C++ manuale di stile" di C. Pescio).
Seconda cosa ho creato una classe contenente solo funzioni: niente di
male, però questo di solito è indice di cattivo design (sempre "C++
manuale di stile").
Poi in questo modo sono costretto ad istanziare una classe ogni volta
che mi serve un numero casuale (supponendo che mi possano servire in
più punti con diverso scope). Sarebbe più comodo dichiarare GetNum()
statica, però in questo modo anche Distribution(Double) dovrebbe
essere statica, e quindi non potrebbe essere virtuale. Non
funzionerebbe. Inoltre istanziando più classi ( o facendo più istanze
della stessa classe) non posso inizializzare il generatore di numeri
casuali del C++, perchè alla fine la chiamata a rand() agisce sullo
stesso seme per tutte le classi.
Qualcuno mi può aiutare?
GRAZIE
Daniele

.   The  pseudo-random |
 | algorithm is due to George Marsaglia, and produces a sequence of 24 |
 | bit numbers produced with a period of about 2**144, that has passed |
 | stringent statistical tests for randomness and independence.        |
 | Supplying two seeds to the sequence generator class constructor is  |
 | required before requesting any random numbers;  however two default |
 | values are provided for the default (no argument) constructor, that |
 | are completely 'magic' (any couple of integers,  odd or even, large |
 | or small,  is suitable).  The correspondence between pairs of seeds |
 | and generated sequences of pseudo-random numbers is many-to-one.    |
 | This package should compile  and run identically on any machine and |
 | with any compiler  which supports >= 16 bit  integer arithmetic and |
 | >=24 bit floating point arithmetic.                                 |
 | References:                                                         |
 |      M.G.Harmon, T.P.Baker - An Ada Implementation of Marsaglia's   |
 |      "Universal" Random Number Generator - Ada Letters, late 1987.  |
 |      G.Marsaglia - Toward a universal random number generator - To  |
 |      appear in the Journal of the American Statistical Association. |
 +---------------------------------------------------------------------+
 | Last modified: MLO 07 Oct 1993 21:01:00                             |
 *---------------------------------------------------------------------*/  
class RandSeq {
  public:
    static const int seeda_default;
    static const int seedb_default;
    RandSeq(int seeda = seeda_default, int seedb = seedb_default);
    ~RandSeq();
    double Rand(void);
  private:
    static const int P;
    static const int PM1;
    static const int Q;
    static const unsigned int STATE_SIZE;
    static const unsigned int MANTISSA_SIZE;
    static const double RANDOM_REALS;
    static const double INIT_C;
    static const double INIT_CD;
    static const double INIT_CM;
    unsigned int ni;
    unsigned int nj;
    double * u;
    double c, cd, cm;
    unsigned int Collapse(const int anyint, const unsigned int size) const;
    RandSeq(const RandSeq & source);
    RandSeq & operator = (const RandSeq & source);
};
#endif // RAND_H_
-----------------------End file: rand.h-----------------------
---------------------Begin file: rand.cxx---------------------
/*----------------------------------------------------------*
 | File: rand.cxx - Last modified: MLO 07 Oct 1993 21:02:14 |
 | C++ implementation of a pseudo-random number generator;  |
 | see the file "rand.h" for comments about the algorithm.  |
 *----------------------------------------------------------*/
extern "C" {
  #include <assert.h>
}
#include "rand.h"
/**
 | Static class constants
**/
const int             RandSeq::seeda_default  =      10579;
const int             RandSeq::seedb_default  =      24683;
const int             RandSeq::P              =        179;
const int             RandSeq::PM1            =     P -  1;
const int             RandSeq::Q              =     P - 10;
const unsigned int    RandSeq::STATE_SIZE     =         97;
const unsigned int    RandSeq::MANTISSA_SIZE  =         24;
const double          RandSeq::RANDOM_REALS   = 16777216.0;
const double          RandSeq::INIT_C         =   362436.0;
const double          RandSeq::INIT_CD        =  7654321.0;
const double          RandSeq::INIT_CM        = 16777213.0;
/**
 | Methods
**/
unsigned int RandSeq::Collapse (
  const          int anyint,
  const unsigned int size
) const {
/**
 | Return a value between 0 and size-1 inclusive.  This value will be
 | anyint itself if possible, otherwise another value in the required
 | interval.
**/
    unsigned int retval = (anyint < 0 ? -(anyint/2) : anyint);
    while (retval >= size) retval /= 2;
    return retval;
}
RandSeq::RandSeq (
  int seed_a,
  int seed_b
) {
/**
 | This procedure initialises the state table u for a lagged Fibonacci
 | sequence generator, filling it with random bits from a small
 | multiplicative congruential sequence.  The auxilliaries c, ni, and
 | nj are also initialized.  The seeds are transformed into an initial
 | state in such a way that identical results are guaranteed across a
 | wide variety of machines.
**/
    double       s, bit;
    unsigned int ii, jj, kk, mm;
    unsigned int ll;
    unsigned int sd;
    unsigned int elt, bit_number;
    u = new double[STATE_SIZE];
    assert(u != 0);
    sd = Collapse(seed_a, PM1 * PM1);
    ii = 1 + sd / PM1;
    jj = 1 + sd % PM1;
    sd = Collapse(seed_b, PM1 * Q);
    kk = 1 + sd / PM1;
    ll = sd % Q;
    if (ii == 1   &&   jj == 1   &&   kk == 1)  ii = 2;
    ni  = STATE_SIZE - 1;
    nj  = STATE_SIZE / 3;
    c   = INIT_C;
    c  /= RANDOM_REALS;		// Compiler might mung the division itself
    cd  = INIT_CD;
    cd /= RANDOM_REALS;
    cm  = INIT_CM;
    cm /= RANDOM_REALS;
    for (elt = 0;   elt < STATE_SIZE;   elt++) {
	s   = 0.0;
	bit = 1.0 / RANDOM_REALS;
	for (bit_number = 0;   bit_number < MANTISSA_SIZE;   bit_number++) {
	    mm = (((ii * jj) % P) * kk) % P;
	    ii = jj;
	    jj = kk;
	    kk = mm;
	    ll = (53 * ll + 1) % Q;
	    if (((ll * mm) % 64) >= 32) s += bit;
	    bit += bit;
	}
	u[elt] = s;
    }
}
RandSeq::~RandSeq(
) {
  delete [] u;
}
double RandSeq::Rand (
) {
/**
 | Return a uniformly distributed pseudo random number in the range
 | 0.0 .. 1.0-2**(-24) inclusive.  There are 2**24 possible return
 | values.  Side-effects the class variables: u, c, ni, nj.
**/
    double uni;
 
    if (u[ni] < u[nj]) uni = u[ni] + (1.0 - u[nj]);
        else           uni = u[ni] - u[nj];
    u[ni] = uni;
    if (ni > 0) ni -= 1;
        else    ni  = STATE_SIZE - 1;
    if (nj > 0) nj -= 1;
        else    nj  = STATE_SIZE - 1;
    if (c < cd) c = c + (cm - cd);
        else    c = c - cd;
    if (uni < c) return (uni + (1.0 - c));
        else     return (uni - c);
}
----------------------End file: rand.cxx----------------------

stesso seme per tutte le classi.
Questo specifico tuo "problema" non mi e` del tutto
chiaro.  Comunque, se alcune specifiche sequenze di
variate casuali nel tuo programma devono usare uno
"stato del generatore globale" diverso da altre, allora
quelle classi, e quelle sole, possono settare, e poi
resettare, detto "stato globale"; esse, dunque, a
differenza di quelle piu` semplici, _avranno_ un
proprio "stato" (di classe, o di istanza, a seconda di
cosa esattamente stiamo parlando).  E` questo uno
dei *vantaggi* della pattern "oggetto senza stato":
la classe-base, l'"oggetto astratto", non ha (almeno
non necessariamente) uno stato, ma una qualsiasi
data _concretizzazione_ (classe derivata) PUO`, se
serve, "aggiungere" uno stato, in modo normalmente
"trasparente" ai clienti della classe stessa.  Ritrovi
questo elemento negli "oggetti-funtori" della libreria
standard C++, ad esempio.
Alex

stesso seme per tutte le classi.
Questo perche` la tua classe delega il suo stato...
E` chiaro che a questo punto ogni oggetto della tua classe deve avere un suo
stato indipendente da quello di ogni altro oggetto.
Paolo

stesso seme per tutte le classi.
Qui dovremmo prima di tutto chiarire se questo e' un problema o meno.
In molti casi concreti lo e' eccome, perche' puo' ridurre la
possibilita' di ripetere una esatta simulazione, ad esempio quando
abbiamo thread diversi e scheduling non deterministico, o parti che
aspettano l'utente e parti che vanno per gli affari loro. Ma in questi
casi, non c'e' problema: basta spostare lo stato dal livello implicito
(libreria run-time) a quello esplicito (data member). Tieni il seme di
ogni singolo generatore nel generatore stesso. Oh, guarda, non va
nemmeno piu' contro la mia non-raccomandazione :-).
L'impostazione di cui sopra permette facilmente (spostando l'utility
function in un'altra classe) anche di condividere il generatore di
riferimento solo tra N oggetti a scelta. Spesso questa e' una
conseguenza diretta del passaggio da white box a black box, con la
possibilita' quindi di usare la composizione in alternativa
all'ereditarieta' [se vogliamo!] come meccanismo di riuso.
Infine, se vuoi esagerare puoi anche templatizzare quel double, ma qui
usciamo dal problema originale. 
Spero comunque che quanto sopra abbia contribuito a chiarire il mio
punto di vista (che resta ovviamente solo un punto di vista come un
altro) e a dissipare qualche dubbio circa le raccomandazioni in
questione. Che possono tranquillamente essere violate, l'importante
come sempre e' capire dove sta il guadagno maggiore.
Ciao,
Carlo