2

非常に大きなデータ セットの 1 つの空白を除く余分な空白を削除する C/C++ 関数を開発しています。これが私の機能です:

       void  iterative_trim_whitespace(const char* src, char* target){


             bool hitspace(*src = ' ');
             while (*src != '\x0'){
                if (!hitspace){
                    *target++ = *src++;
                } 
                else{
                    src++;
                }
                if (isspace(*src)){
                    hitspace = true;
                }  
                else{

                    hitspace = false;
                }
             }

         }

同じことをする再帰関数を書きました。ご希望があればお付けします。ただし、大きな文字列を含む非常に大きなデータの場合、再帰関数 calll スタックのオーバーヘッドが非常に大きくなる可能性があります。C/C++ でこれを行う最速の方法を知っている人はいますか? Standard Template Library と Boost テンプレート ライブラリについてはよく知っています。ただし、ネイティブ C/C++ は C++ テンプレートよりも高速であると思います。

4

6 に答える 6

5

あなたの意図は、「トリム」が通常意味するものとは少し異なると思います。「トリム」は通常、文字列の先頭および/または末尾から余分な空白を削除することを意味するために使用されますが、入力に空白が連続している各場所で、出力に単一の空白が必要であることを意味しているようです。

また、C スタイルの文字列を処理する C に似た実装に設定されていることも前提としています。それが当てはまらない場合は、イテレータと標準アルゴリズムを使用するだけで、はるかにシンプルでクリーンになります。

その場合は、次のようにするとよいと思います。

bool copy_word(char *&dest, char const *&src) { 
    while (isspace(*(unsigned char *)src))
        ++src;

    while (*src && *src != ' ') {
        *dest = *src;
        ++dest;
        ++src;
    }
    return *src != '\0';
}

void trim_whitespace(char *dest, char const *src) { 
    while (copy_word(dest, src))
        *dest++ = ' ';
    *dest = '\0';
}

ここで留意すべき主な点が 2 つあります。まず、実行するアクションのシーケンスがある場合 (たとえば、空白をスキップしてから空白以外をコピーするなど)、それをシーケンスとしてエンコードするのではなく、シーケンスとしてエンコードする方がおそらくクリーンです。単一のループを通るさまざまなルート。次に、 を使用する場合はisspace、UB を回避するためにオペランドを符号なし型にキャストする必要があります。

編集:価値があるので、小さなテスト/ベンチマークプログラムをまとめて、自分のコードとOPの回答のコードがどのように機能するかを確認しました。

#include <ctype.h>
#include <time.h>
#include <vector>
#include <set>
#include <deque>
#include <iostream>
#include <string>
#include <algorithm>

void  iterative_trim_whitespace(const char* src, char* target){
    bool firstspace(true);
    while (*src != '\x0'){
        if (firstspace){
            *target++ = *src++;
        } 
        else{
            src++;
        }
        if (firstspace && isspace(*(src - 1))){
            firstspace    = false;
        }  
        else{
            firstspace = true;
        }
    }
    *target = '\x0';
}

struct my_isspace {
    bool operator()(char ch) {
        return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == '\v';
    }
};

bool copy_word(char *&dest, char const *&src) { 
    my_isspace check;
    while (check((*src)))
        ++src;

    while (*src && !check(*src))
        *dest++ = *src++;   
    return *src != '\0';
}

void trim_whitespace(char *dest, char const *src) { 
    while (copy_word(dest, src))
        *dest++ = ' ';
    *dest = '\0';
}

void show(std::string const &label, double t) { 
    std::cout << "Time for " << label << " " << t << " seconds\n";
}

template <class test, class T>
double timer(test t, T a, T b) {
    clock_t start = clock();
    t(a, b);
    clock_t stop = clock();
    return double(stop-start)/CLOCKS_PER_SEC;
}

void generate_string(std::vector<char> &dest, size_t size) { 
    for (int i=0; i<size; i++) {
        if (rand() % 5 == 0)
            dest.push_back(' ');
        else
            dest.push_back(rand() % 26 + 'A');
    }
    dest.push_back('\0');
}

int main() {
    static const int size = 1024 * 1024 * 100;

    std::vector<char> src, dest;

    generate_string(src, size-2);

    dest.resize(size);

    show("Original", timer(iterative_trim_whitespace, &src[0], &dest[0]));    
    show("Jerry's", timer(trim_whitespace, &dest[0], &src[0]));

    return 0;
}

少なくとも実行すると、次のようになります。

Time for Original 0.749 seconds
Time for Jerry's 0.468 seconds

おそらく追加する必要があります。コメントでほのめかしたように、isspace使用しているコンパイラでの の実装は、少なくともここで投入した単純なものと比較して、かなり遅いです。ただし、公平を期すために、これの利点の一部が関数オブジェクトとして実装されているだけであるとしても、(とにかく)驚くことではありません。これにより、コンパイラがインラインコードを生成するのが非常に簡単になることがよくあります。

