mercoledì, dicembre 20, 2006

procedure in una classe

Ciao!
Stavo facendo qualche "vecchio" esercizio ed avevo una curiosità.
In particolare mi stavo occupando della classe istogramma (che non ho ancora fatto perchè mi ero occupato della classe complessi, ndt).
Dopo aver creato l'header file.h ho iniziato a scrivere il file .cc con l'implementazione della funzione riempi()
dichiarata nel seguente modo nel file .h:
void riempi(const double dato);
la funzione serve a inserire un dato nell'istogramma.
Mi è venuto in mente che servirebbe qualche controllo a proposito del dato inserito, per esempio:
"Cosa si fa se il dato inserito è minore del minimo valore rappresentato o maggiore del massimo?"
a questo punto l'idea è stata (prendiamo per esempio dato < min):
i) perdere il dato (scelta sconsigliata, credo)
ii) salvare il dato, ma non rappresentarlo (sono possibili diverse soluzioni)
iii) rappresentare il dato (modificando non poco l'istogramma), in particolare in questo caso potrei:
a] modificare il minimo in modo da rappresentare il nuovo dato e aggiungere tanti bin con lo stesso step di quelli dell'istogramma precedente (in pratica, se lavorassi con carta e matita allungherei il foglio e aggiungerei tante colonne (vuote) e poi una colonna in fondo a sx nella quale rappresento il nuovo dato)
b] modificare il minimo e calcolare un nuovo step (per esempio mantendo il numero di colonne uguale a quello precedente (di nuovo in pratica, cancello con una gomma le etichette degli intervalli delle colonne dell'istogramma e metto intervalli più larghi in modo da arrivare a contenere anche l'ultimo dato )
c] chiedo all'utente di scegliere di nuovo il numero di colonne che gli aggradano
Sia i punti i), ii), iii), come pure a], b], c] richiedono scelte da parte dell'utente che sarebbe bello poter fare run-time (leggi: l'utente inserisce il dato dall'esterno il programma vede che non va bene e allora si mette a chidere all'utente come si deve comportare).
La domanda che volevo fare è la seguente:
si può inserire scrivere tutta questa procedura di controllo come una funzione all'interno di una classe, senza che sia un metodo della classe. (come se fosse una funzione a parte, insomma, perchè è una specie di sottometodo)?
Da notare che per tutto il lavoro che ci sarebbe da fare sul vettore dei bins dell'istogramma sarebbe utile avere a disposizione un bell'array dinamico riallocabile (quindi prima dovrei mettermi a fare l'esercizio sulla classe dei vettori, oppure usare gli std::vector).
Dite che è troppo complicata tutta questa procedura?
Allora... se sì abbiamo tre possibilità I), II), III)... se no abbiamo... vabbhé lasciate perdere... sto delirando.
Fatemi sapere.
Ciao ciao
Cristian

9 commenti:

hronir ha detto...

Bene, finalmente si torna al blog!
Per quanto riguarda le tue domande, non credo di aver capito bene cos'hai in mente, perchè non mi è chiaro cosa possa essere una funzione che "appartiene" ad una classe ma non ne costituisce un metodo (e ancor di più come questo le attribuisca carattere di "sottometodo"...). Se vuoi che tale funzione possa modificare degli attributi della classe (il numero di bin, la larghezza dell'intervallo, etc etc) è naturale scriverla come metodo della classe. Se il termine "sottometodo" ti è suggerito dal fatto che tale metodo venga di fatto chiamato solo all'interno di un altro metodo della classe, tieni presente che questa è una situazione nientaffatto rara (potresti, tanto per dire, dichiarare il metodo nell'ambito private della classe...!).
Infine due parole sul problema dei dati fuori scala:
i) direi anch'io scelta sconsigliata (si perde la normalizzazione dell'istogramma, ma ci sono molte altre controindicazioni e inconsistenze in un approccio simile...)
ii) quest'opzione rappresenta una scelta percorribile se si hanno forti constraint sull'estensione dell'intervallo (un compromesso sulla rappresentazione del dato potrebbe essere quella di avere due bin "speciali" corrispondenti ai dati rispettivamente superiori e inferiori all'estensione dell'istogramma...)
iii) certamente la scelta più "corretta", anche se in generale più articolata da realizzare. In particolare le opzioni b) e c) sono molto difficili se non impossibili da realizzare, a meno di non decidere di mantenere una copia di tutti i valori rappresentati dall'istogramma (mentre si è sempre preferito usare un'approccio che prende il dato, lo "classifica" nell'istogramma e quindi lo "butta via"...). Senza avere tutti i dati già "passati", non si può riconfigurare la larghezza dei bin perchè non si saprebbe come riversare l'informazione sui conteggi che è diventata irrimediabilmente granulare... L'unica scelta praticabile sarebbe quella del punto a), perchè preserva la suddivisione precedentemente usata e non richiede la ridistribuzione dei dati in bin diversi...

