Perl Merge文件

时间:2014-03-23 01:50:05

标签: perl perl-data-structures

我需要合并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

到目前为止,我已尝试过,但4rth文件中的数据不按排序顺序排列,

#!/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);

4 个答案:

答案 0 :(得分:3)

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

  1. cat 3个文件在一起。
  2. 使用sort对合并文件进行排序。
  3. 但是,使用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;
    

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

    以下解决方案因此流式传输文件,按顺序拉出下一个文件:

    #!/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;
        }
    }
    

答案 1 :(得分: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

答案 2 :(得分:1)

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

#!/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提供的第二个脚本基本相同,但更清晰,更简洁。

答案 3 :(得分:0)

我建议使用这个解决方案,它使用一个排序的哈希数组 - 每个哈希对应一个输入文件,并包含一个文件句柄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