1

更新(16/1/13)

ボロディンは、私が完全に見落としていた別の可能性を指摘しました。実際の
ファイル(手動で座って、それぞれ約 10MB の 46 個のファイルを調べ始めました) では、 File1 の特定の値に対して、File2小さい値が存在しない場合があります(ただし、大きい値は存在します)。

同様に、 File1 の特定の値に対して、より大きな値がFile2に存在しない場合があります(ただし、より小さな値は存在します) 。

この更新を反映するために、ここでサンプル ファイルと目的の出力を更新しています。

更新 (15/1/13)

File1 の値がFile2の値と一致する場合を考慮して、目的の出力を更新しました。そのようなシナリオを指摘してくれたボロディンに感謝します。


次のような 2 つのファイルがあります。

ファイル1

 chr1   10227  
 chr1   447989  
 chr1   535362
 chr1   856788
chr1    249240496

ファイル2

chr1    11017
chr1    11068
chr1    23525
chr1    439583
chr1    454089
chr1    460017
chr1    544711
chr1    546239
chr1    856788
chr1    249213429
chr1    249214499
chr1    249239072

私がする必要があるのは、file1の foreach 値です。、 file210227から検索、最も 近い2 つの値。これらの値の 1 つが大きくなり、もう 1 つが小さくなります。したがって、file1を取り込むと、 file2で最も近い値はとです。ここで、次のような出力 (タブ区切り) を得るために、差を計算する必要があります。102279250110179250 - 10227-97711017 - 10227790

望ましい出力

chr1   10227   No   790   No Match
chr1   447989  No   6100  -8406
chr1   535362  No   9349  -75345
chr1   856788  Yes  
chr1   249240496 No No Match -25997

これを行う最速の方法は、ハッシュを使用して2つのファイルを読み込み、数値を値としてkeys代入することだと考えました1

私がこれまでに書いたコードは、 file210227のすべての値との違いを示しています。と も同様です。これを停止して、最も近い数字のみの違いを見つけるにはどうすればよいですか。1 つは >で、1 は < です。4479895356821022710227

コード

use 5.014;
use warnings;

#code to enter lsdpeak and pg4 data into hash with KEYS as the numerical values, VALUE as 1

#Assign filename
my $file1 = 'lsdpeakmid.txt';
my $file2 = 'pg4mid.txt';

#Open file
open my $fh1, '<', $file1 or die $!;
open my $fh2, '<', $file2 or die $!;

#Read in file linewise
my %hash1;
while(<$fh1>){

    my $key1 = (split)[1];
    $hash1{$key1} = 1;

}


    my %hash2;
    while(<$fh2>){
        my $key2 = (split)[1];

    }


foreach my $key1 (sort keys %hash1){

    foreach my $key2 (sort keys %hash2){

    say $key2-$key1;

    }

}

#Exit
exit;

問題を解決するために時間を割いていただきありがとうございます。すべてのコメント/回答に感謝します。

4

3 に答える 3

2

正しい境界を見つける唯一の方法file2は値のリストを検索することであり、ハッシュはそれを容易にしないため、ここではハッシュは適切な選択ではありません。

このプログラムは、 からのすべての境界をfile2配列@boundariesに入れ、この配列から読み取った各値file1を検索して、より大きな最初の境界値を見つけます。次に、これと前の境界が必要であり、演算はprintステートメントで行われます。

このコードに一致するfile2境界が含まれている場合、または指定された値よりも大きいまたは小さい境界がない場合、このコードには問題があることに注意してください。

use strict;
use warnings;

use Data::Dump;

my $file1 = 'lsdpeakmid.txt';
my $file2 = 'pg4mid.txt';

my @boundaries = do {
  open my $fh, '<', $file2 or die $!;
  map { (split)[1] } <$fh>;
};

open my $fh, '<', $file1 or die $!;

while (my $line = <$fh>) {
  chomp $line;
  my @vals = split ' ', $line;
  my $val = $vals[-1];
  for my $i (1 .. $#boundaries) {
    if ($boundaries[$i] > $val) {
      print join(' ', @vals, $boundaries[$i] - $val, $boundaries[$i-1] - $val), "\n";
      last;
    }
  }
}

出力

chr1 10227 790 -977
chr1 447989 6100 -8406
chr1 535362 9349 -75345
于 2013-01-14T11:50:01.223 に答える
1

一方通行:

#!/usr/bin/perl
use strict;
use warnings;
use List::Util qw(first);

open my $fh1,'<','file1' or die $!;
open my $fh2,'<','file2' or die $!;
my %h1;

while(<$fh2>){
        chomp;
        my ($k,$v)=split(/\s+/);
        push @{$h1{$k}}, $v;
}
close $fh2;

