В C ++, какой самый быстрый способ заменить все вхождения подстроки в строке другой строкой? - PullRequest
14 голосов
/ 11 октября 2011

Я ищу наиболее эффективный (с точки зрения «самого быстрого») способ заменить все вхождения подстроки в строке другой строкой.Все, что я до сих пор придумал, это:

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();
}

... и этот путь (!!!) слишком медленный для моих нужд: - (

С нетерпением ждуучитесь у вас, ребята!

Ответы [ 6 ]

7 голосов
/ 11 октября 2011

Во-первых, я бы использовал 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.(Если before и after имеют одинаковую длину, retval.reserve( original.size() ) - явный выигрыш. Даже если они этого не делают, это может быть выигрыш. Что касается сначала подсчета количества замен, то точного расчета окончательного размера, Я не знаю. Вам придется сравнить с вашими фактическими случаями использования, чтобы узнать.)

3 голосов
/ 11 октября 2011

Я спрашивал об этом то же самое в http://hardforum.com/showthread.php?t=979477 года назад.

Я не очень хорошо помню это, но следующий код был в комментарии # 31 и я думаю, что это было быстрее, чем мои другие попытки (но не быстрее, чем пример metered_string MikeBlas):

#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_all_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
2 голосов
/ 11 октября 2011

Я думаю, что std :: search использует тривиальный алгоритм, по крайней мере ссылка указывает на сложность алгоритма сопоставления наивной строки. Если вы замените его реализацией Boyer-Moore , вы сможете увидеть значительное увеличение производительности.

Кроме того, вы сильно зависите от хороших оптимизаций компилятора. Возвращая строку вместо передачи строки * result, вы вызываете ненужное копирование результата. Тем не менее, компиляторы могут помочь вам там. Но просто чтобы быть уверенным, что вы можете также попытаться передать указатель на результат в качестве аргумента и добавить к этой строке. Однако замена std :: search для реализации нетривиального алгоритма (Boyer-Moore, как упомянуто выше или knuth-morris-pratt) должна иметь влияние, которое больше на несколько порядков по сравнению с тонкой настройкой механизма возврата.

2 голосов
/ 11 октября 2011

Попробуйте это.

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");
1 голос
/ 27 января 2017

Я нашел, что это быстрее:

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;
}

Сравнение с Джеймс Кансес 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

0 голосов
/ 07 июня 2013

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

...