根据返回的结果和前一个正则表达式的规则创建一个新的正则表达式 |索引正则表达式并查看正则表达式如何匹配子字符串

Posted

技术标签:

【中文标题】根据返回的结果和前一个正则表达式的规则创建一个新的正则表达式 |索引正则表达式并查看正则表达式如何匹配子字符串【英文标题】:Creating a new regex based on the returned results and rules of a previous regex | Indexing a regex and seeing how the regex has matched a substring 【发布时间】:2018-02-19 11:04:32 【问题描述】:

我特别关注 R、Perl 和 shell。但是任何其他编程语言也可以。

问题

有没有一种方法可以根据正则表达式直观地或以编程方式检查和索引匹配的字符串?这是为了在第二个正则表达式中引用回第一个正则表达式及其结果,以便能够修改匹配字符串的一部分并为该特定部分编写新规则。

https://regex101.com 确实可视化某个字符串如何匹配正则表达式。但它远非完美,对我庞大的数据集效率不高。

问题

我的第一个正则表达式有大约 12000 个匹配的字符串(DNA 序列),我想处理这些字符串,并根据一些严格的规则在第二个文件中找到与这 12000 个匹配项配合得很好的其他字符串严格的规定。

简化示例

这是贯穿我的第一个文本文件第一个正则表达式(我的原始正则表达式的简化、较短版本)。

[ACGT]1,12000(AAC)[AG]2,5[ACGT]2,5(CTGTGTA)

假设它在我的大文本文件中找到以下三个子字符串:

1. AAACCCGTGTAATAACAGACGTACTGTGTA
2. TTTTTTTGCGACCGAGAAACGGTTCTGTGTA
3. TAACAAGGACCCTGTGTA

现在我有一个 第二个文件,其中包含一个非常大的字符串。从这第二个文件中,我只对提取与新的(第二个)正则表达式匹配的那些子字符串感兴趣,该正则表达式本身依赖于我在几个部分中的第一个正则表达式因此,第二个正则表达式必须考虑到第一个文件中匹配的子字符串,并查看它们如何与第一个正则表达式匹配!

为了简单起见,请允许我用这种方式索引我的第一个正则表达式以便更好地说明:

first.regex.p1 = [ACGT]1,12000
first.regex.p2 = (AAC)
first.regex.p3 = [AG]2,5
first.regex.p4 = [ACGT]2,5
first.regex.p5 = (CTGTGTA)

现在我的 第二个(新)正则表达式将搜索 第二个文本文件,并将取决于第一个正则表达式的结果(以及子字符串如何从第一个文件与第一个正则表达式匹配)将按以下方式定义:

second.regex = (CTAAA)[AC]5,100(TTTGGG)**rule1** (CTT)[AG]10,5000**rule2**

这里 rule1rule2 依赖于来自第一个文件的第一个正则表达式的匹配。因此;

rule1 = look at the matched strings from file1 and complement the pattern of first.regex.p3 that is found in the matched substring from file1 (the complement should of course have the same length)
rule2 = look at the matched strings from file1 and complement the pattern of first.regex.p4 that is found in the matched substring from file1 (the complement should of course have the same length)

您可以看到第二个正则表达式有属于自己的部分(即它们独立于任何其他文件/正则表达式),但它也有部分依赖于第一个文件的结果和第一个正则表达式的规则以及第一个文件中的每个子字符串如何匹配第一个正则表达式!

现在再次为简单起见,我使用 file1 中的第三个匹配子字符串(因为它比其他两个短)来向您展示第二个文件中可能的匹配项的外观以及它如何满足第二个正则表达式:

这是我们从第一个正则表达式运行到第一个文件时得到的结果:

3. TAACAAGGACCCTGTGTA

所以在这场比赛中,我们看到:

T has matched first.regex.p1
AAC has matched first.regex.p2
AAGGA has matched first.regex.p3
CC first.regex.p4
CTGTGTA has matched first.regex.p5

现在在第二个文件的第二个正则表达式中,我们看到在寻找与第二个正则表达式匹配的子字符串时,我们依赖于来自第一个文件的结果(与第一个正则表达式匹配)。特别是我们需要查看匹配的子字符串并补充匹配 first.regex.p3 和 first.regex.p4 的部分(来自 second.regex 的 rule1rule2)。

complement means:
A will be substituted by T
T -> A
G -> C
C -> G

因此,如果您有 TAAA,则补码将是 ATTT。

因此,回到这个例子:

    TAACAAGGACCCTGTGTA

我们需要补充以下内容以满足第二个正则表达式的要求:

AAGGA has matched first.regex.p3
CC first.regex.p4

补语是:

TTCCT (based on rule1)
GG (based on rule2)

因此,匹配 second.regex 的子字符串示例如下:

