【问题标题】:Fastest way to search string with two mismatch in big files in perl?在perl中搜索大文件中两个不匹配的字符串的最快方法?
【发布时间】:2016-09-16 15:00:39
【问题描述】:

我修改了代码以使用两个文件。 to_search.txt 有要搜索的字符串。 big_file.fastq 有要搜索的行,如果找到字符串(允许 2 个不匹配,精确长度范围为 8-10,不添加和删除),放在各自的名称中。因此在 big_file.fastq 的所有行(第 2 行)中搜索每个字符串。

# to_search.txt: (length can be from 8-20 characters)

1       TCCCTTGT
2       ACGAGACT
3       GCTGTACG
4       ATCACCAG
5       TGGTCAAC
6       ATCGCACA
7       GTCGTGTA
8       AGCGGAGG
9       ATCCTTTG
10      TACAGCGC
#2000 search needed

# big_file.fastq: 2 billions lines (each 4 lines are associated: string search is in second line of each 4 lines). 
# Second line can have 100-200 characters
@M04398:19:000000000-APDK3:1:1101:21860:1000 1:N:0:1
TCttTTGTGATCGATCGATCGATCGATCGGTCGTGTAGCCTCCAACCAAGCACCCCATCTGTTCCAAATCTTCTCCCACTGCTACTTGAAGACGCTGAAGTTGAAGGGCCACCTTCATCATTCTGG
+
#8ACCDGGGGGGGGGGGGGEGGGGGGGGDFFEGGG@FFGGGGGGGGGGGGGGGGGCF@<FFGGGGGFE9FGGGFEACE8EFFFGGGGGF9F?CECEFEG,CFGF,CF@CCC?BFFG<,9<9AEFG,,
@M04398:19:000000000-APDK3:1:1101:13382:1000 1:N:0:1
NTCGATCGATCGATCGATCGATCGATCGTTCTGAGAGGTACCAACCAAGCACACCACGGGCGACACAGACAGCTCCGTGTTGAACGGGTTGTTCTTCTTCTTGCCTTCATCATCCCCATCCTCAGTGGACGCAGCTTGCTCATCCTTCCTC
+
#8BCCGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG
@M04398:19:000000000-APDK3:1:1101:18888:1000 1:N:0:1
NCAGAATGAGGAAGGATGAGCCCCGTCGTGTCGAAGCTATTGACACAGCGCTATTCCGTCTTTATGTTCACTTTAAGCGGTACAAGGAGCTGCTTGTTCTGATTCAGGAACCGAACCCTGGTGGTGTGCTTGGTTGGCAAGTTTACGGCTC
+
#8BCCGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGCGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFGGGGGGGGGGGGGGGGGGGGGGGGGGGE

这是两个不匹配的代码。我试过完全匹配,速度还不错:大约需要一天。我使用了 Time::Progress 模块。当我使用 2 mismatch: 显示 115 天完成。这里如何提高速度?

#!/usr/bin/perl
use strict;
use warnings;

$| = 1;

open( IN_P1, "big_file.fastq" ) or die "File not found";

my ( @sample_file_names, @barcode1 );
open( BC_FILE, "to_search.txt" ) or die "No barcode file";
my @barcode_file_content = <BC_FILE>;

foreach (@barcode_file_content) {
    chomp $_;
    $_ =~ s/\r//;
    $_ =~ s/\n//;

    #print $_;
    my @elements = split( "(\t|,| )", $_ );
    push @sample_file_names, $elements[0];
    push @barcode1,   $elements[2];
}

# open FH
my @fh_array_R1;
foreach (@sample_file_names) {
    chomp $_;
    local *OUT_R1;
    open( OUT_R1, ">", "$_\.fq" ) or die "cannot write file";
    push @fh_array_R1, *OUT_R1;
}

# unknown barcode file
open( UNKNOWN_R1, ">unknown-barcode_SE.fq" ) or die "cannot create unknown-r1 file";

while ( defined( my $firstp1 = <IN_P1> ) ) {

    my $p1_first_line  = $firstp1;
    my $p1_second_line = <IN_P1>;
    my $p1_third_line  = <IN_P1>;
    my $p1_fourth_line = <IN_P1>;

    chomp( $p1_first_line, $p1_second_line, $p1_third_line, $p1_fourth_line, );

    my $matched_R1 = "$p1_first_line\n$p1_second_line\n$p1_third_line\n$p1_fourth_line\n";

    for ( my $j = 0 ; $j < scalar @barcode1 ; $j++ ) {
        chomp $barcode1[$j];

        my $barcode1_regex = make_barcode_fragments( $barcode1[$j] );

        if ( $p1_second_line =~ /$barcode1_regex/i ) {
            # keep if matched
            print { $fh_array_R1[$j] } $matched_R1;
            last;
        }
        else {
            #print to unknown;
            print UNKNOWN_R1 $matched_R1;
        }
    }
}

# make two mismatch patterm of barcode
sub make_barcode_fragments {
    my ($in1) = @_;
    my @subpats;
    for my $i ( 0 .. length($in1) - 1 ) {
        for my $j ( $i + 1 .. length($in1) - 1 ) {
            my $subpat = join( '',
                substr( $in1, 0, $i ),
                '\\w', substr( $in1, $i + 1, $j - $i - 1 ),
                '\\w', substr( $in1, $j + 1 ),
            );
            push @subpats, $subpat;
        }
    }
    my $pat = join( '|', @subpats );

    #print $pat;
    return "$pat";
}
exit;

