【问题标题】:Perl Merge filePerl 合并文件
【发布时间】:2014-03-24 02:49:25
【问题描述】:

我有 3 个或多个文件需要合并,数据如下所示..

file 1
0334.45656
0334.45678
0335.67899
file 2
0334.89765
0335.12346
0335.56789
file 3
0334.12345
0335.45678
0335.98764

文件 4 中的预期输出,

0334.89765
0334.89765
0334.89765
0334.12345
0335.67899
0335.12346
0335.56789
0335.45678
0335.98764

到目前为止,我已经尝试过,但第 4 个文件中的数据没有按排序顺序排列,

#!/usr/bin/perl
my %hash;
my $outFile = "outFile.txt";
foreach $file(@ARGV)
{
print "$file\n";
open (IN, "$file") || die "cannot open file $!";
open (OUT,">>$outFile") || die "cannot open file $!";
while ( <IN> )
{
    chomp $_;
    ($timestamp,$data) = split (/\./,$_);
    $hash{$timeStamp}{'data'}=$data;
    if (defined $hash{$timeStamp})
    {
    print "$_\n";
    print OUT"$_\n";

        }
}
}
close (IN);
close (OUT);

【问题讨论】:

    标签: perl perl-data-structures


    【解决方案1】:

    我通常不会建议这样做,但 unix 实用程序应该能够很好地处理这个问题。

    1. cat 3 个文件放在一起。
    2. 使用sort对合并后的文件进行排序。

    但是,使用 perl,可以执行以下操作:

    #!/usr/bin/perl
    
    use strict;
    use warnings;
    
    my @data;
    push @data, $_ while (<>);
    
    # Because the numbers are all equal length, alpha sort will work here
    print for sort @data;
    

    但是,正如我们所讨论的,文件可能会非常大。因此,如果您能够利用所有文件都已排序的事实,那么它在内存和速度上都会更加高效。

    因此,以下解决方案流式传输文件,按 while 的每个循环的顺序提取下一个文件:

    #!/usr/bin/perl
    
    # Could name this catsort.pl
    
    use strict;
    use warnings;
    use autodie;
    
    # Initialize File handles
    my @fhs = map {open my $fh, '<', $_; $fh} @ARGV;
    
    # First Line of each file
    my @data = map {scalar <$_>} @fhs;
    
    # Loop while a next line exists
    while (@data) {
        # Pull out the next entry.
        my $index = (sort {$data[$a] cmp $data[$b]} (0..$#data))[0];
    
        print $data[$index];
    
        # Fill In next Data at index.
        if (! defined($data[$index] = readline $fhs[$index])) {
            # End of that File
            splice @fhs, $index, 1;
            splice @data, $index, 1;
        }
    }
    

    【讨论】:

    • 任何超过三行的 shell 脚本都应该是 perl 脚本...尽管正如您所暗示的,这将是一个两行长的 shell 脚本,非常适合这种情况。
    • 谢谢!我知道,在 unix 中会很容易做到,我想知道如何在 Perl 中做到这一点?
    • 有什么方法可以通过像哈希一样将内存中的数据获取到内存中,然后尝试匹配传入的数据,然后将其放入 OUT 文件中?
    • @efficasy 是的,几乎一切皆有可能,只需编写代码即可。假设您对以前的文件进行了排序,那么应该可以流式传输传入的文件并在我们进行时对它们进行排序。也许我一会儿会写一些类似的东西来取乐。
    • 请注意,您的预期输出实际上并未排序。左边的数字按升序排列,但右边的数字似乎是随机顺序。这是您的问题的错误类型吗?或者是否有我无法解码的订单。
    【解决方案2】:

    以更可重用的方式使用米勒的想法,

    use strict;
    use warnings;
    
    sub get_sort_iterator {
      my @fhs = map {open my $fh, '<', $_ or die $!; $fh} @_;
      my @d;
    
      return sub {
        for my $i (0 .. $#fhs) {
          # skip to next file handle if it doesn't exists or we have value in $d[$i]
          next if !$fhs[$i] or defined $d[$i];
    
          # reading from $fhs[$i] file handle was success?
          if ( defined($d[$i] = readline($fhs[$i])) ) { chomp($d[$i]) }
          # file handle at EOF, not needed any more
          else  { undef $fhs[$i] }
        }
        # compare as numbers, return undef if no more data
        my ($index) = sort {$d[$a] <=> $d[$b]} grep { defined $d[$_] } 0..$#d
          or return;
    
        # return value from $d[$index], and set it to undef
        return delete $d[$index];
      };
    }
    
    my $iter = get_sort_iterator(@ARGV);
    while (defined(my $x = $iter->())) {
      print "$x\n";
    }
    

    输出

    0334.12345
    0334.45656
    0334.45678
    0334.89765
    0335.12346
    0335.45678
    0335.56789
    0335.67899
    0335.98764
    

    【讨论】:

      【解决方案3】:

      假设每个输入文件都已经按升序排列,并且其中至少有一行,这个脚本可以按升序合并它们:

      #!/usr/bin/perl
      
      use warnings;
      use strict;
      
      use List::Util 'reduce';
      
      sub min_index {
          reduce { $_[$a] < $_[$b] ? $a : $b } 0 .. $#_;
      }
      
      my @fhs = map { open my $fh, '<', $_; $fh } @ARGV;
      my @data = map { scalar <$_> } @fhs;
      
      while (@data) {
          my $idx = min_index(@data);
          print "$data[$idx]";
          if (! defined($data[$idx] = readline $fhs[$idx])) {
              splice @data, $idx, 1;
              splice @fhs, $idx, 1;
          }
      }
      

      注意:这与@Miller 提供的第二个脚本基本相同,但更清晰、更简洁。

      【讨论】:

        【解决方案4】:

        我建议这个解决方案,它使用一个排序的哈希数组——每个哈希对应一个输入文件,并包含一个文件句柄fh,最后一行读取line和从timestamp行中提取的时间戳.

        数组末尾的哈希始终对应于时间戳最小值的输入,因此只需重复pop数组中的下一个值,print其数据,读取下一行并(如果尚未达到 eof)按排序顺序将其重新插入到数组中。

        与其他答案使用的每个输出行的所有数据的重复排序相比,这可能会显着提高速度。

        请注意,程序需要输入文件列表作为命令行参数,并将其合并的输出发送到 STDOUT。它还假设输入文件已经排序。

        use strict;
        use warnings;
        use autodie;
        
        my @data;
        
        for my $file (@ARGV) {
          my $item;
          open $item->{fh}, '<', $file;
          insert_item($item, \@data);
        }
        
        while (@data) {
          my $item = pop @data;
          print $item->{line};
          insert_item($item, \@data);
        }
        
        sub insert_item {
          my ($item, $array) = @_;
          return if eof $item->{fh};
          $item->{line} = readline $item->{fh};
          ($item->{timestamp}) = $item->{line} =~ /^(\d+)/;
          my $i = 0;
          ++$i while $i < @$array and $item->{timestamp} < $array->[$i]{timestamp};
          splice @$array, $i, 0, $item;
        }
        

        输出

        0334.45656
        0334.89765
        0334.12345
        0334.45678
        0335.12346
        0335.45678
        0335.67899
        0335.56789
        0335.98764
        

        【讨论】:

          猜你喜欢
          • 2012-11-16
          • 1970-01-01
          • 2013-08-02
          • 2014-10-15
          • 1970-01-01
          • 1970-01-01
          • 2023-04-08
          • 2021-07-03
          • 1970-01-01
          相关资源
          最近更新 更多