Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/date/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Perl使用主键逐行合并2个csv文件_Perl - Fatal编程技术网

Perl使用主键逐行合并2个csv文件

Perl使用主键逐行合并2个csv文件,perl,Perl,编辑:添加了解决方案 您好,我目前有一些工作,虽然缓慢的代码 它使用主键逐行合并2个CSV文件。 例如,如果文件1有以下行: "one,two,,four,42" "one,two,three,four,42"; 文件2有这一行 "one,,three,,42" 其中,在0中,索引$position=4的主键为42 然后是子文件:merge_file($file1,$file2,$outputfile,$position) 将输出带有以下行的文件: "one,two,,four,42"

编辑:添加了解决方案

您好,我目前有一些工作,虽然缓慢的代码

它使用主键逐行合并2个CSV文件。 例如,如果文件1有以下行:

"one,two,,four,42"
"one,two,three,four,42";
文件2有这一行

"one,,three,,42"
其中,在0中,索引$position=4的主键为42

然后是子文件:merge_file($file1,$file2,$outputfile,$position)

将输出带有以下行的文件:

"one,two,,four,42"
"one,two,three,four,42";
每个主键在每个文件中都是唯一的,一个键可能存在于一个文件中,但不存在于另一个文件中(反之亦然)

每个文件中大约有一百万行

遍历第一个文件中的每一行,我使用散列存储主键,并将行号存储为值。行号对应于一个数组[line num],该数组存储第一个文件中的每一行

然后我检查第二个文件中的每一行,检查主键是否在散列中,如果在散列中,则从file1array中获取该行,然后将我需要的列从第一个数组添加到第二个数组,然后concat。到最后。然后删除散列,最后将整个内容转储到文件中。(我使用的是SSD,因此我希望尽量减少文件写入。)

最好用一个代码来解释:

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);
}
子合并_文件2{
我的($file1,$file2,$out,$position)=($\[0]、$\[1]、$\[2]、$\[3]);
打印“合并:\n$file1和\n$file2到:\n$out\n”;
我的$OUTSTRING=未定义;
我的%line_用于;
我的@File1阵列;
打开文件1,“$out”;
打印$header;
foreach(@sortedf2){
打印输出(连接“,”,@{$})。“\n”;
}
收尾;
}

谢谢大家,解决方案张贴在上面。现在合并整个过程大约需要1分钟的时间!:)

假设每个文件大约有20个字节行,总计约20 MB,这并不太大。 因为您正在使用哈希,所以您的时间复杂性似乎不是问题

在第二个循环中,您正在为每行打印到控制台,这一点很慢。试着去掉它会有很大帮助。 您还可以避免在第二个循环中删除


同时阅读多行文字也会有所帮助。但我不认为太多,在幕后总是会有预读。

