Rcpp Power Set实现:尝试在SET_VECTOR_ELT中设置索引8/8

时间:2015-04-25 07:52:29

标签: r rcpp

考虑将一组元素作为输入向量并返回列表中的幂集的函数:

> pwr_set(letters[1:3])

[[1]]
character(0)

[[2]]
[1] "a"

[[3]]
[1] "b"

[[4]]
[1] "a" "b"

[[5]]
[1] "c"

[[6]]
[1] "a" "c"

[[7]]
[1] "b" "c"

[[8]]
[1] "a" "b" "c"

R定义:

pwr_set <- function(els){
  n_els <- length(els)
  out <- vector(mode="list",length = 2 ^ n_els)
  out[[1]] <- character()     # first element in power set is the empty set

  listIdx <- 1L       # start a listIdx

  for(i in 1L:n_els){
    for(j in 1L:listIdx){
      listIdx <- listIdx + 1L
      out[[listIdx]] <- c(out[[j]], els[i])
    }
  }
  out
}

我已经提出以下翻译在Rcpp中实现:

#include <Rcpp.h>
#include <Math.h>
using namespace Rcpp;


// [[Rcpp::export]]
List pwr_set_cpp(CharacterVector els) {

  int n_els = els.size();         // size of set
  int pwrset_card = pow(2,n_els); // number of subsets to make power set is 2^n_elements
  List out(pwrset_card);          // list for output
  int listidx = 0;                // to count through list indeces
  out[0] = CharacterVector::create(); // first element of list represents empty set
  CharacterVector tmp;            // to hold new extended vector

  for (int i=0; i < n_els; ++i) {
    for (int j=0; j <= listidx; ++j) {

      listidx++;
      tmp = out[j];
      tmp.push_back(els[i]);
      out[listidx] = tmp;

    }
  }
  return out;
}

但是!

> pwr_set_cpp(letters[1:3])

给我错误: attempt to set index 8/8 in SET_VECTOR_ELT

使用Google搜索并查看源代码here会让我认为我正在尝试索引超出SET_VECTOR_ELT缓存的内容?这必然意味着我误解了如何在Rcpp或类似的东西中逐步执行输入/输出循环。

任何帮助我理解这里的指导都会很精彩。提前致谢。

更新:修复。

根据@Romain Francois和@nicola的回答/评论,关键的误解就是你在R中逐步完成循环的方式很聪明! (至少我现在比以前更欣赏它了)。要在c++中实现相同的功能,我必须将listidx分解为counter变量(这是j的条件检查)和临时cnt2这基本上记录了当前计数器状态之上采取的j步数。 counter然后在使用当前值cnt2传递每个出口后进行更新。

#include <Rcpp.h>
#include <Math.h>
using namespace Rcpp;

// [[Rcpp::export]]
List pwr_set_cpp(CharacterVector els) {

  int n_els = els.size();         
  int pwrset_card = pow(2,n_els); 
  List out(pwrset_card);          
  out[0] = StringVector::create(); 
  CharacterVector tmp;            
  int counter = 0;
  for (int i=0; i < n_els; ++i) {
    int cnt2 = counter;            // capture counter state
      for (int j =0; j <= counter; ++j) {
        cnt2++;                   // capture counter + j steps
        tmp = as<StringVector>(out[j]);
        tmp.push_back(as<std::string>(els[i]));
        out[cnt2] = tmp;

    }
      counter = cnt2;             // update counter state
  }
  return out;
}

快速时间

只是为了一个有趣的基准。虽然我确信有更有效的方法(使用相同的算法结构),因为我正在进行大量STRSXP元素/向量的复制。

x <- letters[1:18]
pwr_set_bitecompile <- compiler::cmpfun(pwr_set) # R 3.2.0 !
microbenchmark::microbenchmark(
    pwr_set(x),
    pwr_set_bitecompile(x),
    pwr_set_cpp(x))

Unit: milliseconds
                   expr      min       lq     mean   median       uq       max neval
             pwr_set(x) 748.6553 820.0667 841.2828 834.1229 856.2436 1023.1324   100
 pwr_set_bitecompile(x) 365.9969 480.9474 498.2100 503.5115 518.8562  596.8205   100
         pwr_set_cpp(x) 155.9447 283.8771 295.8411 300.4865 314.0826  342.0261   100

1 个答案:

答案 0 :(得分:2)

问题是你正在尝试将某些内容分配到out[8],并且该内容高于列表中的元素数量。

请参阅此添加的行:

  Rprintf( "out.size() = %d, listidx = %d\n", out.size(), listidx );
  out[listidx] = tmp;

你会得到:

> pwr_set_cpp(letters[1:3])
out.size() = 8, listidx = 1
out.size() = 8, listidx = 2
out.size() = 8, listidx = 3
out.size() = 8, listidx = 4
out.size() = 8, listidx = 5
out.size() = 8, listidx = 6
out.size() = 8, listidx = 7
out.size() = 8, listidx = 8
Error in pwr_set_cpp(letters[1:3]) :
  tentative de modification de l'index 8/8 dans SET_VECTOR_ELT
Calls: sourceCpp ... withVisible -> eval -> eval -> pwr_set_cpp -> <Anonymous>
Exécution arrêtée      

另见@nicola的评论。您对listidxj做错了。如果溢出没有阻止它,你将获得无限循环。

可能令人困惑的是R代码:

for(j in 1L:listIdx){
  listIdx <- listIdx + 1L
  out[[listIdx]] <- c(out[[j]], els[i])
}

评估1L:listIdx一次,以便在循环内部,您可以使用listIdx执行其他操作。在C ++中并非如此。