Возвращает динамический вектор от C до R

Я пишу код в C, который динамически вызывается из R.

Этот код генерирует путь случайного процесса Пуассона до желаемого времени T. Поэтому при каждом вызове моей функции C длина возвращаемого вектора будет отличаться в зависимости от генерируемых случайных чисел.

Какую структуру данных R мне нужно создать? a LISTSXP? другой?

Как я могу создать его и как его добавить? И особенно, как я могу вернуть его обратно в R?

Спасибо за помощь.

Ответ 1

Это действительно зависит от того, что вы хотите использовать в качестве временной структуры, потому что в конце концов вам придется выделять вектор для результата в любом случае. Так что бы вы ни использовали, это не то, что вы вернетесь. Существует несколько возможностей:

  • используйте Calloc + Realloc + Free, который позволяет вам развернуть временную память по мере необходимости. Когда у вас есть полный набор, вы выделяете вектор результата и возвращаете его.
  • если вы можете легко переоценить размер, вы можете перераспределить вектор результата и использовать SETLENGTH, прежде чем возвращать его. Есть проблемы с этим, хотя, потому что результат будет по-прежнему перенаправлен до дублирования позже.
  • Вы можете использовать то, что вы намекали, в котором есть скопированный список векторных блоков, т.е. выделять и защищать один парный список, который вы добавляете к хвосту в соответствии с вашими потребностями. В конце вы выделяете вектор возврата и копируете содержимое выделенных вами блоков. Это более сложное, чем оба из вышеперечисленных.

У каждого из них есть недостатки и преимущества, поэтому на самом деле это зависит от вашего приложения, чтобы выбрать наиболее подходящий для вас.

Изменить: добавлен пример использования подхода с парами. Я бы по-прежнему рекомендовал подход Realloc, поскольку он намного проще, но тем не менее:

#define BLOCK_SIZE xxx /* some reasonable size for increments - could be adaptive, too */ 
SEXP block; /* last vector block */
SEXP root = PROTECT(list1(block = allocVector(REALSXP, BLOCK_SIZE)));
SEXP tail = root;
double *values = REAL(block);
int count = 0, total = 0;
do { /* your code to generate values - if you want to add one 
        first try to add it to the existing block, otherwise allocate new one */
    if (count == BLOCK_SIZE) { /* add a new block when needed */
        tail = SETCDR(tail, list1(block = allocVector(REALSXP, BLOCK_SIZE)));
        values = REAL(block);
        total += count;
        count = 0;
    }
    values[count++] = next_value;
} while (...);
total += count;
/* when done, we need to create the result vector */
{
    SEXP res = allocVector(REALSXP, total);
    double *res_values = REAL(res);
    while (root != R_NilValue) {
        int size = (CDR(root) == R_NilValue) ? count : BLOCK_SIZE;
        memcpy(res_values, REAL(CAR(root)), sizeof(double) * size);
        res_values += size;
        root = CDR(root);
    }
    UNPROTECT(1);
    return res;
 }

Ответ 2

Если вы открыты для перехода с C на С++, вы получаете бесплатный уровень Rcpp. Вот пример со страницы пакета RcppExample (еще более неполного):

#include <RcppClassic.h>
#include <cmath>

RcppExport SEXP newRcppVectorExample(SEXP vector) {
BEGIN_RCPP

    Rcpp::NumericVector orig(vector);                // keep a copy 
    Rcpp::NumericVector vec(orig.size());            // create vector same size

    // we could query size via
    //   int n = vec.size();
    // and loop over the vector, but using the STL is so much nicer
    // so we use a STL transform() algorithm on each element
    std::transform(orig.begin(), orig.end(), vec.begin(), ::sqrt);

    return Rcpp::List::create(Rcpp::Named( "result" ) = vec,
                              Rcpp::Named( "original" ) = orig) ;

END_RCPP
}

Как вы видите, нет явного выделения памяти, освобождения, PROTECT/UNPROTECT и т.д., и вы возвращаете объект списка первого класса R.

Есть еще много примеров, включая в других вопросах SO, таких как этот.

Изменить: И вы действительно не говорили, что бы вы делали, но, как простая иллюстрация, вот код С++ с использованием дополнений Rcpp cumsum() и rpois(), которые ведут себя так же, как в R:

R> library(inline)
R> 
R> fun <- cxxfunction(signature(ns="integer", lambdas="numeric"),
+                    plugin="Rcpp",
+                    body='
+    int n = Rcpp::as<int>(ns);
+    double lambda = Rcpp::as<double>(lambdas);
+ 
+    Rcpp::RNGScope tmp;                  // make sure RNG behaves
+ 
+    Rcpp::NumericVector vec = cumsum( rpois( n, lambda ) );
+ 
+    return vec;
+ ')
R> set.seed(42)
R> fun(3, 0.3)
[1] 1 2 2
R> fun(4, 0.4)
[1] 1 1 1 2

И как доказательство, вернемся к R, если мы установим семя, мы сможем генерировать точно такие же числа:

R> set.seed(42)
R> cumsum(rpois(3, 0.3))
[1] 1 2 2
R> cumsum(rpois(4, 0.4))
[1] 1 1 1 2
R>