Newsgroup: it.comp.lang.perl
Titolo: merge di linee...

salve!
Ho un urgente necessità di risolvere un "piccolo" problema...
Praticamente succede che lavorando sull'estrazione di record da un DB
utilizzando Perl e alcune API proprietarie dell'applicativo, ottengo un
output che si presenta più o meno nel modo seguente:
codice     data-in     data-out   ora-in    ora-out  esito
111111     21-05-03               10.30
111111                 22-05-03             11.30
111111                                               chiuso
222222     17-05-03               11.15   
222222                 20-05-03             15.20    aperto
il risultato che invece vorrei ottenere è:
codice     data-in     data-out   ora-in    ora-out  esito
111111     21-05-03    22-05-03   10.30     11.30    chiuso
222222     17-05-03    20-05-03   11.15     15.20    aperto
praticamente un merge di più linee che hanno il valore del codice uguale
(nell'esempio le linee che hanno codice 111111 sono 3 e quelle con codice
222222 sono due, ma alla fine tutto finisce su due linee distinte che
hanno la medesima composizione...)
avete qualche dritta/suggerimento/soluzione da darmi?
vi ringrazio per l'attenzione
M4RIO
---
-- 
questo articolo e` stato inviato via web dal servizio gratuito 
http://www.newsland.it/news
 segnala gli abusi ad 
ab...@newsland.it

 222222     17-05-03    20-05-03   11.15     15.20    aperto
carichi i dati in 5 hash :
%data-in     %data-out   %ora-in    %ora-out  %esito
le cui chiavi sono "111111", "222222", ...
ok ?
 Raffaello

 ok ?
più o meno...
Se non chiedo troppo... 
puoi darmi qualche "traccia" in più? ;)
ti ringrazio per la disponibilità
mario.


if ($data_in_column_item)  { $data_in{$codice}=$data_in_column_item; }
if ($data_out_column_item) { $data_out{$codice}=$data_out_column_item; }
if ($ora_in_column_item)   { $ora_in{$codice}=$ora_in_column_item; }
if ($ora_out_column_item)  { $ora_out{$codice}=$ora_out_column_item; }
if ($esito_column_item)    { $esito{$codice}=$esito_column_item; }
foreach $row ('111111','222222','333333') {
	print STDOUT
		$row."\t".$data_in{$row}.
		"\t".$data_out{$row}.
		"\t".$ora_in{$row}.
		"\t".$ora_out{$row}.
		"\t".$esito{$row}."\n";
	}
ma puoi usare una struttura dati piu' complessa (hash di array o hash di
hash) e ottenere un codice piu' "elegante".
 Raffaello

le cui chiavi sono "111111", "222222", ...
O in uno:   $dati{"111111"}{'data-in'},  $dati{"111111"}{'data-out'}...
$dati{"111111"}{'esito'}.
Il problema può essere quando emettere il record. Se i dati non sono molti,
puoi tenere tutto in memoria e stampare il risultato alla fine. Altrimenti,
se i dati sono ordinati per chiave, puoi emettere un record completo ogni
volta che cambia chiave (e, naturalmente, alla fine della lettura di tutti
i dati).
Può darsi però che lo stesso risultato si possa ottenere direttamente dal
database, per esempio con autojoin. Dipende dalla struttura del database,
da che sistema si sta usando, che dialetto SQL accetta, ecc.
Ciao.

 da che sistema si sta usando, che dialetto SQL accetta, ecc.
La cosa è fattibilissima in SQL... ho già provato a farlo e sembra
funzionare abbastanza bene...
Ma mi hanno chiesto di non farlo ricorrendo ad istruzioni SQL-embedded...
:-((
Quello su cui mi sto stupidamente "incriccando" è la realizzazione di uno
pseudo DISTINCT in Perl... ;(((


pseudo DISTINCT in Perl... ;(((
Supponendo che i dati in entrata siano ordinati sul campo codice, e i campi
siano separati da tabulazione (e, probabilmente, supponendo qualche altra
altra supponenza che al momento non mi viene in mente di aver supposto),
potresti fare qualcosa del genere:
    my @nomi_campi = qw(codice data-in data-out ora-in ora-out  esito);
    my @campi;
    my $chiave;
    my %record;
    sub scrivi_record;
    open DATI, "< quel_che_è.qualcosa";
    print $nomi_campi[0];
    for (my $ind = 1; $ind <= $#nomi_campi; ++$ind) {
        print "\t", $campo;
    }
    print "\n";
    while (<DATI>) {
       chomp();
       @campi = split(/\t/);
       if (!defined($chiave)) {
           $chiave = $campi[0];
       } elsif ($chiave ne $campi[0]) {
           scrivi_record;
           undef %record;
           $chiave = $campi[0];
       }
       for (my $ind_campo = 1;
        $ind_campo <= $#campi;
        ++$ind_campo) {
           if (defined($campi[$ind]) and
               $campi[$ind] ne '')  {
            $record{$nomi_campi[$ind_campi]} =
                $campi[$ind];   
           }
       }
    }
    close DATI;
    scrivi_record;
 
    sub scrivi_record {
        print $record{$nomi_campi[0]};
        for (my $ind = 1; $ind <= $#nomi_campi; ++$ind) {
            print "\t", $record{$nomi_campi[$ind]};
        }
        print "\n";
    }
Nota che ho scritto di getto, senza verificare, sicché non rispondo dei
risultati. Dovrebbe comunque esserci abbastanza informazione perché tu
possa ricavare qualcosa che funzioni.
Ciao.

                $campi[$ind];   
                        ^^^^
Dev'essere, naturalmente:
            $record{$nomi_campi[$ind_campi]} =
                $campi[$ind_campi]; 
Non escludo, comunque, la presenza di altri errori.
Ciao.

....
Vabbe', insomma, se mi decidessi una volta per tutte come si chiama questa
variabile di ciclo magari sarebbe meglio:
       for (my $ind_campo = 1;
        $ind_campo <= $#campi;
        ++$ind_campo) {
           if (defined($campi[$ind_campo]) and
               $campi[$ind_campo] ne '')  {
            $record{$nomi_campi[$ind_campo]} =
                $campi[$ind_campo];
        ....
... e speriamo che basti.
Ciao.

 Non escludo, comunque, la presenza di altri errori.
qualche errorino c'era... ma niente di particolarmente critico da
sistemare...
Dopo una sistematina qua e la il tutto funziona senza pecche... anzi!
Per complicare ancora un pochino le cose, ho fatto in modo che effettuasse
due "merge annidati".
Spiego meglio...
Supponendo di avere in input un file del tipo:
col1    col2    col3    col4    col5    col6
1111	AN	aaaa	bbbb
1111	AN			cccc	dddd
1111	BT	aaaa	bbbb
1111	BT			cccc	dddd
2222	AN	eeee	ffff
2222	AN			gggg	hhhh
2222	BT	eeee	ffff
2222	BT			gggg	hhhh
3333	AN	iiii	jjjj
3333	AN			kkkk
3333	AN				llll
3333	BT	iiii	jjjj
3333	BT			kkkk	llll
e di volere in output qualcosa del tipo:
col1    col2    col3    col4    col5    col6
1111	AN	aaaa	bbbb	cccc	dddd
1111	BT	aaaa	bbbb	cccc	dddd
2222	AN	eeee	ffff	gggg	hhhh
2222	BT	eeee	ffff	gggg	hhhh
3333	AN	iiii	jjjj	kkkk	llll
3333	BT	iiii	jjjj	kkkk	llll
in questo caso si effettua il raggruppamento sia sui valori della prima
colonna sia sul contenuto della seconda...
Il codice Perl per ottenere il tutto (grazie a G.Zezza per l'aiuto) č:
# =========================================
my @nomi_campi = qw(col1 col2 col3 col4 col5 col6 col7 col8 col9 col10
col11 col12 col13);
my @cont_campi;
my $chiave;
my %record;
sub scrivi_record;
$FileName = $ARGV[0];
open(FILE,"< $FileName");
print $nomi_campi[0];
for (my $ind = 1; $ind <= $#nomi_campi; ++$ind) {
	print "\t", $nomi_campi[$ind];
}
print "\n";
while (<FILE>) {
	chomp();
	@cont_campi = split(/\t/);
	if (!defined($chiave)) {
		$chiave = $cont_campi[1];
	} 
	elsif ($chiave ne $cont_campi[1]) {
		scrivi_record;
		undef %record;
		$chiave = $cont_campi[1];
	}
	for (my $ind_campo = 0;$ind_campo <= $#cont_campi;++$ind_campo) {
		if (defined($cont_campi[$ind_campo]) and $cont_campi[$ind_campo] ne '') {
			$record{$nomi_campi[$ind_campo]} = $cont_campi[$ind_campo]; 
		}
	}
}
# scrive l'ultima riga
scrivi_record;
close FILE;
sub scrivi_record {
print $record{$nomi_campi[0]};
for (my $ind = 1; $ind <= $#nomi_campi; ++$ind) {
	print "\t", $record{$nomi_campi[$ind]};
}
print "\n";
}
#================================================================
Sperando che possa servire a risolvere problemi simili anche ad altri...
grazie per la collaborazione!
m.


		$chiave = $cont_campi[1];
Così però potresti avere dei problemi se la seconda colonna non varia, e
varia invece la prima (posto che sia possibile sull'universo considerato). 
A fronte cioè di questo input:
1111	BT	aaaa	bbbb
1111	BT			cccc	dddd
2222	BT	eeee	ffff
tireresti diritto, invece di emettere il record costruito per "1111 BT". Se
vuoi che la chiave sia su due colonne, non c'è verso, devi mettere entrambe
le colonne nella chiave. Per esempio, con qualcosa del genere:
        ...
	if (!defined($chiave)) {
		$chiave = $cont_campi[0] . "\t" . $cont_campi[1];
	} 
	elsif ($chiave ne $cont_campi[1]) {
		scrivi_record;
		undef %record;
		$chiave = $cont_campi[0] . "\t" . $cont_campi[1];
        ...
Naturalmente, se sei CERTO che, nell'insieme di dati considerato non possa
MAI accadere l'evenienza considerata, che cambi la prima colonna e non la
seconda, va bene anche considerare solo la seconda. CERTO e MAI, però, sono
di solito parole pesanti come pietre.
Ciao.

         ...
[cut...]
dopo che mi hai messo la pulce nell'orecchio ho fatto una prova e devo
dire che con la versione "precedente" del programma tutto sembra
comportarsi apparentemente in modo corretto... 
Se faccio la modifica da te suggerita, ottengo in output il file d'input
"paro-paro" così come glielo passo... ovvero senza alcuna compattazione
delle righe.
Probabilmente qualcosa è sfuggito...
Il file di input con cui ho fatto il test è del tipo:
1111	BT	aaaa	bbbb
1111	BT			cccc	dddd
1111	BT	aaaa	bbbb
1111	BT			cccc	dddd
2222	AN	eeee	ffff
2222	AN			gggg	hhhh
2222	BT	eeee	ffff
2222	BT			gggg	hhhh
3333	AN	iiii	jjjj
3333	AN			kkkk
3333	AN				llll
3333	BT	iiii	jjjj
3333	BT			kkkk	llll
???


Probabilmente qualcosa è sfuggito...
Sì, quello che mi è sfuggito è che dovrei riprendere la mia cara vecchia
abitudine di pubblicare solo codice di cui ho verificato il corretto
funzionamento.
E' chiaro che, se cambia la regola di costruzione della chiave di gruppo,
anche la verifica di uscita dal gruppo deve essere fatta secondo la nuova
regola:
    ...
    if (!defined($chiave)) {
        $chiave = $cont_campi[0] . "\t" . $cont_campi[1];
    } elsif ($chiave ne $cont_campi[0] . "\t" . $cont_campi[1]) {
        scrivi_record;
        undef %record;
        $chiave = $cont_campi[0] . "\t" . $cont_campi[1];
    }
    ...
Ciao.

     ...
In questo modo è tutto OK. Il controllo è efficace sia sulla prima che
sulla seconda colonna.
Well done!


>         $chiave = $cont_campi[0] . "t" . $cont_campi[1];
Giuro che io ho scritto:
         $chiave = $cont_campi[0] . "\t" . $cont_campi[1];
                                     ^^
         ...
         } elsif ($chiave ne $cont_campi[0] . "\t" . $cont_campi[1]) {
                                               ^^
         ...
         $chiave = $cont_campi[0] . "\t" . $cont_campi[1];
                                     ^^
Non che la sostanza della cosa cambi, ma il carattere tabulazione è, a
priori, l'unico carattere che siamo certi non possa occorrere in una
colonna, avendo ottenuto i valori di colonna appunto con uno split su
questo carattere.
Ciao.

 questo carattere.
Effettivamente avevi scritto "\t" e non "t"... non avevo notato che nel
fare "cut&paste" era stato eliminato il backslash... probabilmente lo
script di gestione dei post di NewsLand tira via il backslash...
Però è strano... prima non l'aveva fatto.
Boh! mistero...
