单通搜索&更换

时间:2013-08-18 13:42:40

标签: c++ c performance

有没有人知道如何进行单程搜索&替换为文本?我正在开发一个高性能程序,每个微优化都很重要。以下示例说明了我目前的工作:

#include <iostream>
#include <string>

/*!
\brief Replaces all common character escape sequences with text representations
\note Some character seqences messes up the trace output and must be replaces
* with text represantions, ie. the newline character will the replaced with "\n"
* etc.
\returns The formatted string
*/
std::wstring ReplaceAll(std::wstring &str)
{
    SearchAndReplace(str, L"\a", L"\\a");   // control-g, C-g
    SearchAndReplace(str, L"\b", L"\\b");   // backspace, <BS>, C-h
    SearchAndReplace(str, L"\t", L"\\t");   // tab, <TAB>, C-i
    SearchAndReplace(str, L"\n", L"\\n");   // newline, C-j
    SearchAndReplace(str, L"\v", L"\\v");   // vertical tab, C-k
    SearchAndReplace(str, L"\f", L"\\f");   // formfeed character, C-l
    SearchAndReplace(str, L"\r", L"\\r");   // carriage return, <RET>, C-m
    return str;
}

/*!
\brief Wide string search and replace
\param str [in, out] String to search & replace
\param oldStr [in] Old string
\param newStr [in] New string
*/
std::wstring SearchAndReplace(std::wstring &str, const wchar_t *oldStr, const wchar_t *newStr) const
{
    size_t oldStrLen = wcslen(oldStr);
    size_t newStrLen = wcslen(newStr);
    size_t pos = 0;

    while((pos = str.find(oldStr, pos)) != string::npos)
    {
        str.replace(pos, oldStrLen, newStr);
        pos += newStrLen;
    }

    return str;
}

int main()
{
    std::wstring myStr(L"\tThe quick brown fox jumps over the lazy dog.\n\tThe quick brown fox jumps over the lazy dog\n\n");
    std::wcout << L"Before replace: " << myStr;
    std::wcout << L"After replace: " << ReplaceAll(myStr);
    return 0;
}

上面的代码显然效率低,因为它需要多次遍历同一个字符串。单通搜索&amp; replace函数应该非常灵活,它可以处理要替换的不同字符数组(即不仅仅是ReplaceAll()中列出的转义字符。)

5 个答案:

答案 0 :(得分:2)

您可以使用哈希表来存储<from,to>的所有对,并在字符串上运行一次。

对于每个char都要检查它是否存在于哈希表中,如果存在则替换它。

它将一次性完成任务。

答案 1 :(得分:2)

对于手头的任务,您不需要任何复杂的算法!首先,您要搜索的“字符串”实际上是字符和不同的字符串(另一个答复中提到的更复杂的算法是处理与序列匹配的字符串列表) 。此外,您的主要问题是您不断调整序列大小。无论如何,你无法在原地进行替换,因为每次替换时弦都会增长。一个相当简单的方法应该具有很多比当前方法更好的性能,并且据我所知,你非常远离启动微优化 - 你需要首先让你的代码以正确的方式做事。例如,我会尝试这些行:

struct match_first
{
    wchar_t d_c;
    match_first(wchar_t c): d_c(c) {}
    template <typename P>
    bool operator()(P const& p) const { return p.first == this->d_c; }
};

void Replace(std::wstring& value)
{
    std::wstring result;
    result.reserve(value.size());
    std::wstring special(L"\a\b\f\n\r\t\v");
    std::pair<wchar_t, std::wstring> const replacements[] = {
        std::pair<wchar_t, std::wstring>(L'\a', L"\\a"),
        std::pair<wchar_t, std::wstring>(L'\b', L"\\b"),
        std::pair<wchar_t, std::wstring>(L'\f', L"\\f"),
        std::pair<wchar_t, std::wstring>(L'\n', L"\\n"),
        std::pair<wchar_t, std::wstring>(L'\r', L"\\r"),
        std::pair<wchar_t, std::wstring>(L'\t', L"\\t"),
        std::pair<wchar_t, std::wstring>(L'\v', L"\\v")
    };

    std::wstring::size_type cur(0);
    for (std::wstring::size_type found(cur);
         std::wstring::npos != (found = value.find_first_of(special, cur));
         cur = found + 1) {
        result.insert(result.end(),
                      value.begin() + cur, value.begin() + found);

        std::pair<wchar_t, std::wstring> const* replacement
            = std::find_if(std::begin(replacements), std::end(replacements),
                           match_first(value[found]));
        result.insert(result.end(),
                      replacement->second.begin(), replacement->second.end());
    }
    result.insert(result.end(), value.begin() + cur, value.end());

    value.swap(result);
}

该算法的想法是对源字符串进行单次传递,找到需要替换的所有字符串,如果发现有一个不被替换字符的部分,则将替换字符串复制到新字符串建立。通过一些努力可以使得一些事情变得更快一些,但是这个只移动每个字符一次而不是原始代码,这使得字符的尾部不会被一个字符向前看,而每个字符都要被替换。< / p>

答案 2 :(得分:0)

有许多算法用于在线性时间内执行字符串搜索。即使它们中的大多数是字符串搜索算法,您也可以实现它们以在线性时间内执行字符串搜索和替换。它们的线性运行时需要一些预处理,请务必仔细阅读前提条件 very 。清单:

