【问题标题】:Fast string splitting with multiple delimiters使用多个分隔符进行快速字符串拆分
【发布时间】:2011-07-27 05:33:49
【问题描述】:

我在 StackOverflow 上研究了一些时间,以找到将具有多个分隔符的字符串拆分为 vector< string > 的良好算法。我也找到了一些方法:

升压方式:

boost::split(vector, string, boost::is_any_of(" \t"));

getline 方法:

std::stringstream ss(string);
std::string item;
while(std::getline(ss, item, ' ')) {
    vector.push_back(item);
}

Boost的tokenize方式:

char_separator<char> sep(" \t");
tokenizer<char_separator<char>> tokens(string, sep);
BOOST_FOREACH(string t, tokens)
{
   vector.push_back(t);
}

还有很酷的 STL 方式:

     istringstream iss(string);
     copy(istream_iterator<string>(iss),
     istream_iterator<string>(),
     back_inserter<vector<string> >(vector));

以及Shadow2531的方法(见链接主题)。

大部分来自this topic。但不幸的是,他们并没有解决我的问题:

  • Boost 的拆分很容易使用,但是对于大数据(在最佳情况下大约 1.5*10^6 个单个元素)和大约 10 个分隔符,我使用它的速度非常慢。

  • getline、STL 和 Shadow2531 的方法存在我只能使用一个字符作为分隔符的问题。我还需要一些。

  • Boost 的标记化在速度方面更加可怕。用 10 个分隔符将一个字符串拆分为 1.5*10^6 个元素需要 11 秒。

所以我不知道该怎么办:我想要一个非常快速的字符串拆分算法,带有多个分隔符。

Boost 的拆分是最大的还是有办法更快?

【问题讨论】:

  • 你试过多线程吗?例如,将您的 delim 分成 N 组,并为每个组运行一个线程以按该 delim 组拆分字符串并填充一个列表,然后重新组合列表?
  • 一定有更快的方法。这:cat loremipsum_big.txt | ruby -e "ary = Array.new; ARGF.each {|x| ary &lt;&lt; x.split(/a| /)}; puts ary" | wc -l 创建 2.516.715 个元素,在 Virtualboxed Ubuntu 中在 3.74 秒内将它们推送到一个数组中。
  • 以下文章中的一些示例怎么样:codeproject.com/KB/recipes/Tokenizer.aspx 它们非常有效。字符串工具包库使 C++ 中的复杂字符串处理变得简单易行。

标签: c++ string boost split performance


【解决方案1】:

想到两件事:

  1. 使用字符串视图而不是字符串 作为拆分结果,节省了很多 分配。
  2. 如果你知道你只是 将使用字符(在 [0,255] 范围),尝试使用 bitset 来测试成员资格而不是 finding 进入分隔符 字符。

以下是应用这些想法的快速尝试:

#include <vector>
#include <bitset>
#include <iostream>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/timer.hpp>

using namespace std;
size_t const N = 10000000;

template<typename C>
void test_custom(string const& s, char const* d, C& ret)
{
  C output;

  bitset<255> delims;
  while( *d )
  {
    unsigned char code = *d++;
    delims[code] = true;
  }
  typedef string::const_iterator iter;
  iter beg;
  bool in_token = false;
  for( string::const_iterator it = s.begin(), end = s.end();
    it != end; ++it )
  {
    if( delims[*it] )
    {
      if( in_token )
      {
        output.push_back(typename C::value_type(beg, it));
        in_token = false;
      }
    }
    else if( !in_token )
    {
      beg = it;
      in_token = true;
    }
  }
  if( in_token )
    output.push_back(typename C::value_type(beg, s.end()));
  output.swap(ret);
}

template<typename C>
void test_strpbrk(string const& s, char const* delims, C& ret)
{
  C output;

  char const* p = s.c_str();
  char const* q = strpbrk(p+1, delims);
  for( ; q != NULL; q = strpbrk(p, delims) )
  {
    output.push_back(typename C::value_type(p, q));
    p = q + 1;
  }

  output.swap(ret);
}

template<typename C>
void test_boost(string const& s, char const* delims)
{
  C output;
  boost::split(output, s, boost::is_any_of(delims));
}

