Perl.使用until函数



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

输入文件示例:

    .
    .
    .
    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
    .
    .
    .

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

我的总体目标是在整个文件中"填充"第二列和第三列(第四列被忽略)。为了做到这一点,我需要我的脚本来识别一组连续的空白行,加上紧接在这组连续空白行的前一行和后一行。在上面的例子中,这将是第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;
    }

谁能给我一个提示,如何耦合一种方式来读取数组和比较行,以找到我需要跨文件的集合?

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

除了缓存旧值之外,还需要缓存中间的行。因为你只需要标签,你只需要抓住它们。然后,当您到达下一行时,您可以进行插值并发出结果。

我是这样做的。它比我最初的示例稍微复杂一些,但适用相同的原则:只存储中间行,然后在到达终端时发出结果。

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_labelt$last_num1t$last_num2t$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 "$label1t$num1t$num2t$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

当然,你可以做任何你需要的数字四舍五入或格式化。:)

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

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

A 'full', line应该在@entries中有四个元素,这就是if ( @entires == 4 )所寻找的。如果找到了,只有在打印了单列行后,它才会将其作为下一行打印。然后,它节省了这条线。只有当行没有三个制表符时,才会在if之外打印。

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

use strict;
use warnings;
my @file = <DATA>;
for ( my $i = 1 ; $i < $#file ; $i++ ) {
    if ( $file[$i] =~ /(?:ts){3}/ ) {
        print $file[ $i - 1 ];    # Preceeding line
        while ( $file[$i] =~ /(?:ts){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

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

最新更新