Edit : solution ajoutée.
Bonjour, j'ai actuellement un code qui fonctionne, bien que lent.
Il fusionne 2 CSV les fichiers ligne par ligne en utilisant une clé primaire. Par exemple, si le fichier 1 a la ligne :
"one,two,,four,42"
et le fichier 2 a cette ligne ;
"one,,three,,42"
où dans 0 indexé $position = 4 a la clé primaire = 42 ;
puis le sous : merge_file($file1,$file2,$outputfile,$position) ;
produira un fichier avec la ligne :
"one,two,three,four,42";
Chaque clé primaire est unique dans chaque fichier, et une clé peut exister dans un fichier mais pas dans l'autre (et vice versa).
Il y a environ 1 million de lignes dans chaque fichier.
En parcourant chaque ligne du premier fichier, j'utilise un hash pour stocker la clé primaire, et je stocke le numéro de ligne comme valeur. Le numéro de ligne correspond à un tableau [line num] qui stocke chaque ligne du premier fichier.
Ensuite, je passe en revue chaque ligne du deuxième fichier, et je vérifie si la clé primaire est dans le hachage, et si c'est le cas, je récupère la ligne du tableau file1 et j'ajoute les colonnes dont j'ai besoin du premier tableau au deuxième tableau, puis je les concatène à la fin. Ensuite, je supprime le hachage et, à la toute fin, j'envoie le tout au fichier. (J'utilise un SSD donc je veux minimiser les écritures dans le fichier).
La meilleure façon de l'expliquer est probablement d'utiliser un code :
sub merge_file2{
my ($file1,$file2,$out,$position) = ($_[0],$_[1],$_[2],$_[3]);
print "merging: \n$file1 and \n$file2, to: \n$out\n";
my $OUTSTRING = undef;
my %line_for;
my @file1array;
open FILE1, "<$file1";
print "$file1 opened\n";
while (<FILE1>){
chomp;
$line_for{read_csv_string($_,$position)}=$.; #reads csv line at current position (of key)
$file1array[$.] = $_; #store line in file1array.
}
close FILE1;
print "$file2 opened - merging..\n";
open FILE2, "<", $file2;
my @from1to2 = qw( 2 4 8 17 18 19); #which columns from file 1 to be added into cols. of file 2.
while (<FILE2>){
print "$.\n" if ($.%1000) == 0;
chomp;
my @array1 = ();
my @array2 = ();
my @array2 = split /,/, $_; #split 2nd csv line by commas
my @array1 = split /,/, $file1array[$line_for{$array2[$position]}];
# ^ ^ ^
# prev line lookup line in 1st file,lookup hash, pos of key
#my @output = &merge_string(\@array1,\@array2); #merge 2 csv strings (old fn.)
foreach(@from1to2){
$array2[$_] = $array1[$_];
}
my $outstring = join ",", @array2;
$OUTSTRING.=$outstring."\n";
delete $line_for{$array2[$position]};
}
close FILE2;
print "adding rest of lines\n";
foreach my $key (sort { $a <=> $b } keys %line_for){
$OUTSTRING.= $file1array[$line_for{$key}]."\n";
}
print "writing file $out\n\n\n";
write_line($out,$OUTSTRING);
}
La première boucle while fonctionne bien et prend moins d'une minute, mais la deuxième boucle while prend environ une heure à s'exécuter et je me demande si j'ai adopté la bonne approche. Je pense qu'il est possible de gagner beaucoup en vitesse :) Merci d'avance.
Solution :
sub merge_file3{
my ($file1,$file2,$out,$position,$hsize) = ($_[0],$_[1],$_[2],$_[3],$_[4]);
print "merging: \n$file1 and \n$file2, to: \n$out\n";
my $OUTSTRING = undef;
my $header;
my (@file1,@file2);
open FILE1, "<$file1" or die;
while (<FILE1>){
if ($.==1){
$header = $_;
next;
}
print "$.\n" if ($.%100000) == 0;
chomp;
push @file1, [split ',', $_];
}
close FILE1;
open FILE2, "<$file2" or die;
while (<FILE2>){
next if $.==1;
print "$.\n" if ($.%100000) == 0;
chomp;
push @file2, [split ',', $_];
}
close FILE2;
print "sorting files\n";
my @sortedf1 = sort {$a->[$position] <=> $b->[$position]} @file1;
my @sortedf2 = sort {$a->[$position] <=> $b->[$position]} @file2;
print "sorted\n";
@file1 = undef;
@file2 = undef;
#foreach my $line (@file1){print "\t [ @$line ],\n"; }
my ($i,$j) = (0,0);
while ($i < $#sortedf1 and $j < $#sortedf2){
my $key1 = $sortedf1[$i][$position];
my $key2 = $sortedf2[$j][$position];
if ($key1 eq $key2){
foreach(0..$hsize){ #header size.
$sortedf2[$j][$_] = $sortedf1[$i][$_] if $sortedf1[$i][$_] ne undef;
}
$i++;
$j++;
}
elsif ( $key1 < $key2){
push(@sortedf2,[@{$sortedf1[$i]}]);
$i++;
}
elsif ( $key1 > $key2){
$j++;
}
}
#foreach my $line (@sortedf2){print "\t [ @$line ],\n"; }
print "outputting to file\n";
open OUT, ">$out";
print OUT $header;
foreach(@sortedf2){
print OUT (join ",", @{$_})."\n";
}
close OUT;
}
Merci à tous, la solution est affichée ci-dessus. Il faut maintenant environ 1 minute pour fusionner le tout ! :)