Unknown ha detto...
Questo commento è stato eliminato dall'autore.
Unknown ha detto...

ciao,

provo anche io a rispondere.
Un metodo e' una qualunque funzione definita all'interno di una classe. Rispetto ad una funzione definita all'esterno di una classe, ha la proprieta' di poter accedere a tutti i membri e a tutti i metodi della classe. Che un metodo sia privato significa che puo' essere richiamato solamente da altri metodi della classe.
Esiste anche un modo di costruire funzioni esterne ad una classe, le quali tuttavia abbiano accesso ai membri (e metodi) privati di una classe, che viene utile quando queste funzioni debbano avere a che fare con piu' oggetti (ad esempio, l'operator<< ha due argomenti: lo stream (cout) e l'oggetto da mandare in stram. Ne parleremo :) ).

Per quanto riguarda l'istogramma.
Probabilmente la cosa piu' comoda e' pensarlo non come uno strumento per visualizzare singoli dati, ma distribuzioni di misure: quindi, non e' importante salvare i singoli dati.
Allora, per un uso "di tutti i giorni" dell'istogramma l'importante e' quella di salvare due bin agggiuntivi, che siano gli eventi prima del minimo ed oltre il massimo rispettivamente.
Questo perche' di solito si "centra" l'istogramma nella zona di interesse e poi si lavora li' intorno.
Per la soluzione difficile, invece, opterei come dice Enzo per la (a), perche' scegliere un binnaggio dell'istogramma significa decidere una risoluzione e lasciare invariato il numero di bin vuol dire cambiare la risoluzione, se si allarga l'intervallo. Inoltre, interpellare l'utente non e' mai cosa da fare alla leggera, perche' diventa difficle utilizzare l'istogramma in maniera versatile (ad esempio, in un programma eseguito su un cluster chi risponde alla domanda?).
Un'ultima cosa: modificare il range di un istogramma ed il suo binning dopo che e' stato creato ne fa una classe molto comoda, tuttavia fa si' che la sua dimensione possa cambiare. Anche se per chi lo utilizza questo non deve comportare conseguenze nelle funzionalita', non e' scontato che un container di istogrammi non risenta di queste variazioni in termini di prestazioni (velocita', memoria occupata).
La questione della memoria mostra che non esiste una soluzione univocamente migliore di altre, piuttosto soluzioni piu' o meno adatte ai singoli problemi.

Aspettiamo l'istogramma finale!

p

Unknown ha detto...

Ciao,
alla fine ho optato per la possibilità ii): ho aggiunto due bin per contenere i dati minori del minimo e maggiori del massimo.
Ho quasi finito... ho solo un'ultima domanda.
Esiste la possibilità di passare una funzione a più argomenti come argomento di una funzione fissando alcuni parametri della prima?
Un esempio per chiarire:

funzione(int a, int b) {
...
}

fun(int X, int Y, f(int)) {
...
}

posso fare

fun(2,3, funzione(4, ))


Così non si può fare, infatti il compilatore protesta sonoramente, quello che mi chiedevo è se esiste un modo per fare una cosa del genere o sono costretto a fare l'overload della funzione fun e mettere una funzione che accetta due valori interi? e in questo caso... come faccio a fare in modo che uno dei due valori sia fissato "dall'esterno" (ossia non venga scelto all'interno di fun ma dal programma principale)?