CTAAAACACCTTTGGGTTCCTCTTAAAAAAAAAGGGGGAGAGAGAAGAAAAAAAGAGAGGG

这只是一个例子!但就我而言,我有 12000 个匹配的子字符串!我什至无法弄清楚如何解决这个问题。我曾尝试编写纯正则表达式,但我完全未能实现任何正确遵循此逻辑的东西。也许我什至不应该使用正则表达式?

是否可以完全使用正则表达式来做到这一点?还是我应该看看另一种方法?是否可以索引一个正则表达式并在第二个正则表达式引用回第一个正则表达式并强制正则表达式考虑第一个正则表达式返回的匹配子字符串?

【问题讨论】:

问题中有一点不清楚。假设您将在 first.regex.p3 中匹配 AAGGA,那么第二个文件中的对应匹配应该是什么:正好是 'TTCCT' 或 [CT]2,5?甚至 [CT]5? @MarcLambrichs 很抱歉我的文字令人费解且令人困惑。在尝试简化原始正则表达式和结果时,我也迷失在自己的文本中。回答你的问题:是的!它应该完全是TTCCT,因为第二个文件中正则表达式的特定依赖部分的匹配长度应该与第一个文件中的匹配长度完全相同。另一个例子:如果first.regex.p4匹配第一个文件中的GGGG某个子字符串,那么第二个文件(第二个正则表达式)中对应的匹配肯定应该是CCCC 这是因为它们本质上是两条 DNA 链,在某些时候它们部分地形成了一个碱基对(它们聚在一起并相互粘连)。所以第二个文件中的 TTCCT 坚持第一个文件中的 AAGGA。 michaelwosnick.com/CancerResearch/wp-content/uploads/… 【参考方案1】:

在 R 中使用 stringr

将匹配项提取到 regex_1:"[ACGT]1,12000(AAC)[AG]2,5[ACGT]2,5(CTGTGTA)"

reg_1_matches = stringr::str_extract_all(sequences, "[ACGT]1,12000(AAC)[AG]2,5[ACGT]2,5(CTGTGTA)")
reg_1_matches = unlist(reg_1_matches)

假设匹配是:

 reg_1_matches = c("TTTTTTTGCGACCGAGAAACGGTTCTGTGTA", "TAACAAGGACCCTGTGTA")

将 stringr::str_match 与捕获组一起使用 (...)

df_ps = stringr::str_match(reg_1_matches, "[ACGT]1,12000AAC([AG]2,5)([ACGT]2,5)CTGTGTA")

p3 = df_ps[,2]
p4 = df_ps[,3]

补充

rule_1 = chartr(old= "ACGT", "TGCA", p3)
rule_2 = chartr(old= "ACGT", "TGCA", p4)

构造正则表达式_2

  paste("(CTAAA)[AC]5,100(TTTGGG)", rule_1, "(CTT)[AG]10,5000", rule_2, sep="") 

一气呵成:

reg_1_matches =  stringr::str_extract_all(sequences, "[ACGT]1,12000(AAC)[AG]2,5[ACGT]2,5(CTGTGTA)")
df_ps = stringr::str_match(reg_1_matches, "[ACGT]1,12000AAC([AG]2,5)([ACGT]2,5)CTGTGTA")
p3 = df_ps[,2]
p4 = df_ps[,3]
rule_1 = chartr(old= "ACGT", "TGCA", p3)
rule_2 = chartr(old= "ACGT", "TGCA", p4)
paste("(CTAAA)[AC]5,100(TTTGGG)", rule_1, "(CTT)[AG]10,5000", rule_2, sep="") 

【讨论】:

太棒了!我真的很喜欢这个答案,它很简单,是一个非常聪明的实现。我也一直主要使用 stringi/stringr 来解决这个问题,因为我发现它非常有效,而且我需要的许多包都是为 R 找到的,这减少了麻烦。我已经尝试过您的解决方案,它确实是一个智能的解决方案,但我似乎无法理解为什么df_ps = stringr::str_match 部分只能为reg_1_matches 中的第一个匹配字符串生成结果。它为所有其他找到的字符串返回 NA 。你知道为什么会这样吗? pastebin.com/XS7wB4bc stringr::str_extract_all 产生一个列表,在答案中我假设它产生一个向量。在我的简单示例中,需要执行reg_1_matches = unlist(reg_1_matches) 并继续执行stringr::str_extract_all。我正在下载 BSgenome.Hsapiens.UCSC.hg38_1.4 来检查它,但它是一个 700 MB 的文件。我会在测试时编辑评论。【参考方案2】:

awk 解决方案。 要求并不复杂:一个简单的脚本就可以解决问题。只有一个复杂之处:第一个匹配结果的每个正则表达式都必须与第二个文件的 all 行匹配。这是我们使用 xargs 来解决这个问题的地方。

