2

我的脚本应该执行以下操作。它需要一个旧的标量列表,并创建一个新的、相应的数字列表。旧列表称为@oldMarkers,新列表称为@newMarkers。

示例输入如下: chr1, chr2, IMP, chr3, IMP, IMP, IMP, chr4

示例输出如下:1, 2, 2.1, 3, 3.1, 3.2, 3.3, 4

脚本的重点是读取@oldMarkers 列表并输出一个列表,其中对于包含字母“chr”的元素的每个实例,一个整数被推入数组@newMarkers。对于@oldMarkers 中的每个IMP 实例,都会将一个十进制数添加到@newMarkers。新的十进制数与前一个数字具有相同的“基本整数”,但添加了 0.1。换句话说,“IMP”的多个后续实例应该与最近读取的“chr”条目具有相同的整数,并附加一个十进制值,用于计算与该最近“chr”相对应的 IMP 的数量入口。

下面的脚本几乎可以 100% 工作。它甚至通常在以下情况下工作。在@oldMarkers 的某些地方,有许多 IMP 条目。当一行中有超过 10 个 IMP 时,代码应该将值推送到 @newMarkers 以便该条目块的所有“IMP”具有相同的整数,这也与对应于最近的数字匹配在@oldMarkers 中读取“chr”的实例。对该整数加 0.1。当小数点的值达到 0.9 时,小数点“重新开始”回到 .1 并从那里上升,直到 IMP 条目的结束。

例如,如果 @oldMarkers 有 13 个“IMP”块并且是: chr1, chr2, IMP, IMP, IMP, IMP, IMP, IMP, IMP, IMP, IMP, IMP, IMP, IMP, IMP, chr2

那么@newMarkers 应该是: 1, 2, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9, 2.1, 2.2, 2.3, 2.4, 3

脚本摘要:

原始文件包含多行两个元素。第一个元素并不重要,因此在代码中被跳过。每行的第二个元素是一个 ID,类似于“chr4”或“IMP”。循环读取每一行,将while第二个元素添加到数组@oldMarkers。

然后,逐项读取该数组。该脚本首先询问@newMarkers 中的条目是否对应于原始@oldMarker 列表中的“chr”或“IMP”。这是通过第一个ifelse设置完成的。

接下来,对于这两个条件,进一步询问该条目是否来自对应于“chr”或“IMP”条目的数字本身。这是在第一个这样的集合中使用嵌入ifelse集合完成的。

然后根据条件定义新元素并将其推送到@newMarker。

就像我说的,这主要是有效的。然而,有时,当 IMP 的拉伸超过 10 时,脚本不会“回收”小数。相反,它将 .1 添加到前面的值并输入一个新的整数整数。但对于超过 10 的其他拉伸,它工作正常。它与这个“错误”不一致。

你能发现问题吗?

my @oldMarkers = ();
my @newMarkers = ();

while ( my $line = <$FILE> )
    {
    chomp $line;
    my @entries = split( '\t', $line );
    push( @oldMarkers, $entries[ 1 ] ); 
    } ### end of while


for ( my $i = 0 ; $i < scalar @oldMarkers   ; $i++ )
    {  
     if ( $oldMarkers[ $i ] =~ m/chr/ ) ### is a marker
        {
         if ( $oldMarkers[ $i - 1 ] =~ m/IMP/ ) ### new marker comes after imputed site
            {
             push( @newMarkers, int( $newMarkers[ $i - 1 ] ) + 1 );            
            }

       else  ### is coming after a marker                                       
           {
            push( @newMarkers, $newMarkers[ $i - 1 ] + 1 ); 
           }    

      } ### if

   else    ### is an imputed site
      {
       if ( $oldMarkers[ $i - 1 ] =~ m/IMP/ ) ### imputed site is after another imputed site
          {
           my $value = $newMarkers[ $i - 1 ] - int( $newMarkers[ $i - 1 ] );

           if ( $value < .9 )
                {
                 push( @newMarkers, $newMarkers[ $i - 1 ] + .1 );   
                }

          elsif ( $value > .9 )
                {
                 push( @newMarkers, int( $newMarkers[ $i - 1 ] ) + .1  );   
                } 


        } ### if

   else ### imputed site is after a marker
        {
         push( @newMarkers, int( $newMarkers[ $i - 1 ] ) + .1 ); 
        }    

    } ### else   

} ### for    


