Эффективно разбить строку на основе n-го вхождения подстроки, используя R

Вступление

Учитывая строку в R, возможно ли получить векторизованное решение (т.е. без циклов), где мы можем разбить строку на блоки, где каждый блок определяется n-м появлением подстроки в строке.

Работа сделана на воспроизводимом примере

Предположим, у нас есть несколько абзацев знаменитого текста Lorem Ipsum.

library(strex)
# devtools::install_github("aakosm/lipsum")
library(lipsum)

my.string = capture.output(lipsum(5))
my.string = paste(my.string, collapse = " ")

> my.string # (partial output)
# [1] "Lorem ipsum dolor ... id est laborum. "

Мы хотели бы разбить этот текст на сегменты при каждом третьем появлении слова "в" (пробел включен для того, чтобы отличать слова, которые содержат "в" как часть их, например, "мин").

У меня есть следующее решение с циклом while:

# We wish to break up the string at every 
# 3rd occurence of the worn "in"

break.character = " in"
break.occurrence = 3
string.list = list()
i = 1

# initialize string to send into the loop
current.string = my.string

while(length(current.string) > 0){

  # Enter segment into the list which occurs BEFORE nth occurence character of interest
  string.list[[i]] = str_before_nth(current.string, break.character, break.occurrence)

  # Update next string to exmine.
  # Next string to examine is current string AFTER nth occurence of character of interest
  current.string = str_after_nth(current.string, break.character, break.occurrence)

  i = i + 1
}

Мы можем получить желаемый результат в списке с предупреждением (предупреждение не отображается)

> string.list (#partial output shown)
[[1]]
[1] "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit"

[[2]]
[1] " voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.  Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor"
...

[[6]]
[1] " voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.  Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor"

Цель

Можно ли улучшить это решение путем векторизации (т.е. с помощью apply(), lapply(), mapply() так далее.). Кроме того, мое текущее решение обрезает последнее вхождение подстроки в блоке.

Текущее решение может не работать на очень длинных строках (таких как последовательности ДНК, где мы ищем блоки с n-м появлением подстроки нуклеотидов).

2 ответа

Решение

Попробуйте с этим:

text_split=strsplit(text," in ")[[1]]

l=length(text_split)
n = floor(l/3)
Seq = seq(1,by=2,length.out = n)

L= list()
L=sapply(Seq, function(x){
  paste0(paste(text_split[x:(x+2)],collapse=" in ")," in ")
})
if (l>(n*3)){
L = c(L,paste(text_split[(n*3+1):l],collapse=" in "))
}

Последнее условие в случае, если номер in не делится на 3. Кроме того, последний in вставить в sapply() потому что я не знаю, что ты хочешь сделать с одним in это отделяет ваши блоки.

Дайте мне знать, если это поможет. Я постараюсь сделать это быстрее. Держит третий in в блоке кода. Если это сработает, я тоже буду комментировать.

library(lipsum)
library(stringi)

my.string = capture.output(lipsum(5))
my.string = paste(my.string, collapse = " ")

end_of_in <- stri_locate_all(fixed = " in ", my.string)[[1]][,2]
start_of_strings <- c(1, end_of_in[c(F, F, T)]) 
end_of_strings <- c(end_of_in[c(F, F, T)] - 1, nchar(my.string))
end_of_strings <- end_of_strings[!duplicated(end_of_strings)]


stri_sub(my.string, start_of_strings, end_of_strings)

РЕДАКТИРОВАТЬ: на самом деле, использовать stri_sub от stringi, Это будет масштабироваться намного лучше, чем substring, Увидеть:

my.string <- paste(rep(my.string, 10000), collapse = " ")
nchar(my.string)
[1] 22349999

microbenchmark::microbenchmark(
  sol1 = {
    text_split=strsplit(my.string," in ")[[1]]

    l=length(text_split)
    n = floor(l/3)
    Seq = seq(1,by=2,length.out = n)

    L= list()
    L=sapply(Seq, function(x){
      paste0(paste(text_split[x:(x+2)],collapse=" in ")," in ")
    })
    if (l>(n*3)){
      L = c(L,paste(text_split[(n*3+1):l],collapse=" in "))
    }
  },
  sol2 = {
    end_of_in <- stri_locate_all(fixed = " in ", my.string)[[1]][,2]
    start_of_strings <- c(1, end_of_in[c(F, F, T)]) 
    end_of_strings <- c(end_of_in[c(F, F, T)] - 1, nchar(my.string))
    end_of_strings <- end_of_strings[!duplicated(end_of_strings)]
    stri_sub(my.string, start_of_strings, end_of_strings)
  },
  times = 10
)

Unit: milliseconds
 expr      min        lq      mean    median        uq       max neval
 sol1 914.1268 927.45958 941.36117 939.80361 950.18099 980.86941    10
 sol2  55.4163  56.40759  58.53444  56.86043  57.03707  71.02974    10
Другие вопросы по тегам