int main()
{
  // Generate random text
  string text(N, ' ');
  for( size_t i = 0; i != N; ++i )
    text[i] = (i % 2 == 0)?('a'+(i/2)%26):((i/2)%2?' ':'\t');

  char const* delims = " \t[],-'/\\!\"§$%&=()<>?";

  // Output strings
  boost::timer timer;
  test_boost<vector<string> >(text, delims);
  cout << "Time: " << timer.elapsed() << endl;

  // Output string views
  typedef string::const_iterator iter;
  typedef boost::iterator_range<iter> string_view;
  timer.restart();
  test_boost<vector<string_view> >(text, delims);
  cout << "Time: " << timer.elapsed() << endl;

  // Custom split
  timer.restart();
  vector<string> vs;
  test_custom(text, delims, vs);
  cout << "Time: " << timer.elapsed() << endl;

  // Custom split
  timer.restart();
  vector<string_view> vsv;
  test_custom(text, delims, vsv);
  cout << "Time: " << timer.elapsed() << endl;

  // Custom split
  timer.restart();
  vector<string> vsp;
  test_strpbrk(text, delims, vsp);
  cout << "Time: " << timer.elapsed() << endl;

  // Custom split
  timer.restart();
  vector<string_view> vsvp;
  test_strpbrk(text, delims, vsvp);
  cout << "Time: " << timer.elapsed() << endl;

  return 0;
}

使用启用-O4 标志的GCC 4.5.1 使用Boost 1.46.1 编译它,我得到:

  • 时间:5.951(Boost.Split + 矢量)
  • 时间:3.728(Boost.Split + 矢量
  • 时间:1.662(自定义分割+向量)
  • 时间:0.144(自定义分割+向量)
  • 时间:2.13(Strpbrk + 矢量)
  • 时间:0.527(Strpbrk + 矢量)

注意:由于我的自定义函数删除了空标记,因此输出略有不同。但是,如果您决定使用它,您可以根据您的需要调整此代码。

【讨论】:

  • 嗨,巴勃罗,感谢您的回答!我试图使您的解决方案适应我的代码,但未能正确使用它: 1. 问题:我不知道如何让 test_custom 函数返回 C - 我也不知道在哪里添加“返回”。 2. 问题:vector 似乎更好,但我正在分配字符串,所以我需要一个 vector 或者我可以使用字符串的 const_iterators 吗? |我在这里粘贴了我的代码的简化版本:pastebin.com/0k6wNtFe
  • 查看我的编辑。它现在需要一个输出参数来为其分配输出标记。至于字符串视图,只要原始字符串有效,它们就应该保持有效。如果您打算使用 Boost 字符串算法与它们一起工作,则视图被视为有效字符串,只是它们不拥有它们的内容。我不知道你所说的“分配字符串”是什么意思,所以如果你能解释一下,你可以获得一些关于如何继续的建议。
  • 感谢您编辑您的方法。我对其进行了调整,它有点工作,但整个功能给我带来了麻烦。 strpbrk 对我来说用作查找分隔符更有意义,但不知何故这会使事情变得混乱。同样在这种状态下,它几乎与 boost 的带有 vector 的 split 方法一样快/慢。 pastebin.com/WU9wfEqg
  • 啊,我差点忘了:这个问题主要来自函数 strpbrk,因为它无法检测到空格。我怎样才能用那个功能做到这一点?
  • 嗯,问题是在循环中使用strpbrk 会使函数对字符串内容的迭代次数远远超过需要的次数。我向您展示的原始函数检查了字符串中的每个字符一次。如果一个单词有 N 个字符,则使用 strpbrk,而不是检查 N 次,而是检查 N*(N+1)/2。
【解决方案2】:

要结合 Pablo 和 larsmans 答案的最佳部分,请使用 (offset, size) 对来存储子字符串,并使用 strcspn 来获取每个条目的范围。

【讨论】:

    【解决方案3】:

    在如此大的字符串上,改用ropes 可能会有所回报。或者按照 Pablo 的建议使用字符串视图:(char const*, size_t) 对。如果您对strpbrk 有良好的实现,则不需要bitset 技巧。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-11-04
      • 1970-01-01
      • 2018-02-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多