5

我有一个简单的数据文件。文件中的每一行都有四个元素。有些行没有空白条目。其他行有一个第一个条目,其余三个是空白的,或者更确切地说是“填充”了一个空格。它是一个制表符分隔的文件。

输入文件示例:

    .
    .
    .
    30  13387412    34.80391242 sSN_FIRST
    30  13387412    34.80391242 sSN5_40
    30.1             
    30.2             
    30.3             
    30.4             
    31  14740248    65.60590089 s32138223_44
    31  14740248    65.60590089 s321382_LAST
    .
    .
    .

重申一下,如果这很重要,我的文件中的“空白”实际上包含一个空格。

我的总体目标是在整个文件中“填写”第二列和第三列(忽略第四列)。为了做到这一点,我需要我的脚本来识别空白的连续行集,加上紧接在前面的行和紧接在连续空白行之后的行。在上面的示例中,这将是第 2 - 7 行。一旦我能做到这一点,我就可以使用已填充的侧翼行中的信息来帮助“填充”中间行中缺失的条目。

我一直在试验这个until功能,但我没有成功地将它与一个读取数据线的循环耦合。例如,我可以阅读这些行并找到空白行:

open( my $FILE, "<$mapfile" );
my @file = <$FILE>;
close $FILE;

for ( my $i = 1 ; $i < scalar @file ; $i++ ) 
    {
     my @entries = split( '\t', $file[ $i ] );
     if ( $entries[ 1 ] =~ m/ / ) 
        {
         print $file[ $i ]."\n";
        }
    }

但我正在尝试使用该until功能,以便读取行并搜索我正在寻找的连续行集(“空白”行加上两个侧面的“完整”行)。例如:

until ( $file[ a line ] =~ m/ / && $file[ another line ] =~ m/ / )   
    {
     my linear interpolation here;
    }

谁能给我一个提示,告诉我如何结合一种方法来读取数组并比较行以在文件中找到我需要的集合?

4

2 回答 2

3

你想要实现的是一个缓存算法:记住(缓存)以前的值,如果没有新的出现就使用它们。你甚至不需要正则表达式。:)

除了缓存旧值之外,您还需要缓存其间的行。由于您只需要标签,因此您只需要保留这些标签。然后,当您到达下一个完整行时,您可以进行插值并发出结果。

这就是我的做法。它比我原来的例子复杂一点,但同样的原则适用:只存储中间行,然后在你到达终端时发出结果。

use strict;
use warnings;
use feature 'say';


# Get start conditions, and cache those numbers.

sub read_block
{
   my $line = <DATA>;
   return 1 unless defined $line; # we're done if nothing more to read

   # Process and store data from the first line in the block.
   chomp $line;
   my ($last_label, $last_num1, $last_num2, $last_label2) = split /\t/, $line;

   # Keep reading lines until we find the end of the block.
   my @label_cache;
   my $found_last = 0;
   my ($label1, $num1, $num2, $label2);
   while (!$found_last)
   {
      $line = <DATA>;
      chomp $line;
      ($label1, $num1, $num2, $label2) = split /\t/, $line;
      if (defined $num1 && defined $num2)
      {
         $found_last = 1; # We have final numbers!  We can interpolate now.
      }
      else
      {
         push @label_cache, $label1; 
      }
   }

   # Begin display.  Show the first line of the block.
   say "$last_label\t$last_num1\t$last_num2\t$last_label2";

   # Calculate the slope for interpolation: (last - first) / difference
   my $slope1 = ($num1 - $last_num1) / (@label_cache + 1);
   my $slope2 = ($num2 - $last_num2) / (@label_cache + 1);
   my $distance = 0;

   # Display each label and the lines inside.
   foreach my $label (@label_cache)
   {
      ++$distance;
      say $label, "\t",
          $slope1 * $distance + $last_num1, "\t",
          $slope2 * $distance + $last_num2;
   }

   # Display the final line in the block.
   say "$label1\t$num1\t$num2\t$label2";

   # Not done yet, so return a 'false' value.
   return 0;
}

# Main part of the script

my $done = 0;
while (! $done)
{
   $done = read_block();
}


__DATA__
a   3   4   end
e
f
g
h
i
k   15  26  start
k   15  26  end
o
p
q
r
s   3   5   start
s   3   5   end
v
w
x
y   14  16  start

发出:

a       3       4       end
e       5       7.66666666666667
f       7       11.3333333333333
g       9       15
h       11      18.6666666666667
i       13      22.3333333333333
k       15      26      start
k       15      26      end
o       12.6    21.8
p       10.2    17.6
q       7.8     13.4
r       5.4     9.2
s       3       5       start
s       3       5       end
v       5.75    7.75
w       8.5     10.5
x       11.25   13.25
y       14      16      start

然后,您当然可以进行所需的任何类型的数字舍入或格式化。:)

于 2013-01-09T20:26:17.150 回答
2

也许以下内容会有所帮助:

use strict;
use warnings;

my ( $last, $oneColumn );

my @file = <DATA>;

for my $line (@file) {
    my @entires = split ' ', $line;

    if ( @entires == 4 ) {
        if ($oneColumn) {
            print $line;    # Succeeding line
            $oneColumn = 0;
        }
        $last = $line;
        next;
    }

    print $last if $last;    # Preceeding line
    undef $last;
    print $line;             # One-column line
    $oneColumn = 1;

}

__DATA__
30  13387412    34.80391242 sSN_FIRST
30  13387412    34.80391242 sSN5_40
30.1             
30.2             
30.3             
30.4             
31  14740248    65.60590089 s32138223_44
31  14740248    65.60590089 s321382_LAST

输出:

30  13387412    34.80391242 sSN5_40
30.1
30.2
30.3
30.4
31  14740248    65.60590089 s32138223_44

一个“完整”的行应该在 中包含四个元素@entries,这就是要if ( @entires == 4 )查找的内容。如果找到,只有在打印了一列行时,它才会将其打印为后续行。然后,它保存该行。if仅当行没有三个选项卡时,才会在外部打印行。

以下较短的脚本产生相同的输出:

use strict;
use warnings;

my @file = <DATA>;

for ( my $i = 1 ; $i < $#file ; $i++ ) {

    if ( $file[$i] =~ /(?:\t\s){3}/ ) {
        print $file[ $i - 1 ];    # Preceeding line

        while ( $file[$i] =~ /(?:\t\s){3}/ and $i < $#file ) {
            print $file[ $i++ ]    # One-column line
        }

        print $file[$i];           # Succeeding line
    }
}

__DATA__
30  13387412    34.80391242 sSN_FIRST
30  13387412    34.80391242 sSN5_40
30.1             
30.2             
30.3             
30.4             
31  14740248    65.60590089 s32138223_44
31  14740248    65.60590089 s321382_LAST

匹配三组连续的/(?:\t\s){3}/制表符和空格,它们只能在只有一列的行上找到。当它找到该模式时,它会打印前一行,然后进入一个while循环,打印单列行,直到找到整行或它位于数组的末尾。最后,打印下一行。

于 2013-01-09T21:03:32.860 回答