【问题标题】:In C++, what's the fastest way to replace all occurrences of a substring within a string with another string?在 C++ 中,用另一个字符串替换字符串中所有出现的子字符串的最快方法是什么?
【发布时间】:2011-12-05 03:47:44
【问题描述】:

我正在寻找最有效(就“最快”而言)的方法来用另一个字符串替换字符串中所有出现的子字符串。到目前为止,我想出的只是:

std::string StringReplaceAll(const std::string &cstSearch, const std::string &cstReplace, const std::string &cstSubject)
{
    if(cstSearch.length() > cstSubject.length() || cstSearch == cstReplace || cstSubject.empty() || cstSearch.empty() || cstSubject.find(cstSearch) == std::string::npos)
    {
        return cstSubject;
    }

    std::ostringstream                                  ossReturn;
    std::string::const_iterator                         ci(cstSubject.cbegin());
    const std::string::const_iterator::difference_type  ciFindSize(std::distance(cstSearch.cbegin(), cstSearch.cend()));

    for(std::string::const_iterator ciNow; (ciNow = std::search(ci, cstSubject.cend(), cstSearch.cbegin(), cstSearch.cend())) != cstSubject.cend(); ci = ciNow)
    {
        std::copy(ci, ciNow, std::ostreambuf_iterator<char> (ossReturn));
        std::copy(cstReplace.cbegin(), cstReplace.cend(), std::ostreambuf_iterator<char> (ossReturn));
        std::advance(ciNow, ciFindSize);
    }

    std::copy(ci, cstSubject.cend(), std::ostreambuf_iterator<char> (ossReturn));

    return ossReturn.str();
}

