将字符串分割为固定大小的块

4
这似乎是一个非常简单的任务,但我在基础R中找不到一个好的解决方案。我有一个包含2N个字符的字符字符串。如何将其拆分为长度为N的字符向量,其中每个元素都是一个2个字符的字符串?
我可以使用类似substrVectorize的东西:
vss <- Vectorize(substr, c("start", "stop"))
ch <- paste(rep("a", 1e6), collapse="")
vss(ch, seq(1, nchar(ch), by=2), seq(2, nchar(ch), by=2))

但对于长字符串来说,这是非常慢的(我相信是O(N^2))。


2
使用 substring,其中包括向量化的 firstlast - nicola
这个方法可以工作,但是它和 Vectorize(substr) 一样存在 O(N^2) 的运行时间问题。此外,它还会复制 N/2 次初始字符串,因此也需要 O(N^2) 的内存! - Hong Ooi
1
如果你的字符串中的字符是ASCII码(或者至少没有多字节字符),你可以尝试使用apply(matrix(charToRaw(ch),nrow=2),2,rawToChar),它似乎比substring快得多,并且基本上是线性扩展的。 - nicola
2
GSee的答案运行非常快。https://dev59.com/VXE95IYBdhLWcg3wlu4g - user20650
1个回答

4

如果你想要速度,Rcpp总是一个不错的选择:

library(Rcpp);
cppFunction('
    List strsplitN(std::vector<std::string> v, int N ) {
        if (N < 1) throw std::invalid_argument("N must be >= 1.");
        List res(v.size());
        for (int i = 0; i < v.size(); ++i) {
            int num = v[i].size()/N + (v[i].size()%N == 0 ? 0 : 1);
            std::vector<std::string> resCur(num,std::string(N,0));
            for (int j = 0; j < num; ++j) resCur[j].assign(v[i].substr(j*N,N));
            res[i] = resCur;
        }
        return res;
    }
');

ch <- paste(rep('a',1e6),collapse='');
system.time({ res <- strsplitN(ch,2L); });
##    user  system elapsed
##   0.109   0.015   0.121
head(res[[1L]]); tail(res[[1L]]);
## [1] "aa" "aa" "aa" "aa" "aa" "aa"
## [1] "aa" "aa" "aa" "aa" "aa" "aa"
length(res[[1L]]);
## [1] 500000

有用的参考资料:http://gallery.rcpp.org/articles/strings_with_rcpp/


更多示例:

strsplitN(c('abcd','efgh'),2L);
## [[1]]
## [1] "ab" "cd"
##
## [[2]]
## [1] "ef" "gh"
##
strsplitN(c('abcd','efgh'),3L);
## [[1]]
## [1] "abc" "d"
##
## [[2]]
## [1] "efg" "h"
##
strsplitN(c('abcd','efgh'),1L);
## [[1]]
## [1] "a" "b" "c" "d"
##
## [[2]]
## [1] "e" "f" "g" "h"
##
strsplitN(c('abcd','efgh'),5L);
## [[1]]
## [1] "abcd"
##
## [[2]]
## [1] "efgh"
##
strsplitN(character(),5L);
## list()
strsplitN(c('abcd','efgh'),0L);
## Error: N must be >= 1.

以上实现有两个重要的注意事项:

1:它不能正确处理NA。当Rcpp被强制生成std::string时,似乎会将其字符串化为'NA'。您可以在R中使用包装器轻松解决此问题,用真正的NA替换有问题的列表组件。

x <- c('a',NA); strsplitN(x,1L);
## [[1]]
## [1] "a"
##
## [[2]]
## [1] "N" "A"
##
x <- c('a',NA); ifelse(is.na(x),NA,strsplitN(x,1L));
## [[1]]
## [1] "a"
##
## [[2]]
## [1] NA
##

2: 它不能正确处理多字节字符。这是一个更棘手的问题,需要重写核心函数实现以使用 Unicode 意识遍历。修复此问题还会产生显着的性能损失,因为您将无法在赋值循环之前一次性预分配每个向量。

strsplitN('aΩ',1L);
## [[1]]
## [1] "a"    "\xce" "\xa9"
##
strsplit('aΩ','');
## [[1]]
## [1] "a" "Ω"
##

非常感谢您的回答。我能否使用这个函数在GitHub上创建一个包呢?当然,在描述中我会注明您的贡献。我需要将其放入一个脚本中,每天运行多次。创建一个包可以避免我进行编译。 - BerriJ
非常欢迎。当然,请继续。 - bgoldst
1
这个包可以在这里找到:https://github.com/BerriJ/strsplit.fix 它实际上只包含了你的函数,没有文档(但)。也许对其他人也有用 :) - BerriJ
有没有一个函数可以按照以下方式将一个字符分割开来:"My cat" 按照每3个字符分成一组 -> "My ", "y c", " ca", "cat"。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接