【问题标题】:R - Efficiently insert multiple stringsR - 有效地插入多个字符串
【发布时间】:2021-12-14 15:06:21
【问题描述】:

我想将子字符串列表 (word_list) 插入到字符串 (text) 的特定位置 (idx_list)

text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
idx_list = c(5,16,30,50)
word_list = c("AAA", "BBB", "CCC", "DDD")

我知道我可以在循环中使用多个可能的函数(gsub、stri_sub 等)。然而,这在大型语料库上变得相当缓慢。有没有更有效的解决方案?也许矢量化了?

【问题讨论】:

标签: r string


【解决方案1】:

解决方案 1

stringi 包函数的一个小包装器,用于输入 OP 所需的内容。

inject <- function(string, index, replacement){
  stringi::stri_sub_replace_all(string, from = index,
                                to = index-1,
                                replacement = replacement)
}

text <- "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
idx_list <- c(5, 16, 30, 50)
word_list <- c("AAA", "BBB", "CCC", "DDD")

inject(text, idx_list, word_list)
#> [1] "LoreAAAm ipsum dolBBBor sit amet, cCCConsectetur adipiscinDDDg elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."

解决方案 2

Zach Foster's 答案的改编,浓缩为单个函数

inject_two <- function(string, inject, index) {
  inject <- inject[order(index)]
  index <- sort(index)
  # expand string
  split <- substr(rep(string, length(index) + 1),
    start = c(1, index),
    stop = c(index - 1, nchar(string))
  )
  ord1 <- 2 * (1:length(split)) - 1
  ord2 <- 2 * (1:length(inject))
  paste(c(split, inject)[order(c(ord1, ord2))], collapse = "")
}

inject_two(text, word_list, idx_list)
#> [1] "LoreAAAm ipsum dolBBBor sit amet, cCCConsectetur adipiscinDDDg elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."

基准

evans <- function(string, index, replacement){
  ord <- order(-index)
  Reduce(function(S, R) {
    paste0(substring(S, 1, R[[1]]-1), R[[2]], substring(S, R[[1]], nchar(S)))
  }, Map(list, index[ord], replacement[ord]), string)
}
ggplot2::autoplot(microbecnhmark::microbenchmark(
  inject(text, idx_list, word_list),
  inject_two(text, idx_list, word_list),
  evans(text, idx_list, word_list),
  times = 1000
))

对于少量插入,所有解决方案的表现都相似。如果我们执行许多(这里 N = 410 000)插入怎么办?

text_large = paste0(rep(text, 10000), collapse = "")
idx_list_large = seq(1, nchar(text_large), by = 3)
word_list_large = sample(LETTERS, size = length(idx_list_large), replace = T)

bench::mark(
  inject = inject(text_large, idx_list_large, word_list_large),
  inject_two = inject_two(text_large, idx_list_large, word_list_large),
 iterations = 50
)[,c(1,3,5,7)]

# A tibble: 2 x 4
  expression   median mem_alloc n_itr
  <bch:expr> <bch:tm> <bch:byt> <int>
1 inject       32.2ms    9.38MB    50
2 inject_two  157.4ms   65.69MB    50

出人意料,基于 C++ 的 stringi速度和内存方面都占据优势。注意 evans 被省略,因为在测试时 1 次通过需要多分钟

【讨论】:

  • 我想我更喜欢你的单通道 (single-paste) 实现而不是我的 Reduce 方法。
  • @r2evans 结果证明它不是最有效的。请检查我是否没有在基准测试中对您的实施造成伤害
【解决方案2】:

我认为首先从最后一个(最高的idx_list)开始很重要,否则所有数字都需要移动。 (这当然不难,但倒退似乎更容易。)

#      0         1         2         3         4         5         6         7         8         9         a         b         c
#      0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123
text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
idx_list = c(5,16,30,50)
word_list = c("AAA", "BBB", "CCC", "DDD")

作品:

ord <- order(-idx_list)
Reduce(function(S, R) {
  paste0(substring(S, 1, R[[1]]-1), R[[2]], substring(S, R[[1]], nchar(S)))
}, Map(list, idx_list[ord], word_list[ord]), text)
# [1] "LoreAAAm ipsum dolBBBor sit amet, cCCConsectetur adipiscinDDDg elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."

演练:

  • ord 只是降序,例如

    word_list[ord]
    # [1] "DDD" "CCC" "BBB" "AAA"
    
  • 因为我们要使用Reduce(稍后解释),所以我们需要idx_list[1]word_list[1] 的组合在一个参数中,而不是单独的;为此,我们使用Map(list, ...) 将它们组合在一起,将它们“压缩”到一个列表中,每个列表都包含字符位置和要插入的字符串:

    str( Map(list, idx_list[ord], word_list[ord]) )
    # List of 4
    #  $ :List of 2
    #   ..$ : num 50
    #   ..$ : chr "DDD"
    #  $ :List of 2
    #   ..$ : num 30
    #   ..$ : chr "CCC"
    #  $ :List of 2
    #   ..$ : num 16
    #   ..$ : chr "BBB"
    #  $ :List of 2
    #   ..$ : num 5
    #   ..$ : chr "AAA"
    

    (这可以与任意数量的参数一起使用。)

  • 因为我们需要插入一个字符串,然后在第一个结果中插入另一个字符串,所以基函数Reduce在这里可以正常工作。第一个 arg 是一个接受两个参数的函数:上一次调用的结果和 Map'd 参数的下一个元素。

【讨论】:

    猜你喜欢
    • 2011-05-13
    • 2017-06-02
    • 1970-01-01
    • 2023-03-08
    • 1970-01-01
    • 2021-11-14
    • 1970-01-01
    • 2013-06-20
    • 1970-01-01
    相关资源
    最近更新 更多