Подтвердить что ты не робот

Создание нового регулярного выражения на основе возвращаемых результатов и правил предыдущего регулярного выражения | Индексирование регулярного выражения и просмотр того, как регулярное выражение соответствует подстроке

Я особенно смотрю на R, Perl и shell. Но любой другой язык программирования тоже будет хорош.

Вопрос

Есть ли способ визуально или программно проверять и индексировать строку с сопоставлением на основе регулярного выражения? Это предназначено для ссылки на первое регулярное выражение и его результаты внутри второго регулярного выражения, чтобы иметь возможность изменять часть согласованной строки и писать новые правила для этой конкретной части.

https://regex101.com визуализирует, как определенная строка соответствует регулярному выражению. Но он далек от совершенства и не эффективен для моего огромного набора данных.

ПРОБЛЕМА

У меня есть около 12000 согласованных строк (последовательности ДНК) для моего первого регулярного выражения, и я хочу обработать эти строки и на основе некоторых строгих правил найти некоторые другие строки во втором файле, которые хорошо сочетаются с этими 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**}

Здесь правило1 и правило2 зависят от совпадений, исходящих из первого регулярного выражения в первом файле. Следовательно,

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)

Вы можете видеть, что во втором регулярном выражении есть разделы, принадлежащие самому себе (то есть они не зависят от какого-либо другого файла/регулярного выражения), но также имеют разделы, зависящие от результатов первого файла и правил первого регулярного выражения и как каждая подстрока в первом файле совпадает с первым регулярным выражением!

Теперь для простоты я использую третью подобранную подстроку из файла1 (потому что она короче двух других), чтобы показать вам, как выглядит возможное совпадение со вторым файлом и как оно удовлетворяет второму регулярному выражению:

Это то, что мы получили из нашего первого регулярного выражения, запускаемого через первый файл:

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 ( rule1 и rule2 из second.regex).

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

Итак, если у вас есть TAAA, то дополнением будет ATTT.

Поэтому вернемся к этому примеру:

  1. TAACAAGGACCCTGTGTA

Нам нужно дополнить следующее, чтобы удовлетворить требованиям второго регулярного выражения:

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

И дополнения:

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

Итак, пример подстроки, которая соответствует second.regex, такова:

CTAAAACACCTTTGGG TTCCT CTTAAAAAAAAAGGGGGAGAGAGAAGAAAAAAAGAGAG GG

Это только один пример! Но в моем случае у меня есть 12000 подстрочных подстрок!! Я не могу понять, как подойти к этой проблеме. Я пробовал писать чистое регулярное выражение, но я полностью не смог реализовать что-либо, что должным образом следует этой логике. Может быть, я не должен даже использовать регулярное выражение?

Можно ли сделать это полностью с помощью регулярного выражения? Или я должен взглянуть на другой подход? Возможно ли индексировать регулярное выражение и во втором справочнике регулярных выражений назад к первому регулярному выражению и заставить регулярное выражение рассматривать согласованные подстроки, возвращаемые первым регулярным выражением?

4b9b3361

Ответ 1

Это можно сделать программно в Perl или на любом другом языке.

Поскольку вам нужен ввод из двух разных файлов, вы не можете сделать это в чистом регулярном выражении, так как regex не может читать файлы. Вы даже не можете сделать это в одном шаблоне, так как никакой механизм регулярных выражений не запоминает то, что вы сопоставили перед другой строкой ввода. Это должно быть сделано в программе, связанной с вашими совпадениями, что очень хорошо должно быть регулярным выражением, поскольку это означает, для чего предназначено регулярное выражение.

Поэтапно можно построить второй шаблон. Я реализовал более совершенную версию на Perl, которая может быть легко адаптирована к другим комбинациям шаблонов, не изменяя фактический код, который выполняет эту работу.

Вместо файла 1 я буду использовать раздел DATA. Он содержит все три примерные строки ввода. Вместо файла 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 шаблон, который был сконструирован с помощью цитированного оператора регулярных выражений qr//.

Образец соответствует вашему примеру для третьего случая. Полученный @matches2 выглядит так, когда выгружается с помощью Data::Printer.

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

Я не могу сказать ничего о скорости этой реализации, но я считаю, что это будет разумно быстро.

Если вы хотите найти другие комбинации шаблонов, все, что вам нужно было сделать, это заменить записи sub { ... } в этих двух массивах. Если для первого совпадения существует другое число, чем пять, вы также можете создать этот шаблон программно. Я не сделал этого выше, чтобы упростить ситуацию. Вот как это будет выглядеть.

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

Если вы хотите узнать больше об этой стратегии, я предлагаю вам ознакомиться с превосходным Perl, отличным от Per J, который находится в бесплатно, как PDF здесь.

Ответ 2

Использование stringr в R

Извлечь совпадения для 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,12000}AAC([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)

Построить regex_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,12000}AAC([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="") 

Ответ 3

Этот вопрос действительно напоминает старую поговорку о регулярных выражениях, хотя в этом случае языки, которые вы сопоставляете, являются регулярными, поэтому RE подходит для этого.

К сожалению, моего Perl несколько не хватает, но в корне это звучит как проблема с регулярным выражением, а не проблема с R или Perl, поэтому я сделаю все возможное, чтобы ответить на нее на этой основе.

Perl regex engine поддерживает группы захвата. Подстроки, соответствующие подвыражениям в скобках в вашем регулярном выражении, могут быть доступны после сопоставления:

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}/;

Если разделы имеют фактическое значение, я также советую искать имена групп захвата и давать результаты достойные имена, а не $1, $2, $3....

Ответ 4

awk. Требования не так уж сложны: простой script может сделать трюк. Там только одно осложнение: каждое регулярное выражение, являющееся результатом первого совпадения, должно быть сопоставлено с строками 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
}

Что это будет делать, так это создать регулярное выражение для вашего второго файла. (Я добавил дополнительную группу для демонстрационных целей). Теперь давайте посмотрим на второй script:

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

Что это будет делать, так это получить regex и сопоставить его с каждой строкой, считанной из второго входного файла. Мы должны предоставить этому script значение для regex, например:

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

Опция -v awk позволяет определить регулярное выражение, которое подается в этот вызов с помощью первого script.

$ 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}, замедлят вас, независимо от того, какой язык вы выбрали.