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 のインスタンスごとに、10 進数が @newMarkers に追加されます。新しい 10 進数は、前の数値と同じ "基本整数" を持ちますが、それに .1 が追加されます。言い換えれば、「IMP」の複数の連続するインスタンスは、最後に読み取られた「chr」エントリと同じ整数を持ち、その最新の「chr」に対応する IMP の数をカウントする 10 進数の値が付加されていると想定されます。エントリ。

以下のスクリプトは、ほぼ 100% 機能します。次の場合でも、通常は機能しています。@oldMarkers のいくつかの場所には、IMP の多数のエントリがあります。行に 10 を超える IMP がある場合、コードは @newMarkers に値をプッシュして、そのエントリ ブロックのすべての「IMP」が同じ整数を持つようにすることになっています。 @oldMarkers の「chr」のインスタンスを読み取ります。その整数に 0.1 が加算されます。そして、小数の値が .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

スクリプトの要約:

元のファイルには、2 つの要素の複数行が含まれています。最初の要素は重要ではないため、コードではスキップされます。各行の 2 番目の要素は、"chr4" や "IMP" などの ID です。ループはwhile各行を読み取り、2 番目の要素を配列 @oldMarkers に追加します。

次に、この配列はエントリごとに読み取られます。スクリプトは最初に、@newMarkers のエントリが元の @oldMarker リストの「chr」または「IMP」に対応するかどうかを尋ねます。これは、最初ifelseセットで行われます。

次に、両方の条件について、エントリが「chr」または「IMP」エントリに対応する番号自体に由来するかどうかがさらに尋ねられます。これは、最初のそのようなセットに組み込まれたセットでif行われます。else

次に、新しい要素が定義され、条件に応じて @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

整数演算のみを使用してこれを行う方が簡単で信頼性が高くなります。基本的に、2 つの整数値を追跡します。1 つは の前の.数字用で、もう 1 つは後の数字用です。の後の桁.が 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

フロートを扱うときは、常に何らかの形式、丸め、または許容誤差を使用する必要があります。

バイナリで周期的な数が多すぎます。10 進数で 1/3 がどのように周期的か知っていますか? ええと、1/10 は 2 進数で周期的です。10 分の 2、10 分の 3、10 分の 4、10 分の 6、10 分の 7、10 分の 8、10 分の 9 も同様です。これらの数値はどれも、float でエラーなく表すことはできません。

于 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

池上が示唆するように、これらの 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-Trig 関数%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

2番目の例で出力が次のようになる場合: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 に答える