我单独提供了 S. Baldur 函数的基准测试count_empty_cpp()。这比我的方法快得多stringi,所以我添加了另一个Rcpp使用 C++ 标准库的函数,该函数主要基于对 C++ 问题的回答,即检查 std::string 是否仅包含空格的有效方法。
Rcpp::cppFunction("int count_empty_cpp2(CharacterVector x) {
int count = 0, j, n;
std::string str;
for (int i = 0; i < x.size(); i++) {
str = Rcpp::as<std::string>(x[i]);
if(str.find_first_not_of(' ') == std::string::npos)
{
count++;
}
}
return count;
}")
我还添加了第三个Rcpp函数,它查看字符向量每个元素的底层S 表达式CHAR()。这意味着我们可以在字符串为空的情况下避免类型转换。此外,当我们需要查看字符串的内容时,我使用将转换SEXP为 C 样式指针,指向以空字符结尾的字符串(const char*),而不是 C++ std::string。这意味着我们复制引用(每个字符串可能 8 个字节),而不是数据。
Rcpp::cppFunction("int count_empty_cpp3(CharacterVector x) {
int count = 0;
for (int i = 0; i < x.size(); i++) {
SEXP elem = x[i];
R_xlen_t len = Rf_length(elem);
if (len == 0) {
count++;
} else {
const char* str = CHAR(elem);
bool is_empty = true;
for (R_xlen_t j = 0; j < len; j++) {
if (str[j] != ' ') {
is_empty = false;
break;
}
}
if (is_empty) count++;
}
}
return count;
}")
我将这些方法与两个最快的 R 答案进行了比较。Rcpp一旦向量长度为,所有方法都比最快的 R 方法快得多>1e4。
还有一个选择:
以下是对前一个答案的简洁变体:
最后……因为我们对基准测试很感兴趣。与基本 R 相比,还有改进的空间,正如 SamR 使用 stringi 所显示的那样。这是另一个使用 Rcpp 的示例,我们避免修改向量并逐个检查字符,直到第一个非空格字符。
纯 R 方法
您有几个出色的 R 语言基础答案。我注意到您标记了
stringr
。我认为在这里使用 没有任何优势。但是,使用R 包在任何语言环境或字符编码中进行快速、可移植、正确、一致且方便的字符串/文本处理stringr
可能会有优势。stringi
stringi
往往非常快。stringr
取决于stringi
(实际上许多stringr
函数都是stringi
函数的薄包装器),因此如果您已经stringr
安装了,那么您也有stringi
。与 不同
stringr
,stringi
有一个检查空字符串的函数(相当于!base::nzchar()
),它可能比字符串比较更快,并且几乎肯定比计算所有字符串(包括非空字符串)的字符更快。Rcpp 方法
正如 S. Baldur 的回答所表明的,您
Rcpp
也可以使用它。这个速度太快了,我将把它包含在下面一个单独的基准测试部分中,以便更容易地看到纯 R 方法的差异。
纯 R 基准
只是为了好玩,我使用长度不超过 1m 的向量运行了一些基准测试。S . Baldur 的第二种方法对于长度为 10 和 100 的向量来说是最快的。对于长度为 1000 及以上的向量,该
stringi
方法是最快的。如果 RAM 是一个因素,那么 Ben Bolker 的答案始终使用最少的内存。以下是表格形式的数据(请注意,时间是相对的,最快/最低内存方法始终是
1
)。基准代码:
Rcpp
基准我单独提供了 S. Baldur 函数的基准测试
count_empty_cpp()
。这比我的方法快得多stringi
,所以我添加了另一个Rcpp
使用 C++ 标准库的函数,该函数主要基于对 C++ 问题的回答,即检查 std::string 是否仅包含空格的有效方法。我还添加了第三个
Rcpp
函数,它查看字符向量每个元素的底层S 表达式CHAR()
。这意味着我们可以在字符串为空的情况下避免类型转换。此外,当我们需要查看字符串的内容时,我使用将转换SEXP
为 C 样式指针,指向以空字符结尾的字符串(const char*
),而不是 C++std::string
。这意味着我们复制引用(每个字符串可能 8 个字节),而不是数据。我将这些方法与两个最快的 R 答案进行了比较。
Rcpp
一旦向量长度为,所有方法都比最快的 R 方法快得多>1e4
。以下是结果表。前两种方法之间差别很小
Rcpp
。避免的方法std::string
比其他两种方法稍快一些:这些是相对较短的字符串。我不会运行更多基准测试,但我怀疑如果字符串更长,我们会看到复制指针而不是数据的相对优势。
关于基准的说明
这些基准测试主要是为了好玩。所有答案之间的差异相对较小,因此除非您使用巨大的向量重复多次,字符串非常长或内存资源非常有限,否则我会优化可读代码,而不是推出我自己的 Rcpp 解决方案,该解决方案的速度快几纳秒。
对于代码高尔夫,你可能感兴趣
使用
sum(grepl())
加上适当的正则表达式:^
:字符串的开头*
:零个或多个空格$
:字符串结尾如果您想更普遍地寻找“空白”(例如允许制表符),请使用
"^[[:space:]]*$"
,尽管正如所指出的那样?grep
,空白的定义取决于语言环境......length(grep(...))
也可以,或者stringr::str_count(vector, "^ *$")
。物有所值:
(@RuiBarradas 未包括在内,因为 vs 与 @KonradRudolph 类似)。我很惊讶 @s_baldur 的回答如此之快……但可能还值得记住的是,除非这个操作占了你整个工作流程的很大一部分,否则它的速度足够快,不用担心效率……
我会
nzchar()
与...结合使用trimws()
(尽管...的双重否定!nzchar()
使得阅读起来有点尴尬):trimws
删除所有空格。然后nchar
会得到字符数。与零进行比较并仅计算那些。但这比Ben Bolker 的答案慢大约 3 倍。创建于 2024-06-30,使用reprex v2.1.0
编辑
根据现已删除的评论,
创建于 2024-06-30,使用reprex v2.1.0