R 中 %in% 运算符的 C++ 版本

Posted

技术标签:

【中文标题】R 中 %in% 运算符的 C++ 版本【英文标题】:A C++ version of the %in% operator in R 【发布时间】:2014-01-26 03:49:04 【问题描述】:

C++ 中是否有任何函数等效于 R 中的%in% 运算符?考虑 R 中的以下命令:

which(y %in% x)

我试图在 C++ 中找到等效的东西(特别是在 Armadillo 中),但我找不到任何东西。然后我编写了自己的函数,与上面的 R 命令相比非常慢。

这是我写的:

#include <RcppArmadillo.h>
// [[Rcpp::depends("RcppArmadillo")]]

// [[Rcpp::export]]
arma::uvec myInOperator(arma::vec myBigVec, arma::vec mySmallVec )
 arma::uvec rslt = find(myBigVec == mySmallVec[0]);
 for (int i = 1; i < mySmallVec.size(); i++)
   arma::uvec rslt_tmp = find(myBigVec == mySmallVec[i]);
   rslt = arma::unique(join_cols( rslt, rslt_tmp ));
 
 return rslt;

现在在上面的代码中采购后,我们有:

x <- 1:4
y <- 1:10
res <- benchmark(myInOperator(y, x), which(y %in% x), columns = c("test",
      "replications", "elapsed", "relative", "user.self", "sys.self"), 
       order = "relative")

结果如下:

                 test replications elapsed relative user.self sys.self
 2    which(y %in% x)          100   0.001        1     0.001        0
 1 myInOperator(y, x)          100   0.002        2     0.001        0

谁能指导我找到与 which(y %in% x) 对应的 C++ 代码或让我的代码更高效?这两个功能的经过时间已经非常小。我想我所说的效率更多是从编程的角度来看,以及我思考问题的方式和我使用的命令是否有效。

感谢您的帮助。

【问题讨论】:

很难击败which(y %in% x),因为%in%(调用match)和which 已经是.Internal 函数,因此用C 实现并可能进行了优化。有可能通过避免%in% 生成的临时逻辑向量来提高性能。 如果你通过引用而不是复制来传递东西,这可能会有所帮助,(const arma::vec &amp; myBigVec, const arma::vec &amp; mySmallVec ) 我对 RCpp 了解不够(对 Armidillo 也一无所知),所以我无法回答这个问题。但是,如果我在 C++ 中执行此操作,我会查看 std::set_intersection 对于这样的问题(如何在语言 Z 中复制/模仿语言 Y 的特征 X),如果您准确描述该特征的作用或在至少是您关心的功能的子集。 @Yakk 问题包括 R 标签。这些当然是 R 的构造。R 中的“逻辑向量”仅在涉及 RCpp 时与std::vector 相关。也许这个问题不应该应用 C++ 标签,因为 R 和 RCpp 有点不同。但这里的信息是,如果一个问题有关于你不熟悉的语言的标签,你可能会避免回答或评论。这就是我对带有我不懂的语言标签的答案所做的事情。 【参考方案1】:

编辑:感谢 @MatthewLundberg 和 @Yakk 发现我的愚蠢错误。

如果您真正想要的只是更快的匹配,您应该查看 Simon Urbanek 的 fastmatch 包。然而,Rcpp 实际上有一个可以在这里使用的糖in 函数。 in 使用了 fastmatch 包中的一些想法,并将它们合并到 Rcpp 中。我也在这里比较@hadley 的解决方案。

// [[Rcpp::plugins("cpp11")]]
#include <Rcpp.h>
using namespace Rcpp;

// [[Rcpp::export]]
std::vector<int> sugar_in(IntegerVector x, IntegerVector y) 
  LogicalVector ind = in(x, y);
  int n = ind.size();
  std::vector<int> output;
  output.reserve(n);
  for (int i=0; i < n; ++i) 
    if (ind[i]) output.push_back(i+1);
  
  return output;


// [[Rcpp::export]]
std::vector<int> which_in(IntegerVector x, IntegerVector y) 
  int nx = x.size();
  std::unordered_set<int> z(y.begin(), y.end());
  std::vector<int> output;
  output.reserve(nx);
  for (int i=0; i < nx; ++i) 
    if (z.find( x[i] ) != z.end() ) 
      output.push_back(i+1);
    
  
  return output;



// [[Rcpp::export]]
std::vector<int> which_in2(IntegerVector x, IntegerVector y) 
  std::vector<int> y_sort(y.size());
  std::partial_sort_copy (y.begin(), y.end(), y_sort.begin(), y_sort.end());

  int nx = x.size();
  std::vector<int> out;

  for (int i = 0; i < nx; ++i) 
    std::vector<int>::iterator found =
      lower_bound(y_sort.begin(), y_sort.end(), x[i]);
    if (found != y_sort.end()) 
      out.push_back(i + 1);
    
  
  return out;