Per capire perchè vi faccio queste domande dovrete aspettare la pubblicazione del mio programma... (e intanto così vi viene un po' di curiosità... =) )

Unknown ha detto...

Ciao Cristian,

forse non ho capito il problema.

Scrivere quanto segue e' quello che vorresti?

int f1 (int,int) ;

int f2 (int uno, int due, int funz (int,int))
{
int numero = uno * due ;
return funz (4, numero) ;
}

Oppure vorresti fissare i parametri di "funz" ogni volta che chiami f2?
In quest'ultimo caso, mi sa che ti conviene passare un altro parametro ad f2 da passare poi a funz:

int f2 (int uno, int due, int par, int funz (int, int))
{
int numero = uno * due ;
return funz (par, numero) ;
}

e poi chiamare, ad esempio:

f2 (1,2,4,f1) ;

Oppure qualcosa d'altro?

p

Unknown ha detto...

Ciao Pietro,
scuse se rispondo solo adesso.
In pratica ho creato due file pdf.h e pdf.cc in cui ho definito delle distibuzioni (per esempio cos(x)+1, sin(x)+1, x^2, exp(x)), insomma delle funzioni che potrebbero essere rappresentate nell'istogramma.
Perchè ho fatto questa cosa?
Nel mio primo istogramma (quello senza le classi) aveve dato all'utente la possibilità di scegliere la pdf che volevano rappresentare con un output del tipo:
[cristian@localhost Random]$ ./random4.exe
Quanti numeri casuali vuoi generare? 9000
------------------------------------
Inserisci gli estremi dell'intervallo nel quale vuoi che siano generati i numeri casuali (in ordine): -3 3
------------------------------------
In quante porzioni vuoi dividere l'asse x? 30
------------------------------------
Lo step è: 0.2
Scegli la PDF!!
0 = parabola
1 = coseno
2 = seno
3 = esponenziale
Scelta = 0
La scelta è stata: 0
------------------------------------
860
28
plot dell'istogramma:
| ###########################
| #########################
| ####################
| ##################
| ###############
| ############
| ##########
| ########
| ######
| #####
| ###
| #
| #
| #
|
+---------------------->
|
|
| #
| ##
| ###
| ####
| #####
| ########
| #########
| ###########
| ###############
| ##################
| ####################
| #######################
| ##############################
|
\/

media = -0.0122249
sigma = 2.29998

Insomma l'utente poteva scegliere da un "menu" di PDF quella che più gli era congeniale.
In pratica volevo ricreare la stessa cosa nella versione dell'istogramma con le classi.
Il mio problema era trovare il modo per passare alla funzione RandPDF (che genera le coppie (x,y) e le valuta con il metodo di bisezione), la PDF scelta dall'utente all'inizio del programma. (i dettagli dovrebbero essere più chiari guardando il file che ho caricato sul gruppo).
Carico nel gruppo il file random4.cpp del quale ho postato l'output (così si può vedere come ho "gestito" la scelta della PDF).
La scelta della PDF ha comunque un limite che consiste nel fatto che bisogna stabilire nella compilazione quali sono il massimo e il minimo valore assunto dalle y. (problema che potrebbe essere facilmente aggirato chiedento all'utente di inserire questi valori... ma non avevo voglia di mettere un'altra richiesta all'utente...scusate)
Spero di aver dato un'idea di quello che avevo in mente di fare(ho paura di essere stato un po' incomprensibile...)

Unknown ha detto...

Ciao Cristian,

sto leggendo il tuo codice: approfitto di questo commento per alcune note di carattere generale.

E' utile avere un'idea chiara delle funzioni del codice. La prima richiesta che va soddisfatta e' ovviamente che il programma funzioni, quindi serve che si scriva una struttura di test relativa. Di solito si cerca di scrivere tanti piccoli programmi di test che verifichino le singole funzionalita' delle funzioni e delle classi in modo indipendente, per rendere piu' semplice il debugging (cioe' trovare i problemi del codice) ed il profiling (cioe' capire dove si spende piu' tempo o memoria).
Le richieste dell'utente finale (che nella maggior parte dei casi siamo noi :) ) sono diverse: non ci aspettiamo, ad esempio, che per un istogramma sia l'utente a scegliere di volta in volta la PDF da utilizzare, piuttosto che un istogramma sia usato per visualizzare grandezze fisiche. Non ci aspettiamo che sia un problema _dell'istogramma_ il limite dovuto al range in asse y della generazione di numeri casuali.