要使用这些字符串搜索算法来实现搜索和替换算法,您需要执行以下操作(请注意,我所做的分析专门针对KMP):

  1. 使用KMP查找句子中所有出现的给定单词(跟踪其起始索引及其找到的单词)。将它们存储在一个hashmap中,为我们提供常量查找和插入。
  2. 创建一个新的动态字符串(考虑动态数组ADT)。
  3. 迭代句子中的每个字符以及每个位置i,检查该位置是否在您的hashmap中。如果是这样,找到相应的单词替换(存储在另一个散列映射中 - O(1)查找)并一次替换一个字符,直到j - i,其中{{1是你的下一个位置,大于你的替换的字符串长度减1,继续并重复,直到你迭代整个句子。
  4. 备注:对于第3步,您将逐个将字符复制到新字符串中。如果您找到了某个字词,则会复制该替换字词并跳过j个位置,其中k是匹配字词的字符串长度。

    ...最后:释放与原始字符串关联的任何内存,并返回新字符串或将指针设置为等于新字符串。

    最后这应该是k,因此是线性时间。

答案 3 :(得分:0)

搜索和替换中的主要问题是尝试就地执行它通常效率很低。创建一个新字符串是O(n)时间和空间;就地替换很难做到。

可以在字符串上进行两次传递;第一个只计算结果的长度(或者可能构造一个分散 - 收集列表)。完成后,如果需要,可以调整字符串的大小,然后可以完成替换传递,从字符串的末尾开始并朝着开头工作。然而,根据我的经验,这是很多编码的很少的价值。 (此外,如果某些替换是删除,它也不起作用。)

所以我使用类似下面的内容(使用C ++ 11 lambdas,只是因为它),虽然它不是最理想的:更好的方法是对替换向量进行排序以便可以使用二进制搜索,或者 - 在替换控制字符的情况下 - 将它们放入由目标字符索引的向量(具有最小值和最大值),以便查找只需要两次比较。 (或者你可以构建一个只需要一次比较的压缩表,但这也是很多工作。)

#include <algorithm>
#include <initializer_list>
#include <string>
#include <utility>
#include <vector>

template<typename Char, typename String=std::basic_string<Char>>
class Translator {
  public:
    Translator(const std::initializer_list<std::pair<Char, String>> trans) {
      std::for_each(trans.begin(), trans.end(),
                    [&](const std::pair<Char, String>& fromto) {
                    from_.push_back(fromto.first);
                    to_.push_back(fromto.second);
                    });
    }
    void push_translated(String& res, Char ch) { 
      size_t pos = from_.find(ch);
      if (pos == String::npos) res += ch; else res += to_[pos];
    }
    String translate(const String& orig) {
      String rv;
      std::for_each(orig.begin(), orig.end(), [&](Char ch){push_translated(rv, ch);});
      return rv;
    }
  private:
    String from_;
    std::vector<String> to_;
};

上面的初始化列表的使用很可爱,但是也应该有一个构造函数,它需要std::mapstd::vector对或类似的,因为有时候你会想要在运行时构建替换,而不是在编译时。

如果您想使用上面的代码,这里有一个简单的驱动程序(对于您的应用程序,您可能需要Translator<wchar_t>):

#include <iostream>
int main(int argc, char** argv) {
  Translator<char> trans{
    {'\a', "\\a"},
    {'\b', "\\b"},
    {'\f', "\\f"},
    {'\n', "\\n"},
    {'\r', "\\r"},
    {'\t', "\\t"},
    {'\v', "\\v"}
  };
  for (int i = 1; i < argc; ++i) {
    std::cout << trans.translate(argv[i]) << std::endl;
  }
  return 0;
}

答案 4 :(得分:0)

如果您正在寻找更好的性能,那么您的代码可能效率低下,因为这一行:

  

str.replace(pos,oldStrLen,newStr);

这可能导致:

  1. 字符串重新分配(如果新字符串比旧字符串长)。
  2. 内存移动操作(如果新字符串比旧字符串更短或更长,那么其后的所有字符都将移动到新位置)
  3. 一个愚蠢的STL实现甚至可以为每个替换动态分配缓冲区。 内存分配可能很慢,很容易变成瓶颈。

    将输入和输出字符串/缓冲区分开,并将输出缓冲区预分配为大于输入缓冲区的字符串可能更有效。如果将输入和输出字符串分开,则可以保证程序将复制inputString.size()个字符,并且只有一个初始内存分配(可预测的性能)。如果你就地替换字符,那么很可能不会移动任何字符并且不会发生重新分配,并且每次替换字符串中的每个字符都会被移动多次(更难以预测性能)和新的/ delete将被多次调用。

    替换可以这样做:

    1. 预分配大于输入字符串的空输出字符串(使用reserve())。
    2. 重复直到你的字符用完为止:
      1.1阅读几个字符 2.2。看看它们是否符合您要替换的内容。 (如果在map / hashtable中存储可替换字符,可能会非常快,可以有o(log n)或o(1)查找速度)。
      3.3。如果它们匹配您想要替换的内容,请编写新字符串。如果不这样做,则写字符不变。
    3. 另见markov chains