perl 中的高效子串匹配

Posted

技术标签:

【中文标题】perl 中的高效子串匹配【英文标题】:Efficient substring matching in perl 【发布时间】:2011-09-09 16:15:10 【问题描述】:

我正在寻找一种有效的解决方案来在字符串中找到最长的子字符串,以容忍主字符串中的 n 个不匹配

例如: 主字符串

    AGACGTACTACTCTACTAGATGCA*TACTCTAC* AGACGTACTACTCTACTAGATGCA*TACTCTAC* AGACGTACTACTCTACAAGATGCA*TACTCTAC* AGACGTACTACTTTACAAGATGCA*TACTCTAC*

搜索字符串:

    TACTCTACT :这应该被视为与上述所有主要字符串的匹配项。

我也可能存在子字符串的一部分位于主字符串末尾的情况,我也想把它捡起来。

如果您能指点一下,我将不胜感激。

PS:我将有一个搜索字符串和大约 1 亿个主字符串来搜索子字符串。

谢谢! -阿比

【问题讨论】:

问题。 1)您在“主字符串”中的数字 - 是一个“主字符串”中的那些单独的行,还是四个单独的“主字符串”(我假设后者,但想确认)? 2) 你能解释为什么那个搜索字符串会被认为是#3 和#4 的匹配项吗?前两个显然匹配,但我不清楚为什么后两个匹配,这听起来可能是一个需要理解的关键细节。 3)你能解释一下子字符串的一部分可能在主字符串的末尾是什么意思吗? 您提到了“最长”,但您的示例并未提供任何线索来说明最长的含义。你的意思是最少的不匹配? @Brian Gerard:1)这些是百万主字符串中的 4 个。 2)因为他容忍不匹配。 #3:A 代表 T,#4,T 代表 C,A 代表 T。3) "...TACT" 应该与搜索字符串 TACTCTACT 匹配,因为它后面可能跟有 "CTACT"。跨度> @Brian : 1.) 我展示了 4 种不同类型的主要字符串作为示例。 2.) 我允许 n 不匹配。在这种情况下,n = 1,2 等 3.) 搜索字符串可能部分包含在主字符串中,我们也希望匹配该部分情况。感谢您的提问 @ikegami :最长我的意思是字面上的“最长”和最好的匹配(最少的不匹配)。在某种程度上,这可能会产生误导,感谢您的提问。所以最长可能不是正确的词。我的意思是如果子字符串部分包含在主字符串的末尾,我想选择这种情况而不是忽略它。我希望这能让它更清楚一点。 【参考方案1】:

我不确定这就是你所追求的,但 BioPerl 有一个名为 Bio::Grep::Backend::Agrep 的近似 grep 工具:

Bio::Grep::Backend::Agrep 使用 agrep 搜索查询

agrep 是“近似 grep”。 AFAIK,这会构建一个数据库,然后使用该数据库来加快您的搜索速度,所以这听起来就像您所追求的那样。

看起来您正在处理 DNA 序列,因此您可能已经安装了 BioPerl。

您也可以尝试直接使用String::Approx

Perl 扩展用于近似匹配(模糊匹配)

我怀疑Bio::Grep::Backend::Agrep 会更快,更适合您的任务。

【讨论】:

我会试试这个,让你们知道它有效且有效。【参考方案2】:
use strict;
use warnings;
use feature qw( say );

sub match 
   my ($s, $t, $max_x) = @_;

   my $m = my @s = unpack('(a)*', $s);
   my $n = my @t = unpack('(a)*', $t);

   my @length_at_k     = ( 0 ) x ($m+$n);
   my @mismatches_at_k = ( 0 ) x ($m+$n);
   my $offset = $m;

   my $best_length = 0;
   my @solutions;
   for my $i (0..$m-1) 
      --$offset;

      for my $j (0..$n-1) 
         my $k = $j + $offset;

         if ($s[$i] eq $t[$j]) 
            ++$length_at_k[$k];
         
         elsif ($length_at_k[$k] > 0 && $mismatches_at_k[$k] < $max_x) 
            ++$length_at_k[$k];
            ++$mismatches_at_k[$k];
         
         else 
            $length_at_k[$k] = 0;
            $mismatches_at_k[$k] = 0;
         

         my $length = $length_at_k[$k] + $max_x - $mismatches_at_k[$k];
         $length = $i+1 if $length > $i+1;

         if ($length >= $best_length) 
            if ($length > $best_length) 
               $best_length = $length;
               @solutions = ();
            

            push @solutions, $i-$length+1;
         
      
   

   return map  substr($s, $_, $best_length)  @solutions;


say for match('AABBCC', 'DDBBEE', 2);

输出:

AABB
ABBC
BBCC

【讨论】:

你的代码工作得很好。只是想知道我在一定时间内检查的序列数量是否可行,或者是否有办法扩大规模。我正在 9000 万个 100 个字母长的搜索序列中搜索 36 个字母子串(最多 5 个不匹配)。我花了大约 13 个小时来做​​这个搜索。根据您的经验,这听起来是否正常。我没有时间/内存分析我的代码。谢谢! @Abhi,我不是算法专家,但我采取了我能想到的每一条捷径。您可以通过在 C 中实现它来加快一两个订单。 @Mariya,它基于最长公共子串问题的“动态规划”解决方案。查看基于 here 的解释。

以上是关于perl 中的高效子串匹配的主要内容,如果未能解决你的问题,请参考以下文章

Perl Goatse 'Secret Operator' 高效吗?

Perl模式匹配大型连载1--初识正则

Perl 使用perl命令批量替换文件内容

高效 pre-perl-5.10 等效于 pack("Q>")

[Perl] 内置特殊变量

Perl入门Perl的正則表達式