Insomma, quando si scrive C++ si preparano librerie, ciascuna delle quali ha una funzione specifica, il piu' possibile disgiunta dalle altre librerie (ad es. generazione di numeri casuali e gestione di istogrammi). Una volta scritte, la prima cosa da fare e' il test delle procedure, tuttavia la progettazione della libreria si fa tenendo conto dell'uso finale alla quale e' destinata, non del codice di test.

Ecco invece una lista di commenti specifici.

* non mi piace che maxColumns sia definito con un define, perche' si tratta di una variabile del programma

* prova a fare un po' di "refactoring", cioe' portare la variabili dove vengono utilizzate: definire una variabile lontano da dove viene usata non porta a vantaggi e rende piu' difficile la lettura del codice ed il suo riciclaggio nel caso di copia-incolla. In particolare, la variabili dei loop si possono definire direttamente all'interno dei cicli for.

* immagino che il prossimo passo sia scrivere la classe istogramma con un std::vector, vero? ;)

Infine, un approfondimento: come altri operatori, anche l'operator() puo' essere implementato, per una classe. Questo significa che, dato un oggetto, come abbiamo implementato il operator[] per la classe dei vettori cosi' possiamo scrivere una funzione che venga chiamata con le parentesi tonde:

tipoClasse oggetto ;
oggetto () ;

che di fatto e' come si comporta una funzione. Questo tipo di oggetti ha un nome, che e' quello di "funtore" (Function object o functor). Nel caso di questo esempio potrebbe sostituire le varie funzioni parabola, coseno, in modo che poi si possa costruire una std::map di funzioni associate a numeri (o nomi). Il vantaggio di uno schema del genere e' che non e' mai necessario sapere a priori quali o quante siano le funzioni esistenti, basta cercarle nella mappa di volta in volta, oppure anche aggiungerle a posteriori senza dover modificare tutto il codice. Ad esempio nella wikipedia si trovano esempi di funtori.

p

Unknown ha detto...

Ciao,
grazie per i commenti.
In effetti la classe istogramma è indipendente da tutto il discorso sulla scelta della PDF. Ma nel programma che stavo scrivendo per provarla volevo riottenere lo stesso output del primo esercizio random4.cpp. Così ho sviluppato anche l'idea della lista di mettere le PDF in un file separato ed ho incontrato i problemi per cui ho chiesto aiuto. Per questo motivo tutti i miei problemi ruotano attorno al codice di test (e non ho ancora testato l'effettivo funzionamento della classe istogramma!!) Mi appello alla clemenza della corte...
Per quanto riguarda i problemi specifici:
1. Ho definito MaxColumns con #define perchè non mi sembrava una variabile del programma ma piuttosto un paramentro legato alla macchina su cui viene eseguito il programma stesso (maxColumns è il massimo numero di colonne che posso visualizzare sullo schermo del PC che sto usando, no?)
2. il "refactoring" è una "tecnica" abbastanza nuova (imparata da poco), in quel primo programma non ne ero a conoscenza.
3. Prima di passare agli std::vector devo testare il funzionamento della classe istogramma (sarebbe anche l'ora), comunque lo utilizzerò (l'idea è di arrivare a fare una classe istogramma templata che utilizzi gli std::vector)

Per quanto riguarda i funtori, mi piacerebbe vederne uno in un codice semplice. Per farmi un idea di come funziona (anzi di come funtora.. ah ah)
In particolare nel nostro caso avremmo una cosa del tipo?
class PDF {
}
int main() {
PDF parabola;
...
parabola();

sarebbe molto bello!!!
Ma come si fa in dettaglio?
Grazie dell'aiuto.
Ciao
Cristian

ps. consiglio a tutti di leggere la voce funtore su wikipedia...

Unknown ha detto...

ma quale corte... ;) qui si approfitta dei pochi che scrivono per fare commenti, il codice va bene!
Per quanto riguarda MaxColumns, ho capito che cosa intendevi fare: utilizzare i comandi del parser per "adattare" il codice a proprieta' della macchina. Tuttavia, in questo caso il numero di colonne dipende dalla finestra della shell e, probabilmente, dal suo stato (ad esempio se e' stata allargata con il mouse). Immagino (ma non saprei dire come, ora) che esistano librerie in grado di ritornare al codice il numero di colonne disponibili.

Il funtore funziona come dici, ho messo un esempio banale nei quaderni.

ciao,

p