/*** R
set.seed(123)
library(microbenchmark)
x <- sample(1:100)
y <- sample(1:10000, 1000)
identical( sugar_in(y, x), which(y %in% x) )
identical( which_in(y, x), which(y %in% x) )
identical( which_in2(y, x), which(y %in% x) )
microbenchmark(
  sugar_in(y, x),
  which_in(y, x),
  which_in2(y, x),
  which(y %in% x)
)
*/

在这个问题上调用 sourceCpp 给我,从基准,

Unit: microseconds
            expr    min      lq  median      uq    max neval
  sugar_in(y, x)  7.590 10.0795 11.4825 14.3630 32.753   100
  which_in(y, x) 40.757 42.4460 43.4400 46.8240 63.690   100
 which_in2(y, x) 14.325 15.2365 16.7005 17.2620 30.580   100
 which(y %in% x) 17.070 21.6145 23.7070 29.0105 78.009   100

【讨论】:

调用which_in时一定要复制向量吗? lhs 的排序使返回值顺序错误。尝试使用从 rhs 复制的无序集,并在不首先复制的情况下转换 lhs?也比对数因子更快。为了获得额外的功劳,懒惰地产生输出(可能使用boost)。哦,请注意,任何可迭代的范围都是有效的 lhs 和 rhs:不知道 R 是否使它无用。 犰狳的find() 和this Rcpp Gallery post 一样吗? FWIW,Rcpp 有一个in 糖函数。 github.com/RcppCore/Rcpp/blob/master/inst/include/Rcpp/sugar/… 在引擎盖下,它将使用类似于凯文在这里展示的东西。 看来Rcpp的糖还是能打赢的:)【参考方案2】:

对于这组输入,我们可以通过使用在技术上具有更高算法复杂度(每次查找 O(ln n) vs O(1))但具有更低常数的方法来获得更多性能:二分搜索.

// [[Rcpp::plugins("cpp11")]]
#include <Rcpp.h>
using namespace Rcpp;

// [[Rcpp::export]]
std::vector<int> which_in(IntegerVector x, IntegerVector y) 
  int nx = x.size();
  std::unordered_set<int> z(y.begin(), y.end());
  std::vector<int> output;
  output.reserve(nx);
  for (int i=0; i < nx; ++i) 
    if (z.find( x[i] ) != z.end() ) 
      output.push_back(i+1);
    
  
  return output;


// [[Rcpp::export]]
std::vector<int> which_in2(IntegerVector x, IntegerVector y) 
  std::vector<int> y_sort(y.size());
  std::partial_sort_copy (y.begin(), y.end(), y_sort.begin(), y_sort.end());

  int nx = x.size();
  std::vector<int> out;

  for (int i = 0; i < nx; ++i) 
    std::vector<int>::iterator found =
      lower_bound(y_sort.begin(), y_sort.end(), x[i]);
    if (found != y_sort.end()) 
      out.push_back(i + 1);
    
  
  return out;


/*** R
set.seed(123)
library(microbenchmark)
x <- sample(1:100)
y <- sample(1:10000, 1000)
identical( which_in(y, x), which(y %in% x) )
identical( which_in2(y, x), which(y %in% x) )
microbenchmark(
  which_in(y, x),
  which_in2(y, x),
  which(y %in% x)
)
*/

在我的电脑上

Unit: microseconds
            expr  min   lq median   uq  max neval
  which_in(y, x) 39.3 41.0   42.7 44.0 81.5   100
 which_in2(y, x) 12.8 13.6   14.4 15.0 23.8   100
 which(y %in% x) 16.8 20.2   21.0 21.9 31.1   100

比基础 R 好大约 30%。

【讨论】:

你的意思是说which_in2的复杂度为O(n log n)? @MatthewLundberg 哎呀,我很困惑。我的意思是 O(1),指的是个人查找。

以上是关于R 中 %in% 运算符的 C++ 版本的主要内容,如果未能解决你的问题,请参考以下文章

R - 使用匹配运算符时保留顺序 (%in%)

泛化 R %in% 运算符以匹配元组

在 R 中为 Google BigQuery 使用 IN 运算符

C ++中[in]相等运算符的评估顺序?

r %in% 运算符 |控制大小写敏感性[重复]

为啥 ~= 在 C++ 中缺少唯一的非逻辑赋值运算符? [关闭]