...这对我的需求来说太慢了(!!!):-(

期待向你们学习!

【问题讨论】:

  • 相同的算法,但附加到字符串而不是使用字符串流会更有效。
  • 什么是“您的需求”,是什么决定了它们?我们在这里谈论什么样的字符串长度?字符串从哪里来?优化需要针对特定​​条件进行优化。
  • 谢谢!使用 std::string 而不是 std::ostringstream 确实更有效!现在也将研究 Boost 库!

标签: c++ string performance stl str-replace


【解决方案1】:

我发现这个更快:

typedef std::string String;

String replaceStringAll(String str, const String& old, const String& new_s) {
    if(!old.empty()){
        size_t pos = str.find(old);
        while ((pos = str.find(old, pos)) != String::npos) {
             str=str.replace(pos, old.length(), new_s);
             pos += new_s.length();
        }
    }
    return str;
}

James kanzes replaceAll函数的比较:

replaceAll      : 28552
replaceStringAll: 33518
replaceAll      : 64541
replaceStringAll: 14158
replaceAll      : 20164
replaceStringAll: 13651
replaceAll      : 11099
replaceStringAll: 5650
replaceAll      : 23775
replaceStringAll: 10821
replaceAll      : 10261
replaceStringAll: 5125
replaceAll      : 10283
replaceStringAll: 5374
replaceAll      : 9993
replaceStringAll: 5664
replaceAll      : 10035
replaceStringAll: 5246
replaceAll      : 8570
replaceStringAll: 4381

时间以纳秒计算,std::chrono::high_resolution_clock

【讨论】:

【解决方案2】:

我会提出一个优化步骤:做一个初步的通过来检查是否有任何东西可以替换。如果没有什么可替换的,就直接返回,避免分配内存和复制。

【讨论】:

  • 我自己也想知道这一点。 -O3 已经用string::replace 处理这个问题了吗?我很想看看基准
【解决方案3】:

几年前我在http://hardforum.com/showthread.php?t=979477 询问过同样的事情。

我记不太清了,但是下面的代码在评论 #31 中,我认为它比我的其他尝试快(但不比 MikeBlas 的 Metered_string 示例快):

#include <iostream>
#include <string>
#include <algorithm>
#include <iterator>
#include <sstream>

using namespace std;

inline string replaceAll(const string& s, const string& f, const string& r) {
    if (s.empty() || f.empty() || f == r || f.size() > s.size() || s.find(f) == string::npos) {
        return s;
    }
    ostringstream build_it;
    typedef string::const_iterator iter;
    iter i(s.begin());
    const iter::difference_type f_size(distance(f.begin(), f.end()));
    for (iter pos; (pos = search(i , s.end(), f.begin(), f.end())) != s.end(); ) {
        copy(i, pos,  ostreambuf_iterator<char>(build_it));
        copy(r.begin(), r.end(), ostreambuf_iterator<char>(build_it));
        advance(pos, f_size);
        i = pos;
    }
    copy(i, s.end(), ostreambuf_iterator<char>(build_it));
    return build_it.str();
}

int main() {
    const string source(20971520, 'a');
    const string test(replaceAll(source, "a", "4"));
}

查看主题以获得更多示例和大量讨论。

如果我没记错的话,让事情变得比 boost 的 replace_all 更快真的很容易。

这是一个更清晰的 c++0x 版本:

#include <string>
#include <algorithm>
#include <iterator>
#include <sstream>
#include <ostream>
using namespace std;

string replace_all_copy(const string& s, const string& f, const string& r) {
    if (s.empty() || f.empty() || f == r || f.size() > s.size()) {
        return s;
    }
    ostringstream buffer;
    auto start = s.cbegin();
    while (true) {
        const auto end = search(start , s.cend(), f.cbegin(), f.cend());
        copy(start, end,  ostreambuf_iterator<char>(buffer));
        if (end == s.cend()) {
            break;
        }
        copy(r.cbegin(), r.cend(), ostreambuf_iterator<char>(buffer));
        start = end + f.size();
    }
    return buffer.str();
}

int main() {
    const string s(20971520, 'a');
    const string result = replace_all_copy(s, "a", "4");
}

// g++ -Wall -Wextra replace_all_copy.cc -o replace_all_copy -O3 -s -std=c++0x

【讨论】:

    【解决方案4】:

    我认为 std::search 使用了一个简单的算法,至少the reference 说明了一个简单的字符串匹配算法的复杂性。如果您将其替换为 Boyer-Moore 的实现,您应该能够看到显着的性能提升。

    除此之外,您还严重依赖良好的编译器优化。通过返回字符串而不是传递 string* 结果,会导致不必要的结果复制。但是,编译器可能会帮助您。但只是为了确保您可以尝试将指向结果的指针作为参数传递并附加到该字符串。但是,与微调返回机制相比,交换 std::search 以实现非平凡算法(上面提到的 boyer-moore 或 knuth-morris-pratt)的影响应该大几个数量级。

    【讨论】:

      【解决方案5】:

      试试这个。

      template<class T> inline void Replace(T& str, const T& str1, const T& str2)
      {
          const T::size_type str2Size(str2.size());
          const T::size_type str1Size(str1.size());
      
          T::size_type n = 0;
          while (T::npos != (n = str.find(str1, n))) {
              str.replace(n, str1Size, str2);
              n += str2Size;
          }
      }
      
      std::string val(L"abcabcabc");
      Replace(val, L"abc", L"d");
      

      【讨论】:

        【解决方案6】:

        首先,我会使用std::string,而不是std::ostringstream,来构建 提高结果; std::ostringstream 用于格式化,没有 格式化在这里完成。除此之外,您基本上拥有 正确的算法;使用std::search 查找下一个位置 应该更换。我会使用while 循环来做一些事情 更具可读性,这给出了:

        std::string
        replaceAll( std::string const& original,
                    std::string const& before,
                    std::string const& after )
        {
            std::string retval;
            std::string::const_iterator end     = original.end();
            std::string::const_iterator current = original.begin();
            std::string::const_iterator next    =
                    std::search( current, end, before.begin(), before.end() );
            while ( next != end ) {
                retval.append( current, next );
                retval.append( after );
                current = next + before.size();
                next = std::search( current, end, before.begin(), before.end() );
            }
            retval.append( current, next );
            return retval;
        }
        

        (注意使用std::string::append会比使用快 std::copy;字符串知道它必须添加多少,并且可以调整 相应的字符串。)

        之后,抓住特殊情况将是微不足道的 无需替换,立即返回初始字符串;那里 使用std::string::reserve 可能会有一些改进 好吧。 (如果beforeafter 的长度相同, retval.reserve( original.size() ) 是一个明显的胜利。即使他们没有, 这可能是一场胜利。至于先计算换人次数,然后 精确计算最终尺寸,我不知道。你必须 用你的实际用例进行测量以找出答案。)

        【讨论】:

          猜你喜欢
          • 2013-07-10
          • 1970-01-01
          • 1970-01-01
          • 2011-08-14
          • 2011-06-06
          • 1970-01-01
          • 2017-12-31
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多