R: быстрый способ извлечь все подстроки, содержащиеся между двумя подстроками

Я ищу эффективный способ извлечь все совпадения между двумя подстроками в символьной строке. Например. Я хочу извлечь все подстроки, содержащиеся между строкой

start="strt"

и

stop="stp"
in string
x="strt111stpblablastrt222stp"

Я хотел бы получить вектор

"111" "222"

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

Ответ 1

Для чего-то такого простого база R отлично справляется с этим.

Вы можете включить PCRE с помощью perl=T и использовать lookaround утверждения.

x <- 'strt111stpblablastrt222stp'
regmatches(x, gregexpr('(?<=strt).*?(?=stp)', x, perl=T))[[1]]
# [1] "111" "222"

Объяснение

(?<=          # look behind to see if there is:
  strt        #   'strt'
)             # end of look-behind
.*?           # any character except \n (0 or more times)
(?=           # look ahead to see if there is:
  stp         #   'stp'
)             # end of look-ahead

EDIT: Обновлено ниже в соответствии с новым синтаксисом.

Вы также можете использовать пакет stringi.

library(stringi)
x <- 'strt111stpblablastrt222stp'
stri_extract_all_regex(x, '(?<=strt).*?(?=stp)')[[1]]
# [1] "111" "222"

И rm_between из пакета qdapRegex.

library(qdapRegex)
x <- 'strt111stpblablastrt222stp'
rm_between(x, 'strt', 'stp', extract=TRUE)[[1]]
# [1] "111" "222"

Ответ 2

Вы также можете рассмотреть:

library(qdap)
unname(genXtract(x, "strt", "stp"))
#[1] "111" "222"

Сравнение скорости

 x1 <- rep(x,1e5)
 system.time(res1 <- regmatches(x1,gregexpr('(?<=strt).*?(?=stp)',x1,perl=T)))
 #   user  system elapsed 
 #  2.187   0.000   2.015 

 system.time(res2 <- regmatches(x1, gregexpr("(?<=strt)(?:(?!stp).)*", x1, perl=TRUE)))
 #user  system elapsed 
 #  1.902   0.000   1.780 

 system.time(res3 <- str_extract_all(x1, perl('(?<=strt).*?(?=stp)')))
 # user  system elapsed 
 #  6.990   0.000   6.636 

 system.time(res4 <- genXtract(x1, "strt", "stp")) ##setNames(genXtract(...), NULL) is a bit slower
 # user  system elapsed 
 # 1.457   0.000   1.414 

 names(res4) <- NULL
identical(res1,res4)
#[1] TRUE

Ответ 3

Если вы говорите о скорости в R-строках, для этого есть только один пакет - stringi

 x <- "strt111stpblablastrt222stp"
 hwnd <- function(x1) regmatches(x1,gregexpr('(?<=strt).*?(?=stp)',x1,perl=T))
 Tim <- function(x1) regmatches(x1, gregexpr("(?<=strt)(?:(?!stp).)*", x1, perl=TRUE))
 stringr <- function(x1) str_extract_all(x1, perl('(?<=strt).*?(?=stp)'))
 akrun <- function(x1) genXtract(x1, "strt", "stp")
 stringi <- function(x1) stri_extract_all_regex(x1, perl('(?<=strt).*?(?=stp)'))

 require(microbenchmark)
 microbenchmark(stringi(x), hwnd(x), Tim(x), stringr(x))
Unit: microseconds
       expr     min       lq  median       uq     max neval
 stringi(x)  46.778  58.1030  64.017  67.3485 123.398   100
    hwnd(x)  61.498  73.1095  79.084  85.5190 111.757   100
     Tim(x)  60.243  74.6830  80.755  86.3370 102.678   100
 stringr(x) 236.081 261.9425 272.115 279.6750 440.036   100

К сожалению, я не смог протестировать решение @akrun, потому что у пакета qdap есть некоторые ошибки во время установки. И только его решение выглядит как тот, который может бить струн...

Ответ 4

Поскольку на вход может быть несколько строк начала/остановки, я думаю, что наиболее эффективным решением будет регулярное выражение:

(?<=strt)(?:(?!stp).)*

будет соответствовать всем значениям после strt до конца строки или stp, в зависимости от того, что наступит раньше. Если вы хотите утверждать, что всегда есть stp, добавьте (?=stp) в конец регулярного выражения. Вы можете применить это регулярное выражение к вектору.

regmatches(subject, gregexpr("(?<=strt)(?:(?!stp).)*", subject, perl=TRUE));