それが価値があるものについては、他の2つのポイント:

  1. Microsoft のリンク時のコード生成は、これらの両方をかなり遅くします
  2. いずれにせよ、トリミングは最初に入力を生成するよりもかなり高速です

1まあ、技術的には、最初から unsigned 型である可能性はありますが、それはchar十分に異常なので、それを当てにすべきではありません。すべての入力が、おそらく保持できる文字の ASCII サブセット内に収まる可能性もあります。charその場合、問題なく動作するように見えますが、それは有害なことです。(必要なだけ) テストできます。ただし、 として負の数になるようにエンコードされた文字を含むテキストでそうするまでは、問題ないようcharに見えます。次に、フランス語/スペイン語/ノルウェー語などの顧客がテストすると、顔が平らになります。

于 2012-11-29T15:17:05.877 に答える
2

これは確かに合理的に見えますが、再帰的なバージョンは恐ろしいものです。これらが大きな文字列である場合は、コピーする代わりにその場で変更することを検討しますが、それはより高度な設計上の決定です。速度には影響しませんが、メモリ消費を減らすことができます。

于 2012-11-29T15:07:13.123 に答える
2

本当に迅速な解決策が必要な場合は、これをまったく行わないでください。代わりに、スペースをスキップする入力文字列に対するイテレータを使用します。「トリミングされた」文字列を操作する必要がある場合は、この反復子を渡すだけです。

これは、開発が今までにどれだけ進んだかによって、可能である場合と不可能である場合があります。

于 2012-11-29T15:12:11.597 に答える
1

ジェリー・コフィン、レキシントンからの約束から帰ってきたところです。このバージョンはテスト済みです。レキシントンの歯医者に行くために急いで書いた最初のバージョンをお詫びします。

void  iterative_trim_whitespace(const char* src, char* target){

         bool firstspace(true);
         while (*src != '\x0'){
            if (firstspace){
                *target++ = *src++;
            } 
            else{
                src++;
            }
            if (firstspace && isspace(*(src - 1))){
      firstspace    = false;
            }  
            else{
      firstspace = true;
            }
         }
    *target = '\x0';
}

void iterative_trim_whitespace_revised(const char* src, char* target){
    bool firstspace(true);
    int ct(0);
    while (*src != '\x0'){
        if (firstspace){
            *target++ = *src++;
        } 
        else{
            src += ct - 2; 
        }
        if (firstspace){ 
            char *x = (char *)src - 1;
            ct = 1;
            bool sentinel(false);
            while(isspace(*(x + (ct - 1)))){
                ct += 1;
                sentinel = true;

            }
            if (sentinel){
                firstspace = false;
            }
        }
        else{
            ct = 1;
            firstspace = true;
        }
    }
    *target = '\x0';
}

void iterative_trim_whitespace_friday_5Timesfaster(const char* src, char* target){ bool firstspace(true); int ct(0); while (*src != '\x0'){ if (firstspace){ *target++ = *src++; } else{ src += ct - 2; }

       if (firstspace){ 
     char *x = (char *)src - 1;
     ct = 1;
     bool sentinel(false);
     while(*(x + (ct - 1)) == ' '){ 
         ct += 1;
         sentinel = true;

     }
     if (sentinel){
         firstspace    = false;
     }
            }
            else{
     ct = 1;
     firstspace = true;
            }
         }
    *target = '\x0';

}

// これが今朝の ProjectDirector のバージョンです // TrimLeading() と TrimTrailing は追加のインライン関数です

void iterative_trim_whitespace_ProjectDirector(const char* src, char* target){

int out=0;

for (int i=0;src[i]!= '\x0';i++) {

    if (src[i] != ' ' || src[i+1] != ' '){

        target[out++]=src[i];
    }

}

target[out]= '\x0';

}

于 2012-11-29T22:29:58.343 に答える
1

あなたのコードは書かれたとおりには機能しません。最初の文字がスペースの場合、スペースはコピーされず、スペースの後の文字もコピーされません。このようなものはより合理的です:

bool hitSpace = false;
while (*src != '\x0')
{
    if (isspace(*src))
    {
        if (hitSpace)
        {
            src++;
        }
        else
        {
            *target++ = *src++;
            hitSpace = true;
        }
    }
    else
    {
        *target++ = *src++;
        hitSpace = false;
    }
}
于 2012-11-29T15:12:21.570 に答える
1

まず、再帰ではなく反復 (C または C++) を選択します。いずれにせよ、コンパイラはおそらく再帰アルゴリズムをループに変換しますが、変換しない場合 (またはデバッグ モードでビルドする場合) は、確実にスタックをオーバーフローさせます。さらに、関数の呼び出しにはコストがかかるため、それを避けたいと考えています。

あなたの基本的なアルゴリズムは健全に見えます (ジムが発見したバグが修正されれば)。isspaceインライン化されていることを確認します。そうでない場合は、に置き換え*str == ' 'ます。

テンプレートを使用したソリューションは、単純な問題を複雑にするだけで、何のメリットもありません。

于 2012-11-29T15:14:12.630 に答える