在 R 中将字符串拆分为固定长度元素的最快方法

fastest way to split strings into fixed-length elements in R

提问人:jeanlain 提问时间:9/4/2015 最后编辑:jeanlain 更新时间:9/4/2015 访问量:4948

问:

如何在 R 中将字符串拆分为固定长度的元素是一个常见问题,典型的答案要么依赖 ,要么后跟 。 例如,可以通过指定 3 个字符的固定长度来将字符串切入。substring(x)strsplit(x, sep="")paste(y, collapse = "")"azertyuiop""aze", "rty","uio", "p"

我正在寻找最快的方法。 在对长字符串(> 1000 个字符)进行了一些测试后,我发现这太慢了。因此,策略是将字符串拆分为单个字符,然后通过应用一些巧妙的方法将它们粘贴回所需长度的组中。substring()

这是我能想到的最快的功能。这个想法是将字符串拆分为单独的字符,然后在字符向量的正确位置散布一个分隔符,将字符(和分隔符)折叠回字符串,然后再次拆分字符串,但这次指定分隔符。

splitInParts <- function(string, size) {              #can process a vector of strings. "size" is the length of desired substrings
    chars <- strsplit(string,"",T)
    lengths <- nchar(string)
    nFullGroups <- floor(lengths/size)                #the number of complete substrings of the desired size

    #here we prepare a vector of separators (comas), which we will replace by the characters, except at the positions that will have to separate substring groups of length "size". Assumes that the string doesn't have any comas.
    seps  <-  Map(rep, ",", lengths + nFullGroups)     #so the seps vector is longer than the chars vector, because there are separators (as may as they are groups)
    indices <- Map(seq, 1, lengths + nFullGroups)      #the positions at which separators will be replaced by the characters
    indices <- lapply(indices, function(x) which(x %% (size+1) != 0))  #those exclude the positions at which we want to retain the separators (I haven't found a better way to generate such vector of indices)

    temp <- function(x,y,z) {        #a fonction describing the replacement, because we call it in the Map() call below
        x[y] <- z
        x
    }
    res <- Map(temp, seps, indices, chars)             #so now we have a vector of chars with separators interspersed
    res <- sapply(res, paste, collapse="", USE.NAMES=F)  #collapses the characters and separators
    res <- strsplit(res, ",", T)                        #and at last, we can split the strings into elements of the desired length
}

这看起来很乏味,但我试图简单地将向量放入具有足够行数的矩阵中,然后用 折叠矩阵列。这要慢得多。而将字符向量拆分为适当长度的向量列表,以便折叠元素,甚至会更慢。charsapply(mat, 2, paste, collapse="")split()

因此,如果您能更快地找到东西,请告诉我。如果没有,那么我的函数可能会有一些用处。:)

r

评论

0赞 akrun 9/4/2015
请提供一些示例数据和基于此的预期输出。
0赞 jeanlain 9/4/2015
我已经编辑了我的问题并添加了一个示例。
0赞 akrun 9/4/2015
这一行代码难道还不够吗?strsplit(str1, '(?<=.{3})', perl=TRUE)str1 <- "azertyuiop"
0赞 akrun 9/4/2015
没关系。我没有测试它,以为它会比你的多功能/多行代码更快。

答:

3赞 akrun 9/4/2015 #1

我们可以通过指定一个正则表达式来匹配以“n”个字符开头的位置,例如,如果我们被 3 个字符拆分,我们匹配以 3 个字符 () 开头的位置/边界。split(?<=.{3})

splitInParts <- function(string, size){
    pat <- paste0('(?<=.{',size,'})')
    strsplit(string, pat, perl=TRUE)
 }

splitInParts(str1, 3)
#[[1]]
#[1] "aze" "rty" "uio" "p"  

splitInParts(str1, 4)
#[[1]]
#[1] "azer" "tyui" "op"  

splitInParts(str1, 5)
#[[1]]
#[1] "azert" "yuiop"

或者另一种方法是使用 from 。stri_extract_alllibrary(stringi)

library(stringi)
splitInParts2 <- function(string, size){
   pat <- paste0('.{1,', size, '}')
   stri_extract_all_regex(string, pat)
 }
splitInParts2(str1, 3)
#[[1]]
#[1] "aze" "rty" "uio" "p"  

stri_extract_all_regex(str1, '.{1,3}')

数据

 str1 <- "azertyuiop"

评论

0赞 jeanlain 9/4/2015
谢谢。但它似乎更慢。我的解决方案需要 9 秒才能完成 35838 个总长度为 41413966 的基因序列,而您的解决方案需要 31 秒。我上面发布的另一个解决方案需要 6 秒。
0赞 akrun 9/4/2015
@jeanlain 你能试试这个方法吗?stringi
1赞 jeanlain 9/4/2015 #2

好吧,这里有一个更快的解决方案(哦!

只是

strsplit(gsub("([[:alnum:]]{size})", "\\1 ", string)," ",T)

这里使用空格作为分隔符。 (没想过)。[[:allnum::]]{}

如何将自己的问题标记为重复问题?:(

评论

1赞 akrun 9/4/2015
我认为应该更快library(stringi);stri_extract_all_regex(str1, '.{1,3}')
0赞 jeanlain 9/4/2015
是的!这个需要 1.16 秒。您可以将其作为答案发布。
4赞 Tensibai 9/4/2015 #3

阅读更新很有趣,所以我进行了基准测试:

> nchar(mystring)
[1] 260000

我的想法与@akrun的想法几乎相同,因为str_extract_all引擎盖下使用相同的功能 IIRC)

library(stringr)
tensiSplit <- function(string,size) {
  str_extract_all(string, paste0('.{1,',size,'}'))
}

我的机器上的结果:

> microbenchmark(splitInParts(mystring,3),akrunSplit(mystring,3),splitInParts2(mystring,3),tensiSplit(mystring,3),gsubSplit(mystring,3),times=3)
Unit: milliseconds
                       expr        min         lq       mean     median         uq        max neval
  splitInParts(mystring, 3)   64.80683   64.83033   64.92800   64.85384   64.98858   65.12332     3
    akrunSplit(mystring, 3) 4309.19807 4315.29134 4330.40417 4321.38461 4341.00722 4360.62983     3
 splitInParts2(mystring, 3)   21.73150   21.73829   21.90200   21.74507   21.98725   22.22942     3
    tensiSplit(mystring, 3)   21.80367   21.85201   21.93754   21.90035   22.00447   22.10859     3
     gsubSplit(mystring, 3)   53.90416   54.28191   54.55416   54.65966   54.87915   55.09865     3