web-dev-qa-db-ja.com

複数の区切り文字による高速文字列分割

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のトークン化方法:

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のメソッド(リンクされたトピックを参照)。

それらのほとんどは このトピック から来ました。しかし、残念ながら、彼らは私の問題を解決しません。

  • Boostの分割は使いやすいですが、ビッグデータ(最良の場合は約1.5 * 10 ^ 6の単一要素)と約10個の区切り文字を使用すると、非常に遅くなります。

  • getline、STL、およびShadow2531のメソッドには、区切り文字として1つの文字しか使用できないという問題があります。もう少し必要です。

  • Boostのトークン化は、速度の面でさらに恐ろしいものです。文字列を1.5 * 10 ^ 6の要素に分割するには、10個の区切り文字で11秒かかりました。

だから私は何をすべきかわかりません:私は複数の区切り文字を持つ本当に速い文字列分割アルゴリズムが欲しいです。

Boostの分割は最大ですか、それともそれを行う方法はありますかより速く

27
Paul

2つのことが頭に浮かびます。

  1. 分割結果として文字列の代わりに文字列ビューを使用すると、多くの割り当てが節約されます。
  2. ([0,255]の範囲の)charのみを使用することがわかっている場合は、区切り文字にfindする代わりに、ビットセットを使用してメンバーシップをテストしてみてください。

これらのアイデアを適用する簡単な試みは次のとおりです。

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

これをBoost1.46.1でコンパイルし、GCC4.5.1を-O4フラグが有効になっています:

  • 時間:5.951(Boost.Split +ベクトル)
  • 時間:3.728(Boost.Split +ベクトル
  • 時間:1.662(カスタム分割+ベクトル)
  • 時間:0.144(カスタム分割+ベクトル)
  • 時間:2.13(Strpbrk +ベクトル)
  • 時間:0.527(Strpbrk +ベクトル)

注:カスタム関数によって空のトークンが削除されるため、出力にわずかな違いがあります。ただし、このコードを使用する場合は、このコードをニーズに合わせて調整できます。

33
Pablo

Pabloとlarsmansの回答の最良の部分を組み合わせるには、(offset, size)ペアを使用して部分文字列を格納し、strcspnを使用してすべてのエントリのエクステントを取得します。

2
MSN

このような大きな文字列では、代わりに ropes を使用する方が効果的です。または、Pabloが推奨する文字列ビューを使用します:(char const*size_t)ペア。 bitset の適切な実装がある場合、strpbrkトリックは必要ありません。

1
Fred Foo