在R

时间:2015-09-04 12:38:47

标签: r

如何将字符串拆分为R中固定长度的元素是一个常见问题,典型答案要么依赖于substring(x),要么strsplit(x, sep="")后跟paste(y, collapse = "")。 例如,通过指定3个字符的固定长度,可以将字符串"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
}

这看起来相当繁琐,但我试图简单地将chars向量放入具有足够行数的矩阵中,然后使用apply(mat, 2, paste, collapse="")折叠矩阵列。这慢得多。将带有split()的字符向量拆分为正确长度的向量列表,以便折叠元素,甚至更慢。

所以如果你能找到更快的东西,请告诉我。如果没有,我的功能可能会有所帮助。 :)

3 个答案:

答案 0 :(得分: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

答案 1 :(得分:2)

我们可以split指定一个正则表达式,以匹配前面带有'n'个字符的位置,例如,如果我们分割3个字符,我们匹配前面带有3个字符的位置/边界({{ 1}})。

(?<=.{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" 中的stri_extract_all

library(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}')

答案 2 :(得分:1)

好吧,有一个更快的解决方案发布here(d'oh!)

简单

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

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

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