将字符串拆分为相等长度的子字符串的更快方法

时间:2016-01-12 14:40:52

标签: c++ r rcpp

我想创建一个函数,将字符串拆分为长度相等n的字符串,并返回一个字符向量。

e.g。 F('atgctgttg',n=5)应该返回

'atgct','tgctg','gctgt','ctgtt','tgttg'

我尝试了两种不同的功能:

// [[Rcpp::export]]
CharacterVector f( const std::string str, const int n ) {
    int lim = str.length() - n + 1;
    CharacterVector result( lim );
    for ( int j = 0; j < lim; j++ )
    { 
        result[j] = str.substr( j, n );
    }
    return result;
}

// [[Rcpp::export]]
CharacterVector f1( const std::string str, const int n ) {
    const int lim = str.length();
    const int n1 = n - 1;
    CharacterVector result( lim - n1 );
    int j = 1;
    std::string tmp = str.substr( 0, n );
    result[0] = tmp;

    for ( int i = n; i < lim; i++ )
    {
        tmp.erase( 0, 1 );
        tmp.push_back( str[i] );
        result[j] = tmp;
        j++;
    }
    return result;
}

我也尝试使用迭代器,但它并不比函数f1快。 请注意,Rcpp将输入转换为引用变量。 有更快的方法吗?

3 个答案:

答案 0 :(得分:1)

我将使用的方法是创建一个迭代器到字符串的开头,一个迭代器到第一个子字符串的结尾。然后使用std::vector使用emplace_back()在作为子字符串的向量的末尾构造一个字符串。然后递增两个迭代器,直到结束。

std::vector<std::string> splitString(const std::string& str, std::size_t len)
{
    if (len >= str.size())
        return { str };
    auto it = str.begin();
    auto end = it + len;
    std::vector<std::string> strings;
    while (end != str.end())
    {
        strings.emplace_back(it, end);
        ++end;
        ++it;
    }
    // have to do this to get the last string since end == str.end()
    strings.emplace_back(it, end);
    return strings;
}

Live Example

答案 1 :(得分:1)

编译器会将您的 string _text = Convert.ToString(btn.Text); int iSelectionStart = txtCal.SelectionStart; string sBefore = txtCal.Text.Substring(0, iSelectionStart); string sAfter = txtCal.Text.Substring(iSelectionStart + txtCal.SelectionLength); txtCal.Text = sBefore + _text + sAfter; txtCal.SelectionStart = iSelectionStart; txtCal.SelectionLength = _text.Length; 函数转换为最快的代码,如果您更改为通过引用复制:f

虽然您未能看到速度提升,但您可以通过取消CharacterVector f(const std::string& str, const int n)并使用CharacterVector来简化您的流程:

vector<string>

[Live Example]

如果您可以使用const string str("atgctgttg"); const int n = 5; // Assumed positive number smaller than str.size() const int n1 = n - 1; vector<string> result(str.size() - n1); transform(str.cbegin(), str.cend() - n1, result.begin(), [n](const auto& i) {return string(&i, n);}); 代替array可以看到速度提升的一种方式:

string

[Live Example]

但到目前为止,执行此操作的最快(最好)方法只是处理原始const string str("atgctgttg"); const int n1 = N - 1; vector<array<char, N>> result(str.size() - n1); transform(str.cbegin(), str.cend() - n1, result.begin(), [](const auto& i) { array<char, N> result; copy_n(&i, N, result.begin()); return result; }); 而不是将其分解为string的数组。这需要在后端进行更多工作,因为您需要使用c-strings而不是string s。例如,我已使用std::string打印我的所有for (auto& i : result) cout << string(i.data(), N) << endl;,但如果您没有使用vector,则可以打印出:vector显然需要更多工作,但如果你的for (auto i = str.cbegin(); i != str.cend() - n1; ++i) printf("%.*s\n", n, &*i);很大,你会发现它更快。

[Live example]

答案 2 :(得分:0)

首先,您的功能签名存在问题:

CharacterVector f( const std::string str, const int n )

您通过值传递string,在函数的每次调用中都会有一个字符串副本(除非您使用C ++ 11传递可移动字符串)。最好通过const引用const std::string& str传递字符串。

关于这个问题,我想到了两个可能的答案。

  1. 实际上返回输入字符串的字符副本。在这种情况下,通过索引迭代字符串并在结构中插入新字符串(如代码示例1中)应该很快(可能更快的只有1个副本,子串的副本到结构)。
  2. 返回指向真实字符串的指针结构。例如:返回包含字符串中子字符串(start,end)的代理对象。优点是不是字符串的副本。例如:
  3. 代码(已测试:GCC 4.9.2与C ++ 11)

    #include <iostream>
    #include <vector>
    
    struct string_ref {
        const char* start;
        const char* end;
    };
    
    // [[Rcpp::export]]
    std::vector<string_ref> f(std::string&&, const int) = delete; // disallow calls with temporaries
    // [[Rcpp::export]]
    std::vector<string_ref> f(const std::string& str, const int n) {
        int lim = str.length() - n + 1;
        std::vector<string_ref> result(lim);
        for (int j = 0; j < lim; j++) {
            result[j] = { &str[j], &str[j + n] };
        }
        return result;
    }
    
    int main() {
        std::string input{"atgctgttg"};
        auto result = f(input, 5);
        for (const auto r : result) {
            std::cout << std::string(r.start, r.end) << std::endl;
        }
        return 0;
    }
    

    许多解析文本的库使用此方法(例如:词法分析器,正则表达式引擎等)。对于C ++ 17,有一个建议类型std::string_view,用于引用部分或全部字符串字符。

    根据代码中的注释,您正在实现在R中使用的功能(完全不知道),在这种情况下,第二个解决方案可能会带来内存访问问题(输入字符串内存需要使用子串指针时可访问和实时)。如果输入字符串是在R中创建并调用F,则返回指针很可能是有效的,则需要测试更好的证据。

    问题中的代码2示例。第一个是更快的,因为在每个循环的第二个中,有一个字符的擦除和push_back(在大多数STL实现中擦除第一个字符很可能需要字符串的所有其他字符的副本) ,在某些情况下,push_back可能需要扩展字符串的内存。