【问题讨论】:

  • Text::Levenshtein 可能会更快。
  • @Sobrique :这给出了字符串之间的距离。在这里,一个字符串与另一个更大的字符串进行搜索,这里如何使用距离?谢谢
  • 我正在尝试理解代码:您为什么要计算 (ASCII) 1-173(八进制 001-255)范围内的字符?
  • 这看起来像是生物信息学中经常出现的问题。有一些算法和程序可以非常快速地将读数与参考序列对齐。例如 bwa (github.com/lh3/bwa) 实现 Burrow Wheeler 变换。如果这听起来像是正确的轨道并且您遇到了困难,您可能想在 Biostars (biostars.org) 上寻求帮助。
  • @dariober : 这是基于条形码而不是对齐方式的多路分解。

标签: string perl search


【解决方案1】:

如果您的算法无法在 Perl 本身中更改/改进,您仍然可以通过在 C 中编写耗时的部分来获得加速。以下是使用内联 C 的示例:

use strict;
use warnings;
use Benchmark qw(timethese);
use Inline C => './check_line_c.c';

my $find    = "MATCH1";
my $search = "saasdadadadadasd";

my %sub_info = (
    c    => sub { check_line_c( $find, $search ) },
    perl => sub { check_line_perl( $find, $search ) },
);

timethese( 4_000_000, \%sub_info );

sub check_line_perl {
    my ($find, $search ) = @_;

    my $max_distance = 2;

    for my $offset ( 0 .. length($search) - length($find) ) {
        my $substr = substr( $search, $offset, length($find) );
        my $hd = hd( $find, $substr );
        if ( $hd <= $max_distance ) {
            return ( $hd, $substr );
        }
    }
    return ( undef, undef );
}

sub hd {
    return ( $_[0] ^ $_[1] ) =~ tr/\001-\377//;
}

check_line_c.c 在哪里:

void check_line_c( char* find, char * search ) {
    int max_distance = 2;
    int flen = strlen(find);
    int last_ind = strlen(search) - flen;

    SV *dis = &PL_sv_undef;
    SV *match = &PL_sv_undef;
    for ( int ind = 0; ind <= last_ind; ind++ ) 
    {
        int count = 0;
        for ( int j = 0; j < flen; j++ ) 
        {
            if ( find[j] ^ search[ind+j] ) count++; 
        }
        if ( count < max_distance )
        {
            match = newSV(flen);
            sv_catpvn(match, search+ind, flen );
            dis = newSViv(count);
            break;
        }
    }
    Inline_Stack_Vars;
    Inline_Stack_Reset;
    Inline_Stack_Push(sv_2mortal(dis));
    Inline_Stack_Push(sv_2mortal(match));
    Inline_Stack_Done;
}

输出是(使用 Intel Core i7-4702MQ CPU @2.20GHz 的 Ubuntu 笔记本电脑):

Benchmark: timing 4000000 iterations of c, perl...
         c:  2 wallclock secs ( 0.76 usr +  0.00 sys =  0.76 CPU) @ 5263157.89/s (n=4000000)
      perl: 19 wallclock secs (18.30 usr +  0.00 sys = 18.30 CPU) @ 218579.23/s (n=4000000)

因此,这为这种情况提供了 24 倍的加速。

【讨论】:

  • 感谢内联示例,一个问题——简单地使用这个 (Inline) 来使用 C 是否有很大的缺点/问题?我不知何故认为我必须去 XS 并且还有更多事情要做。
  • @zdim 我不确定,我刚开始用 Perl 编写 C。我认为你是对的,程序员有更多的工作可以直接在 XS 中编写。但是我还没有在 XS 中编写任何东西。但我希望尽快学会它:)
  • 我记得我在寻找如何在 Perl 中使用 C 并且(我认为)找到了 Inline 的警告(并为此感到抱歉,因为它是如此简单)。我也想进入 XS,但还没有开始。
  • @zdim,它在运行时创建文件,因此您可能会遇到权限问题。和所有自行安装的模块一样,它需要一个工作编译器。就是这样。如果您打算分发它,您将制作一个实际的 XS 模块。
  • @ikegami 非常感谢。我不知何故记得有一些警告,很高兴知道它只是这个。 (不过我最终还是得学习 XS。看起来毛茸茸的很有趣。)
【解决方案2】:

我建议创建一个非常糟糕的哈希算法。一些好的、可逆的和低效的东西,比如字符的总和。或者可能是字符表示的唯一值 (1-4) 的总和。

计算目标总和,并计算最大允许方差。也就是说,如果目标是两次换人的比赛,那么最大可能的差异是多少? (4-1 + 4-1 = 6)。

然后,对于目标数据文件中适当长度的文本的每个“窗口”,计算运行分数。 (末尾加一个字符,开头去掉一个字符,更新hash score。)如果某个窗口的score在允许范围内,可以做进一步调查。

您可能希望将其实现为不同的通道。甚至可能作为 shell 管道或脚本中的不同阶段。这个想法是您可能能够并行化部分搜索。 (例如,所有具有相同长度的匹配字符串可以由一个进程搜索,因为哈希窗口是相同的。)

当然,如果您的程序在后期崩溃,您可以保留早期工作,这当然是有益的。您甚至可以在开发结束阶段的同时运行流程的早期部分。

【讨论】:

    猜你喜欢
    • 2016-10-25
    • 2011-03-02
    • 1970-01-01
    • 2017-04-03
    • 2021-10-29
    • 2015-12-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多