【问题标题】:Concatenate StringVector with Rcpp将 StringVector 与 Rcpp 连接起来
【发布时间】:2017-08-28 03:57:03
【问题描述】:

我不知道如何用 Rcpp 连接 2 个字符串;当我怀疑有一个明显的答案时,文档并没有帮助我。

http://gallery.rcpp.org/articles/working-with-Rcpp-StringVector/

http://gallery.rcpp.org/articles/strings_with_rcpp/

StringVector concatenate(StringVector a, StringVector b)
{
 StringVector c;
 c= ??;
 return c;
}

我希望这个输出:

a=c("a","b"); b=c("c","d");
concatenate(a,b)
[1] "ac" "bd"

【问题讨论】:

    标签: c++ r concatenation rcpp


    【解决方案1】:

    一个可行的解决方案是使用:

    #include <Rcpp.h>
    using namespace Rcpp;
    
    // [[Rcpp::export]]
    CharacterVector concatenate(std::string x, std::string y)
    {
                   return wrap(x + y);
    }
    

    然后:

    Vconcatenate=Vectorize(concatenate)
    Vconcatenate(letters[1:2],letters[3:4])
    

    或者:

    // [[Rcpp::export]]
    CharacterVector concatenate(std::vector<std::string> x,std::vector<std::string> y)
    {
      std::vector<std::string> res(x.size());
      for (int i=0; i < x.size(); i++)
      {
        res[i]=x[i]+y[i];
      }
      return wrap(res);
    }
    

    【讨论】:

    • 为什么说不完全Rcpp?当然是这样,否则您如何获得胶水和Rcpp::CharacterVector 类型?但是您忘记了必需的#includeRcpp::export 标签。
    • 我指的是Vectorize函数的使用
    • 胶水也适用于std::vector&lt;std::string&gt;;然后你可以在 . 内循环
    • 好的,我添加这个解决方案
    【解决方案2】:

    可能有几种不同的方法来解决这个问题,但这是std::transform 的一种选择:

    #include <Rcpp.h>
    using namespace Rcpp;
    
    struct Functor {
        std::string
        operator()(const std::string& lhs, const internal::string_proxy<STRSXP>& rhs) const
        {
            return lhs + rhs;
        }
    };
    
    // [[Rcpp::export]]
    CharacterVector paste2(CharacterVector lhs, CharacterVector rhs)
    {
        std::vector<std::string> res(lhs.begin(), lhs.end());
        std::transform(
            res.begin(), res.end(),
            rhs.begin(), res.begin(),
            Functor()
        );
        return wrap(res);
    }
    
    /*** R
    
    lhs <- letters[1:2]; rhs <- letters[3:4]
    
    paste(lhs, rhs, sep = "")
    # [1] "ac" "bd"
    
    paste2(lhs, rhs)
    # [1] "ac" "bd"
    
    */ 
    

    首先将左侧表达式复制到std::vector&lt;std::string&gt; 的原因是internal::string_proxy&lt;&gt;provides operator+ 带有签名

    std::string operator+(const std::string& x, const internal::string_proxy<STRSXP>& y) 
    

    而不是,例如

    operator+(const internal::string_proxy<STRSXP>& x, const internal::string_proxy<STRSXP>& y) 
    

    如果你的编译器支持 C++11,这可以稍微干净一点:

    // [[Rcpp::plugins(cpp11)]]
    #include <Rcpp.h>
    using namespace Rcpp;
    
    // [[Rcpp::export]]
    CharacterVector paste3(CharacterVector lhs, CharacterVector rhs)
    {
        using proxy_t = internal::string_proxy<STRSXP>;
    
        std::vector<std::string> res(lhs.begin(), lhs.end());
        std::transform(res.begin(), res.end(), rhs.begin(), res.begin(),
            [&](const std::string& x, const proxy_t& y) {
                return x + y;
            }
        );
    
        return wrap(res);
    }
    
    /*** R
    
    lhs <- letters[1:2]; rhs <- letters[3:4]
    
    paste(lhs, rhs, sep = "")
    # [1] "ac" "bd"
    
    paste3(lhs, rhs)
    # [1] "ac" "bd"
    
    */
    

    【讨论】:

    • 为了更好地理解,您能否简要评论一下internal::string_proxy&lt;STRSXP&gt;&amp;String的关系以及为什么不能使用String
    • @Dominik 简而言之,它们并不真正相关; string_proxy 基本上是当单个元素为 accessed in a Vector 时返回的轻量级包装类(即 proxy class)。这种方法可以在不实际存储(“拥有”)SEXP 本身的情况下,使用附加功能(例如多个构造函数、运算符重载等)来装饰原本可能是 CHARSXPconst char* 的东西.
    • 由于string_proxy 仅包含对给定CharacterVector 中特定元素的引用,这使得修改能够通过代理对象并影响父向量(例如通过Vector::operator[])。另一方面,String 更像是一个功能齐全的字符串类。而string_proxy 只包含一个static std::string 缓冲区、一个索引和一个指向其父Vector 的指针,而String 包含more data members,因此它需要比等效代理对象更多的内存。
    • 此外,由于 String “拥有”其基础数据(SEXP data 成员)而不是持有对 CHARSXP 的引用,因此对 String 的修改将仅影响该对象没有别的了。注意这两个对象的行为有何不同in this example
    【解决方案3】:

    我将保留这个答案,但请注意@nrussell 提供的关于使用push_back() 的警告!


    我自己还在掌握Rcpp,所以我在循环中寻找了一个字符串生成器

    library(Rcpp)
    
    cppFunction('StringVector concatenate(StringVector a, StringVector b)
    {
      StringVector c;
      std::ostringstream x;
      std::ostringstream y;
    
     // concatenate inputs
      for (int i = 0; i < a.size(); i++)
        x << a[i];
    
      for (int i = 0; i < b.size(); i++)
        y << b[i];
    
      c.push_back(x.str());
      c.push_back(y.str());
    
      return c;
    
    }')
    
    a=c("a","b"); b=c("c","d");
    concatenate(a,b)
    # [1] "ab" "cd" 
    

    比较 (i) 重复调用 push_back 与 (ii) 预分配和填充策略的性能,我们可以看到后者更可取:

    #include <Rcpp.h>
    using namespace Rcpp;
    
    // [[Rcpp::export]]
    CharacterVector pbpaste(CharacterVector lhs, CharacterVector rhs)
    {
        R_xlen_t i = 0, sz = lhs.size();
        CharacterVector res;
    
        for (std::ostringstream oss; i < sz; i++, oss.str("")) {
            oss << lhs[i] << rhs[i];
            res.push_back(oss.str());
        }
    
        return res;
    }
    
    // [[Rcpp::export]]
    CharacterVector sspaste(CharacterVector lhs, CharacterVector rhs)
    {
        R_xlen_t i = 0, sz = lhs.size();
        CharacterVector res(sz);
    
        for (std::ostringstream oss; i < sz; i++, oss.str("")) {
            oss << lhs[i] << rhs[i];
            res[i] = oss.str();
        }
    
        return res;
    }
    
    /*** R
    
    lhs <- as.character(1:5000); rhs <- as.character(5001:10000)
    
    all.equal(pbpaste(lhs, rhs), sspaste(lhs, rhs))
    # [1] TRUE
    
    microbenchmark::microbenchmark(
        "push_back" = pbpaste(lhs, rhs),
        "preallocate" = sspaste(lhs, rhs),
        times = 200L
    )
    # Unit: milliseconds
    #         expr        min         lq       mean     median         uq        max neval cld
    #    push_back 101.521579 105.334649 115.156544 107.275678 110.957420 256.722239   200   b
    #  preallocate   1.364213   1.585818   1.789564   1.778153   1.934758   2.955352   200   a
    
    */
    

    【讨论】:

    • 正如您所说,您是 Rcpp 新手,请注意,最好尽可能避免在 Rcpp *Vector 类型上使用 push_backVector 类不使用内存分配器,因此此函数的效率远低于例如std::vector 对应方。当然,对于长度为 2 的对象,这可以忽略不计,但即使对于中等大小的对象,差异也可能很大。
    • @nrussell 有趣 - 很高兴知道。我也刚刚注意到我的输出与 OP 要求的不同......
    • @nrussell - 我要删除这个答案;但我认为您的评论应该包含在您的回答中,作为对其他人的说明/警告?
    • @nrussell - 是的,点了:) - 我已将您的要点添加到我的答案中,以作为对其他人的警告
    • 我希望你不介意,但我在你的问题中添加了一个比较来突出这一点,因为它比我的要点中的人为示例更直接适用。
    猜你喜欢
    • 2018-03-21
    • 1970-01-01
    • 2017-09-24
    • 2014-08-16
    • 2023-03-29
    • 2020-01-20
    • 2012-03-10
    • 2022-01-19
    • 2013-01-27
    相关资源
    最近更新 更多