现在,无论您选择哪种语言,看起来匹配的数量都会非常多,因此需要先对正则表达式进行一些说明。

第一个文件的正则表达式会很慢,因为在

[ACGT]1,12000(AAC)[AG]2,5[ACGT]2,5(CTGTGTA)

第一部分[AGCT]1,12000 的可能性非常大。实际上,它只说从 A、C、G、T 中选择任何元素以及 1 到 12000 次之间的元素。然后匹配其余部分。我们不能做一个

AAC([AG]2,5)([ACGT]2,5)CTGTGTA$

相反?速度增益相当可观。

可以对第二个文件的正则表达式进行类似的说明。如果你更换

 (CTAAA)[AC]5,100(TTTGGG)**rule1**(CTT)[AG]10,5000**rule2**

(CTAAA)[AC]5,100(TTTGGG)**rule1**(CTT)[AG]***rule2**$

你会体验到一些进步。

因为我以要求的低复杂性开始这个答案,所以让我们看一些代码:

$ cat tst.awk
match($0, /AAC([AG]2,5)([ACGT]2,5)CTGTGTA$/, a) 
   r = sprintf("(CTAAA)[AC]5,100(TTTGGG)(%s)(CTT)[AG]*(%s)$", 
               translate(a[1]), 
               translate(a[2]));
   print r


function translate(word) 
   cmd = "echo '" word "' | tr 'ACGT' 'TGCA'";
   res = ((cmd | getline line) > 0 ? line : "");
   close(cmd);
   return res

这将为您的第二个文件生成正则表达式。 (出于演示目的,我添加了额外的分组)。现在,让我们看一下第二个脚本:

$ cat tst2.awk
match($0, regex, a) printf("Found matches %s and %s\n", a[3], a[5]) 

这将得到一个regex 并将其与从第二个输入文件中读取的每一行进行匹配。我们需要为该脚本提供regex 的值,如下所示:

$ awk -f tst.awk input1.txt | xargs -I  -n 1 awk -v regex= -f tst2.awk input2.txt

awk 的 -v 选项让我们定义一个正则表达式,它被第一个脚本输入到这个调用中。

$ cat input1.txt
TAACAAGGACCCTGTGTA

$ cat input2.txt
CTAAAACACCTTTGGGTTCCTCTTAAAAAAAAAGGGGGAGAGAGAAGAAAAAAAGAGAGGG

结果是:

$ awk -f tst.awk input1.txt | xargs -I  -n 1 awk -v regex= -f tst2.awk input2.txt
Found matches TTCCT and GG

总结:您应该使用正则表达式来解决您的问题吗?是的,但是你不需要太雄心勃勃地一次匹配整个字符串。无论您选择何种语言,像 1,12000 这样的量词都会减慢您的速度。

【讨论】:

【参考方案3】:

这可以用 Perl 或任何其他语言以编程方式完成。

由于您需要来自两个不同文件的输入,因此您不能在纯正则表达式中执行此操作,因为正则表达式无法读取文件。您甚至无法以一种模式执行此操作,因为没有正则表达式引擎会记住您之前在不同输入字符串上匹配的内容。它必须在您的比赛周围的程序中完成,这应该是正则表达式,因为这就是正则表达式的含义。

您可以逐步构建第二个模式。我在 Perl 中实现了一个更高级的版本,它可以很容易地适应其他模式组合,而无需更改执行工作的实际代码。

我将使用DATA 部分而不是文件 1。它包含所有三个示例输入字符串。我将您的示例输出用于第三个输入字符串,而不是文件 2。

这背后的主要思想是将两种模式分成子模式。对于第一个,我们可以简单地使用一组模式。对于第二个,我们创建匿名函数,我们将调用第一个模式的匹配结果来构造第二个完整模式。它们中的大多数只返回一个固定的字符串,但实际上有两个从参数中获取一个值来构建补码。

use strict;
use warnings;

sub complement 
    my $string = shift;
    $string =~ tr/ATGC/TACG/; # this is a transliteration, faster than s///
    return $string;


# first regex, split into sub-patterns
my @first = ( 
    qr([ACGT]1,12000), 
    qr(AAC), 
    qr([AG]2,5), 
    qr([ACGT]2,5), 
    qr(CTGTGTA), 
);

# second regex, split into sub-patterns as callbacks
my @second = (
    sub  return qr(CTAAA) ,
    sub  return qr([AC]5,100) ,
    sub  return qr(TTTGGG) ,
    sub 
        my (@matches) = @_;

        # complement the pattern of first.regex.p3
        return complement( $matches[3] );
    ,
    sub  return qr(CTT) ,
    sub  return qr([AG]10,5000) ,
    sub 
        my (@matches) = @_;

        # complement the pattern of first.regex.p4
        return complement( $matches[4] );
    ,
);

