【问题标题】:use regular expression to find and replace but only every 3 characters for DNA sequence使用正则表达式查找和替换 DNA 序列的每 3 个字符
【发布时间】:2013-05-11 17:35:11
【问题描述】:

是否可以在 dna 字符串上使用正则表达式进行查找/替换,这样它一次只考虑每 3 个字符(dna 的密码子)。

例如,我希望正则表达式看到这个:
dna="AAACCCTTTGGG"
像这样:
AAA CCC TTT GGG

如果我现在使用正则表达式并且表达式是
Regex.Replace(dna,"ACC","AAA") 它会找到匹配项,但在这种情况下,一次查看 3 个字符将没有匹配项。

这可能吗?

【问题讨论】:

  • 为什么不在你的字符串本身的第三个位置插入一个空格(你显示的方式)然后运行你的正则表达式?
  • 我不会为此使用正则表达式。使用基本操作来实现您正在寻找的逻辑应该不是很难。还可以考虑使用StringBuilder(如果我怀疑这是 C#)来避免必须为每个替换复制并创建一个新字符串(因为 DNA 字符串可能非常大)。
  • 在 Javascript 中,您可以得到一个密码子数组,如下所示:'dna="AAACCCTTTGGG"'.match(/dna="(\w+)"/)[1].match(/\w{1,3}/g),然后您可以将数组连接成一个用空格分隔元素的字符串。

标签: regex replace character


【解决方案1】:

为什么要使用正则表达式?试试这个,这可能更有效地启动:

public string DnaReplaceCodon(string input, string match, string replace) {
  if (match.Length != 3  || replace.Length != 3) 
      throw new ArgumentOutOfRangeException();

  var output = new StringBuilder(input.Length);
  int i = 0;
  while (i + 2 < input.Length) {
    if (input[i] == match[0] && input[i+1] == match[1] && input[i+2] == match[2]) {
      output.Append(replace);
    } else {
      output.Append(input[i]);
      output.Append(input[i]+1);
      output.Append(input[i]+2);
    }

    i += 3;
  }

  // pick up trailing letters.
  while (i < input.Length)   output.Append(input[i]);

  return output.ToString();
}

【讨论】:

  • 谢谢,我也可以试试这个。不过我不确定哪个更快。我从未使用过 StringBuilder,但我会检查一下。
  • 字符串是不可变的;当您从片段构建字符串时,每个片段以及片段的每个附加都需要实例化另一个 String 对象。随着时间的推移,所有这些堆分配和垃圾收集都会累加。在上面的代码中,如果可以接受就地覆盖输入字符串,那么这将比使用 String 的内置索引器的 StringBuilder 更有效。
  • 我也要试试这个解决方案。你是说我可以避免创建新字符串,而是继续替换相同的字符串以避免垃圾收集问题。
  • 是的。 StringBuilder 支持更广泛的操作,但如果您所做的只是就地替换,您可以像这样使用字符串索引器:input[i] = replace[0]; input[i+1] = replace[1];
【解决方案2】:

解决方案

使用正则表达式可以做到这一点。假设输入有效(仅包含ATGC):

Regex.Replace(input, @"\G((?:.{3})*?)" + codon, "$1" + replacement);

DEMO

如果不能保证输入有效,您可以使用正则表达式 ^[ATCG]*$(允许非 3 的倍数)或 ^([ATCG]{3})*$(序列必须是 3 的倍数)进行检查。无论如何,对无效输入进行操作是没有意义的。

说明

上述结构适用于任何密码子。为了便于解释,设密码子为AAA。正则表达式为\G((?:.{3})*?)AAA

整个正则表达式实际上匹配以要替换的密码子结尾的 shortest 子字符串。

\G            # Must be at beginning of the string, or where last match left off
((?:.{3})*?)  # Match any number of codon, lazily. The text is also captured.
AAA           # The codon we want to replace

我们确保匹配只从索引为 3 的倍数的位置开始:

  • \G 断言匹配从上一个匹配停止的地方(或字符串的开头)开始
  • 事实上,模式((?:.{3})*?)AAA 只能匹配长度为 3 的倍数的序列。

由于惰性量词,我们可以确定在每次匹配中,要替换的密码子之前的部分(与((?:.{3})*?)部分匹配)不包含密码子。

在替换中,我们将密码子之前的部分放回(捕获组1中捕获,可以用$1引用),后面是替换密码子。

【讨论】:

  • 哇。证明和解释以及速度测试。惊人的。正则表达式似乎很神奇,因此可能需要一些时间来消化您的解决方案。谢谢。
  • 在尝试进行多次替换时似乎是一个问题。接下来我想用 TTT 代替 TTC,用 CCC 代替 AGT,用 AAA 代替 AAG。我从 AAGTCT 开始,首先用 TTT 替换 TCT,然后它读取 AAGTTT,然后由于某种原因它匹配 AGT 并替换它,即使它不是密码子。那时的密码子是 AAG 和 TTT。
  • 另外我正在使用多个替换,例如 TTG|TTC 将被 TTT 替换。我想知道这是否导致了问题。比如“\G((?:.{3})*?)TTG|TTC”
  • @danielsavage:我再说一遍;这个问题不太适合正则表达式解决方案(即使应该存在)。
  • @PieterGeerkens:我确信正则表达式解决方案可以正常工作。可维护性是另一回事......我认为复杂性应该与您的解决方案相同,减去编译正则表达式部分。
【解决方案3】:

注意

正如评论中所解释的,以下不是一个好的解决方案!我把它留在里面,这样其他人就不会犯同样的错误

您通常可以通过m.start()m.end() 找到比赛的开始和结束位置。如果m.start() % 3 == 0 找到了相关匹配项。

【讨论】:

  • 不,这不是一个好的解决方案。例如,给定CAAAAA,您想找到AAA。引擎将在索引 1 处返回 单个匹配,根据您的方法,这是无效的。虽然实际上在索引 3 处有一个有效匹配项(不是由引擎返回,因为在索引 1 处的匹配项中消耗了 A)。
  • 真的,我没有想到这个!
猜你喜欢
  • 1970-01-01
  • 2021-05-18
  • 1970-01-01
  • 1970-01-01
  • 2016-08-16
  • 1970-01-01
  • 1970-01-01
  • 2015-04-27
  • 1970-01-01
相关资源
最近更新 更多