print $newMarkerfile join( "\t", @newMarkers);             
4

6 回答 6

6

仅使用整数算术来做到这一点会更容易和更可靠。基本上,跟踪两个整数值:一个用于前面的.数字,一个用于后面的数字。如果后面的数字.达到 10,则将其重置为 1:

my @newMarkers;
my $chrCount = 0;
my $impCount = 0;

foreach my $marker (@oldMarkers) {
    if ( $marker =~ /^chr\d+$/ ) {
        $chrCount++;
        $impCount = 0;
        push @newMarkers, $chrCount;
    } elsif ( $marker eq "IMP" ) {
        $impCount++;
        $impCount = 1 if $impCount == 10;
        push @newMarkers, "$chrCount.$impCount";
    } else {
        die "Unrecognized marker $marker";
    }
}

codepad.org 上的演示

于 2013-01-07T21:51:33.290 回答
3

10 × 0.1 = 1,然而

>perl -E"$x=0; $x += 0.1 for 1..10; say sprintf('%0.16f', $x); say int($x);"
0.9999999999999999
0

在处理浮点数时,您应该始终使用某种形式或舍入或公差。

太多的数字在二进制中是周期性的。你知道 1/3 在十进制中是如何周期性的吗?嗯,1/10 在二进制中是周期性的。2/10、3/10、4/10、6/10、7/10、8/10 和 9/10 也是如此。这些数字都不能用浮点数无误地表示。

于 2013-01-07T21:36:28.553 回答
3

似乎工作正常:

$imp_order = 0;
$chr_order = 0;
for my $old (@oldMarkers) {   
  if ( $old =~ m/chr/ ) ### is a marker
  {

    $imp_order = 0;
    $chr_order++;

    push( @newMarkers,  $chr_order );    

  } ### if

  else    ### is an imputed site
  {
      $imp_order = 0 if $imp_order == 9;
      $imp_order++;
      push( @newMarkers, $chr_order + $imp_order / 10 );   

  } ### else   

} ### for    
于 2013-01-07T21:55:52.757 回答
1

正如 ikegami 建议的那样,那些 int() 调用肯定会导致您的舍入问题。您可以使用 POSIX,然后根据需要使用 ceil() 或 floor() 来解决问题。

请参阅此处的文档:http: //perldoc.perl.org/perlfaq4.html#Does-Perl-have-a-round%28%29-function%3F-What-about-ceil%28%29-and-floor %28%29%3F-三角函数%3F

例如,我认为您描述的确切错误可以通过替换来修复:

elsif ( $value > .9 )
    {
        push( @newMarkers, int( $newMarkers[ $i - 1 ] ) + .1  );   
    }

和:

elsif ( $value > .9 )
    {
        push( @newMarkers, ceil( $newMarkers[ $i - 1 ] ) + .1  );   
    }

对于每种情况,您可能应该用适当的舍入函数替换所有这些 int() 调用。

跟进:我实际上更喜欢建议分别跟踪“chr”计数/订单和“imp”计数/订单的多个解决方案,而不是作为单个浮点数。但我将把它留在这里,因为我认为它对发帖人有关如何实施四舍五入的解决方案具有指导意义。

于 2013-01-07T21:46:07.997 回答
0

如果我理解正确,那么这就是所有必要的。

use strict;
use warnings;

my @old = do {
  open my $fh, '<', 'markers.txt' or die $!;
  map /([^\t]+)$/, <$fh>;
};

my @new;
my @marker;
my $chr = 0;

for (@old) {
  if ( /chr/ ) {
    @marker = (++$chr);
  }
  elsif ( @marker > 1 and $marker[1] == 9 ) {
    $marker[1] = 1;
  }
  else {
    $marker[1]++;
  }
  push @new, [@marker];
}

@new = map join('.', @$_), @new;

print join(', ', @new), "\n";

输出

1, 2, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9, 2.1, 2.2, 2.3, 2.4, 3
于 2013-01-08T06:55:25.073 回答
-1

如果在您的第​​二个示例中,输出应为: 1 2 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 3 3.1 3.2 3.3 4

而不是 > 使用 >=

然后你有 2 个选项: int( $newMarkers[ $i - 1 ] ) + $value + .100000 或在 newMarkers[$i - 1] 的 int 值上加 1

于 2013-01-07T22:21:16.263 回答