my $file2 = "CTAAAACACCTTTGGGTTCCTCTTAAAAAAAAAGGGGGAGAGAGAAGAAAAAAAGAGAGGG";

while ( my $file1 = <DATA> ) 

    # this pattern will match the full thing in $1, and each sub-section in $2, $3, ...
    # @matches will contain (full, $2, $3, $4, $5, $6)
    my @matches = ( $file1 =~ m/(($first[0])($first[1])($first[2])($first[3])($first[4]))/g );

    # iterate the list of anonymous functions and call each of them,
    # passing in the match results of the first match
    my $pattern2 = join q, map  '(' . $_->(@matches) . ')'  @second;

    my @matches2 = ( $file2 =~ m/($pattern2)/ );


__DATA__
AAACCCGTGTAATAACAGACGTACTGTGTA
TTTTTTTGCGACCGAGAAACGGTTCTGTGTA
TAACAAGGACCCTGTGTA

这些是为您的三个输入子字符串生成的第二个模式。

((?^:CTAAA))((?^:[AC]5,100))((?^:TTTGGG))(TCT)((?^:CTT))((?^:[AG]10,5000))(GCAT)
((?^:CTAAA))((?^:[AC]5,100))((?^:TTTGGG))(CC)((?^:CTT))((?^:[AG]10,5000))(AA)
((?^:CTAAA))((?^:[AC]5,100))((?^:TTTGGG))(TTCCT)((?^:CTT))((?^:[AG]10,5000))(GG)

如果您对此不熟悉,如果您 print 使用 quoted regex operator qr// 构造的模式会发生这种情况。

该模式与您在第三种情况下的示例输出相匹配。使用 Data::Printer 转储时,生成的 @matches2 看起来像这样。

[
    [0] "CTAAAACACCTTTGGGTTCCTCTTAAAAAAAAAGGGGGAGAGAGAAGAAAAAAAGAGAGGG",
    [1] "CTAAA",
    [2] "ACACC",
    [3] "TTTGGG",
    [4] "TTCCT",
    [5] "CTT",
    [6] "AAAAAAAAAGGGGGAGAGAGAAGAAAAAAAGAGAG",
    [7] "GG"
]

我不能说这个实现的速度,但我相信它会很快。

如果您想找到其他模式组合,您只需替换这两个数组中的 sub ... 条目即可。如果第一个匹配项的数量与其中五个不同,您还可以通过编程方式构造该模式。我没有在上面这样做以使事情变得更简单。这是它的样子。

my @matches = ( $file1 =~ join q, map  "($_)"  @first);

如果你想进一步了解这种策略,我建议你阅读 Mark Jason Dominus 的优秀Higher Order Perl,即available for free as a PDF here。

【讨论】:

【参考方案4】:

这个问题确实让我想起了the old saying about regular expressions,虽然在这种情况下你匹配的语言是常规的,所以 RE 非常适合这个。

不幸的是,我的 Perl 有点欠缺,但从根本上讲,这听起来像是 Regex 问题,而不是 R 或 Perl 问题,所以我会在此基础上尽力回答。

Perl 的正则表达式引擎支持捕获组。匹配正则表达式中括号子表达式的子字符串可以在匹配后提供:

use feature qw(say);

$foo = 'foo';
'aaa' =~ /(a)(a+)/;
say($1); # => 'a'
say($2); # => 'aa'
say("Matched!") if 'aaaa' =~ /$2/;

我建议做的是将您的正则表达式正确括起来,在匹配后分离捕获组,然后将它们组合成一个新的正则表达式,比如说...

use feature qw(say);

'ACGTAACAGAGATCTGTGTA' =~ /([ACGT]1,12000)(AAC)([AG]2,5)([ACGT]2,5)(CTGTGTA)/ ; # Note that I've added a lot of (s and )s here so that the results get sorted into nice groups
say($1); # => 'ACGT'
say($2); # => 'AAC'
say($3); # => 'AGAG'
say($4); # => 'AT'
say($5); # => 'CTGTGTA'

$complemented_3 = complement($3); # You can probably implement these yourself...
$complemented_4 = complement($4);

$new_regex = /$complemented_3[ACGT]+$complemented_4/;

如果这些部分具有实际意义,那么我还建议查找named capture groups,并为结果命名而不是$1, $2, $3...

【讨论】:

我很确定如果 Damian Conway 遇到这个问题,他会写一个很长的正则表达式来解决它。 :)

以上是关于根据返回的结果和前一个正则表达式的规则创建一个新的正则表达式 |索引正则表达式并查看正则表达式如何匹配子字符串的主要内容,如果未能解决你的问题,请参考以下文章

第九章 正则表达式

js基础(正则表达式)

Nginx URL重写规则配置详解

JavaScript学习手册(43)

正则表达式“或“的使用

iOS--正则表达式