regexp:用匹配的模式替换它的长度

时间:2013-01-24 23:42:28

标签: regex r

假设我有一个这样的字符串:

> x <- c("16^TG40")

我正在尝试将结果c(16 2 40)设为2 length(^TG)-1。我能够找到这种模式,例如:

> gsub("(\\^[ACGT]+)", " \\1 ", x)
[1] "16 ^TG 40"

但是,我无法直接用length-1替换此字符串。是否有更简单的方法用长度替换匹配的模式?

经过相当多的搜索(这里是SO和谷歌搜索),我最终得到了stringr包,我觉得这很棒。但是,这一切都归结为找到这个模式的位置(使用str_locate_all),然后用你想要的任何值替换子字符串(使用str_sub)。我有超过100,000个字符串,这非常耗时(因为模式也可能在字符串中多次出现)。

我现在并行运行以弥补缓慢,但我很高兴知道这是否可能直接(或快速)。

有什么想法吗?

3 个答案:

答案 0 :(得分:8)

(1)gsubfn gsubfn语句将^ ...部分替换为空格包围的长度,strapply从该字符串中抽出数字并转换他们是数字的。如果字符输出足够,则省略strapply

> library(gsubfn)
> xx <- gsubfn("\\^[ACGT]*", ~ sprintf(" %s ", nchar(x) - 1), x)
> strapply(xx, "\\d+", as.numeric)
[[1]]
[1] 16  2 40

(2)循环通过一组长度

这假设每个ACGT序列中的字符数在mn和mx之间,它只是用循环中的gsub继续替换i长度的ACGT序列。如果只有几个可能的长度,那么只会有几次迭代,所以它会很快但是如果字符串可能有很多不同的长度,那么它将会很慢,因为需要更多的迭代迭代。下面我们假设ACGT序列长2,4或6,但这些可能需要调整。该解决方案的可能缺点是需要假设一组可能的序列长度。

x <- "4^CG5^CAGT656"

mn <- 2
mx <- 6
y <- x
for(i in seq(mn, mx, 2)) {
   pat <- sprintf("\\^[ACGT]{%d}(\\d)", i)
   replacement <- sprintf(" %d \\1", i)
   y <- gsub(pat, replacement, y)
}

(3)循环ACGT序列

这个循环通过ACGT序列替换其长度直到没有留下。如果存在少量的ACGT序列,则它可以是快速的,因为将发生很少的迭代但是如果可能存在许多ACGT序列,则由于较大的迭代次数将会很慢。

x <- "4^CG5^CAGT656"
y <- x
while(regexpr("^", y, fixed = TRUE) > 0) {
    y <- sprintf("%s %d %s", sub("\\^.*", "", y),
        nchar(sub("^[0-9 ]+\\^([ACGT]+).*", "\\1", y)),
        sub("^[0-9 ]+\\^[ACGT]+", "", y))
}

<强>基准

这是一个基准。请注意,在某些解决方案中,我将字符串转换为数字(当然需要额外的时间),但为了使基准测试可比,我比较了创建字符串的速度而没有任何数字转换。

x <- "4^CGT5^CCA656"
library(rbenchmark)
benchmark(order = "relative", replications = 10000,
   columns = c("test", "replications", "relative", "elapsed"),
   regmatch = {
      pat <- "(\\^[ACGT]+)"
      x2 <- x
      m <- gregexpr(pat, x2)
      regmatches(x2, m) <- sapply(regmatches(x2, m), modFun)
      x2
   },
   gsubfn = gsubfn("\\^[ACGT]*", ~ sprintf(" %s ", length(x) - 1), x),
   loop.on.len = {
    mn <- 2
    mx <- 6
    y <- x
    for(i in seq(mn, mx, 2)) {
       pat <- sprintf("\\^[ACGT]{%d}(\\d)", i)
       replacement <- sprintf(" %d \\1", i)
       y <- gsub(pat, replacement, y)
    }
   },
   loop.on.seq = {
    y <- x
    while(regexpr("^", y, fixed = TRUE) > 0) {
        y <- sprintf("%s %d %s", sub("\\^.*", "", y),
            nchar(sub("^[0-9 ]+\\^([ACGT]+).*", "\\1", y)),
            sub("^[0-9 ]+\\^[ACGT]+", "", y))
    }
  }
)

结果如下所示。两个循环解决方案在所示输入上是最快的,但它们的性能将根据需要多少次迭代而变化,因此实际数据可能会有所不同。 loop.on.len解决方案的缺点是ACGT长度必须在假定集合中。来自Josh的regmatch解决方案不涉及循环而且速度很快。 gsubfn解决方案的优势在于它只有一行代码并且特别直接。

         test replications relative elapsed
4 loop.on.seq        10000    1.000    1.93
3 loop.on.len        10000    1.140    2.20
1    regmatch        10000    1.803    3.48
2      gsubfn        10000    7.145   13.79

更新已添加两个循环解决方案,并删除了之前部分帖子中不处理多个ACGT序列的解决方案(基于澄清问题的评论)。还重新做了基准测试,包括只处理多个ACGT序列的解决方案。

更新删除了一个无法使用多个^ ...序列的解决方案。它先前已从基准测试中删除,但代码尚未删除。改进了(1)中的解释。

答案 1 :(得分:8)

这是一个基础R方法。

语法远非直观,但通过密切关注此模板,您可以执行各种操作和替换匹配的子串。 (有关更复杂的示例,请参阅?gregexpr。)

x2 <- x <- c("16^TG40", "16^TGCT40", "16^TG40^GATTACA40")

pat <- "(\\^[ACGT]+)"              ## A pattern matching substrings of interest
modFun <- function(ss) {           ## A function to modify them
    paste0(" ", nchar(ss) - 1, " ")
}

## Use regmatches() <- regmatches(gregexpr()) to search, modify, and replace.
m <- gregexpr(pat, x2)
regmatches(x2, m) <- sapply(regmatches(x2, m), modFun)
x2
## [1] "16 2 40"      "16 4 40"      "16 2 40 7 40"

答案 2 :(得分:2)

我正在投票支持令人难以置信的gsubfn答案,但因为我已经有了这个笨重的代码:

mod <- gsub("(\\^[ACGT]+)", " \\1 ", x)
locs <- gregexpr(" ", mod , fixed=TRUE)[[1]]
paste( substr( x, 1, locs[1]-1), 
       diff(locs)-2, 
       substr(mod, locs[2]+1, nchar(mod) ) , sep=" ")
#[1] "16 2 40"