想到两种技巧

  • 将CSV文件中的数据读入DBMS中的两个表(SQLite可以正常工作),然后使用DB进行连接并将数据写回CSV。数据库将使用索引来优化连接

  • 首先,按主键对每个文件进行排序(使用perl或unix
    sort
    ),然后并行地对每个文件进行线性扫描(从每个文件中读取一条记录;如果键相等,则输出一个连接行并使两个文件前进;如果键不相等,则使用较小的键使文件前进,然后重试)。这一步是O(n+m)时间,而不是O(n*m)和O(1)内存


  • 影响性能的是这段代码,它被连接了数百万次

    $OUTSTRING.=$outstring."\n";
    
    ....
    
    foreach my $key (sort { $a <=> $b } keys %line_for){
        $OUTSTRING.= $file1array[$line_for{$key}]."\n";
    }
    

    我看不出有什么明显的慢,但我会做出以下改变:

    • 首先,我要消除
      @file1array
      变量。你不需要它;只需将行本身存储在哈希中:

      while (<FILE1>){
           chomp;
           $line_for{read_csv_string($_,$position)}=$_;
      }
      
      while(){
      咀嚼;
      $line_表示{read_csv_string($\u,$position)}=$\u;
      }
      
    • 第二,虽然这对perl没有多大影响,但我不会一直在
      $OUTSTRING
      中添加。相反,保留一个输出行数组,并每次将
      推到该数组上。如果出于某种原因,您仍然需要使用大量字符串调用
      write\u line
      ,您可以始终在末尾使用
      join(“”,@OUTLINES)

    • 如果
      write\u-line
      不使用
      syswrite
      或类似的低级调用,而是使用
      print
      或其他基于stdio的调用,那么通过在内存中建立输出文件,就不会保存任何磁盘写入。因此,您最好根本不在内存中构建输出,而是在创建时将其写出。当然,如果您使用的是
      syswrite
      ,请忘记这一点

    • 因为没有明显的慢,试着抛出你的代码。我发现这是最好的perl分析器,可以产生那些“哦!这是慢行!”的见解


    我会将每条记录存储在一个散列中,该散列的键是主键。给定主键的值是对CSV值数组的引用,其中
    unde
    表示未知值

    use 5.10.0;  # for // ("defined-or")
    use Carp;
    use Text::CSV;
    
    sub merge_csv {
      my($path,$record) = @_;
    
      open my $fh, "<", $path or croak "$0: open $path: $!";
    
      my $csv = Text::CSV->new;
      local $_;
      while (<$fh>) {
        if ($csv->parse($_)) {
          my @f = map length($_) ? $_ : undef, $csv->fields;
          next unless @f >= 1;
    
          my $primary = pop @f;
          if ($record->{$primary}) {
            $record->{$primary}[$_] //= $f[$_]
              for 0 .. $#{ $record->{$primary} };
          }
          else {
            $record->{$primary} = \@f;
          }
        }
        else {
          warn "$0: $path:$.: parse failed; skipping...\n";
          next;
        }
      }
    }
    
    Data::Dumper
    模块显示,给定问题的简单输入,得到的哈希值是

    $VAR1 = { '42' => [ 'one', 'two', 'three', 'four' ] }; $VAR1={ '42' => [ "一",, “两个”, "三",, “四” ]
    }; 如果你想合并,你应该真的合并。首先,您必须按键对数据进行排序,然后再合并!您将在性能上击败MySQL。我在这方面有很多经验

    你可以这样写:

    #!/usr/bin/env perl
    use strict;
    use warnings;
    
    use Text::CSV_XS;
    use autodie;
    
    use constant KEYPOS => 4;
    
    die "Insufficient number of parameters" if @ARGV < 2;
    my $csv = Text::CSV_XS->new( { eol => $/ } );
    my $sortpos = KEYPOS + 1;
    open my $file1, "sort -n -k$sortpos -t, $ARGV[0] |";
    open my $file2, "sort -n -k$sortpos -t, $ARGV[1] |";
    my $row1 = $csv->getline($file1);
    my $row2 = $csv->getline($file2);
    while ( $row1 and $row2 ) {
        my $row;
        if ( $row1->[KEYPOS] == $row2->[KEYPOS] ) {    # merge rows
            $row  = [ map { $row1->[$_] || $row2->[$_] } 0 .. $#$row1 ];
            $row1 = $csv->getline($file1);
            $row2 = $csv->getline($file2);
        }
        elsif ( $row1->[KEYPOS] < $row2->[KEYPOS] ) {
            $row  = $row1;
            $row1 = $csv->getline($file1);
        }
        else {
            $row  = $row2;
            $row2 = $csv->getline($file2);
        }
        $csv->print( *STDOUT, $row );
    }
    
    # flush possible tail
    while ( $row1 ) {
        $csv->print( *STDOUT, $row1 );
        $row1 = $csv->getline($file1);
    }
    while ( $row2 ) {
        $csv->print( *STDOUT, $row2 );
        $row2 = $csv->getline($file1);
    }
    close $file1;
    close $file2;
    

    嗯,他每1000行只在控制台上打印一次,“delete”对于他在while语句后面的循环中所做的工作非常重要。每行20字节。哈哈,你对Perl的内存效率知之甚少。如果你解析它并存储在散列中,它需要更多的时间。你说的O(nm)是什么意思?他在这里什么也没做。他在一个文件上循环一次,在第二个文件上循环一次,并且没有做任何愚蠢的事情,比如在第二个循环中顺序扫描数组。@Daniel:如果你认为哈希查找是O(1),那么你就太天真了。这只是书本上的事,但事实并非如此。首先,哈希映射查找与哈希计算成正比,它与密钥长度乘以哈希长度成正比,哈希长度通常是密钥空间的logN。所以实际上查找至少是O(logN)。(是的,Perl使用自适应哈希长度。)其次,还有一些额外的效果,比如CPU缓存命中等等。事实上,它比O(logN)和O(1)都要慢得多。我原来的散列变得越来越慢,int
    #!/usr/bin/env perl
    use strict;
    use warnings;
    
    use Text::CSV_XS;
    use autodie;
    
    use constant KEYPOS => 4;
    
    die "Insufficient number of parameters" if @ARGV < 2;
    my $csv = Text::CSV_XS->new( { eol => $/ } );
    my $sortpos = KEYPOS + 1;
    open my $file1, "sort -n -k$sortpos -t, $ARGV[0] |";
    open my $file2, "sort -n -k$sortpos -t, $ARGV[1] |";
    my $row1 = $csv->getline($file1);
    my $row2 = $csv->getline($file2);
    while ( $row1 and $row2 ) {
        my $row;
        if ( $row1->[KEYPOS] == $row2->[KEYPOS] ) {    # merge rows
            $row  = [ map { $row1->[$_] || $row2->[$_] } 0 .. $#$row1 ];
            $row1 = $csv->getline($file1);
            $row2 = $csv->getline($file2);
        }
        elsif ( $row1->[KEYPOS] < $row2->[KEYPOS] ) {
            $row  = $row1;
            $row1 = $csv->getline($file1);
        }
        else {
            $row  = $row2;
            $row2 = $csv->getline($file2);
        }
        $csv->print( *STDOUT, $row );
    }
    
    # flush possible tail
    while ( $row1 ) {
        $csv->print( *STDOUT, $row1 );
        $row1 = $csv->getline($file1);
    }
    while ( $row2 ) {
        $csv->print( *STDOUT, $row2 );
        $row2 = $csv->getline($file1);
    }
    close $file1;
    close $file2;
    
    (open my $file1, '-|') || exec('sort',  '-n',  "-k$sortpos",  '-t,',  $ARGV[0]);
    (open my $file2, '-|') || exec('sort',  '-n',  "-k$sortpos",  '-t,',  $ARGV[1]);