あなたの意図は、「トリム」が通常意味するものとは少し異なると思います。「トリム」は通常、文字列の先頭および/または末尾から余分な空白を削除することを意味するために使用されますが、入力に空白が連続している各場所で、出力に単一の空白が必要であることを意味しているようです。
また、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つのポイント:
- Microsoft のリンク時のコード生成は、これらの両方をかなり遅くします
- いずれにせよ、トリミングは最初に入力を生成するよりもかなり高速です
1まあ、技術的には、最初から unsigned 型である可能性はありますが、それはchar
十分に異常なので、それを当てにすべきではありません。すべての入力が、おそらく保持できる文字の ASCII サブセット内に収まる可能性もあります。char
その場合、問題なく動作するように見えますが、それは有害なことです。(必要なだけ) テストできます。ただし、 として負の数になるようにエンコードされた文字を含むテキストでそうするまでは、問題ないようchar
に見えます。次に、フランス語/スペイン語/ノルウェー語などの顧客がテストすると、顔が平らになります。