while (<$fh1>){
        chomp;
        my ($k, $v)=split(/\s+/);
        my $bef=first{$_ >= $v}@{$h1{$k}};
        $bef=defined $bef?$bef-$v:"No match";
        my $aft=first{$_ <= $v}reverse @{$h1{$k}};
        $aft=defined $aft?$aft-$v:"No match";
        my $str=sprintf("%-8s %-10d %-5s %-8s %-8s",$k, $v,$bef?"No":"Yes",$bef?$bef:"",$aft?$aft:"");
        print $str, "\n";
}
close $fh1;

最初のwhileループは2番目のファイルを読み取り、キーがchr1で、値がchr1のすべての値を含む配列参照であるハッシュを作成します。

ブロックはforeach、すべてのキーを番号順にソートします。2番目のwhileループは、file1のレコードを処理し、のfirst関数を使用しList::Utilて結果を取得します。

この関数は2回使用されます。1回目は現在の値よりも最初の最大値を取得し、2回目はed配列firstを使用して取得した現在の値よりも最後の最小値を取得します。firstreverse sort

最初の関数:最初の関数は、条件を満たす配列の最初の数値を返します。

first{$_ > $v}@{$h1{$k}}=>これは、現在の数値よりも大きい配列の最初の数値を取得します。10227と言うと、最初に11017が返されます。

次に必要なのは、10227より前の最後の最小数です。これを取得するには、最初の関数を逆配列に適用します。

first{$_ < $v}reverse @{$h1{$k}}=>これにより、10227未満の最初の数値が返されます。配列が逆になっているため、実際に取得されるのは、10227より前の最後の最小数値である9250です。

これを実行すると:

chr1     10227      No    790      No match
chr1     447989     No    6100     -8406
chr1     535362     No    9349     -75345
chr1     856788     Yes
chr1     249240496  No    No match -1424
于 2013-01-14T09:15:02.373 に答える
1

まず、2 番目のファイルを読み取り、値を配列に入れます。chr1さらに、これは一定であり、安全に破棄できると想定しています。

#!/usr/bin/perl
use strict; use warnings;
my @file2;
open my $fh2, "<", "file2" or die $!;
while (<$fh2>) {
  my (undef, $num) = split;
  die "the number contains illegal characters" if $num =~ /\D/;
  push @file2, $num;
}
@file2 = sort {$a <=> $b} @file2; # sort ascending
# remove previous line if sorting is already guaranteed.

次に、サブルーチンを定義して、配列内の 2 つの値を見つけます。これは、並べ替えられたリスト ( O(log n) )で特定の値を見つけるための基本的なアルゴリズムの単なるバリエーションであり、少なくとも大きなセットでは、各値を反復処理するよりも優れたパフォーマンスを発揮するはずです。また、値ごとにリスト全体を逆にする必要はありません。

sub find {
  my ($num, $arrayref) = @_;

  # exit if array is too small
  return unless @$arrayref >= 2;
  # exit if $num is outside the values of this array (-1 is last element)
  return if $num <= $arrayref->[0] or $arrayref->[-1] < $num;

   my ($lo, $hi) = (1, $#$arrayref);
  my $i = int(($lo+$hi)/2); # start in the middle

  # iterate until
  #   a) the previous index contains a number that is smaller than $num and
  #   b) the current index contains a number that is greater or equal to $num.
  until($arrayref->[$i-1] < $num and $num <= $arrayref->[$i]) {
    # make $i the next lower or upper bound.
    # instead of going into an infinite loop (which would happen if we
    # assign $i to a variable that already holds the same value), we discard
    # the value and move on towards the middle.
          # $i is too small
    if    ($num >  $arrayref->[$i]  ) { $lo = ($lo == $i ? $i+1 : $i) }
          # $i is too large
    elsif ($num <= $arrayref->[$i-1]) { $hi = ($hi == $i ? $i-1 : $i) }
          # in case I made an error:
    else                              { die "illegal state" }
    # calculate the next index
    $i  = int(($lo+$hi)/2);
  }
  return @{$arrayref}[$i-1, $i];
}

残りは簡単です:

open my $fh1, "<", "file1" or die $!;
while (<$fh1>) {
  my ($chr, $num) = split;
  die "the number contains illegal characters" if $num =~ /\D/;
  if (my ($lo, $hi) = find($num, \@file2)) {
    if ($hi == $num) {
      print join("\t", $chr, $num, "Yes"), "\n";
    } else {
      print join("\t", $chr, $num, "No", $hi-$num, $lo-$num), "\n";
    }
  } else {
    # no matching numbers were found in file 2
    print join("\t", $chr, $num, "No-match"), "\n";
  }
}

出力:

chr1    10227   No      790     -977                                                            
chr1    447989  No      6100    -8406                                                           
chr1    535362  No      9349    -75345                                                          
chr1    856788  Yes
于 2013-01-14T11:08:02.723 に答える