web-dev-qa-db-ja.com

C ++で余分な空白を削除する

余分な空白を削除するスクリプトを作成しようとしましたが、どうにか完了できませんでした。

基本的に、abc sssd g g sdg gg gfabc sssd g g sdg gg gfに変換します。

PHPまたはC#のような言語では、非常に簡単ですが、C++ではそうではありません。これは私のコードです。

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <cstring>
#include <unistd.h>
#include <string.h>

char* trim3(char* s) {
    int l = strlen(s);

    while(isspace(s[l - 1])) --l;
    while(* s && isspace(* s)) ++s, --l;

    return strndup(s, l);
}

char *str_replace(char * t1, char * t2, char * t6)
{
    char*t4;
    char*t5=(char *)malloc(10);
    memset(t5, 0, 10);
    while(strstr(t6,t1))
    {
        t4=strstr(t6,t1);
        strncpy(t5+strlen(t5),t6,t4-t6);
        strcat(t5,t2);
        t4+=strlen(t1);
        t6=t4;
    }

    return strcat(t5,t4);
}

void remove_extra_whitespaces(char* input,char* output)
{
    char* inputPtr = input; // init inputPtr always at the last moment.
    int spacecount = 0;
    while(*inputPtr != '\0')
    {
        char* substr;
        strncpy(substr, inputPtr+0, 1);

        if(substr == " ")
        {
            spacecount++;
        }
        else
        {
            spacecount = 0;
        }

        printf("[%p] -> %d\n",*substr,spacecount);

        // Assume the string last with \0
        // some code
        inputPtr++; // After "some code" (instead of what you wrote).
    }   
}

int main(int argc, char **argv)
{
    printf("testing 2 ..\n");

    char input[0x255] = "asfa sas    f f dgdgd  dg   ggg";
    char output[0x255] = "NO_OUTPUT_YET";
    remove_extra_whitespaces(input,output);

    return 1;
}

機能しません。いくつかの方法を試しました。私がやろうとしているのは、文字列を文字ごとに繰り返して、行にスペースが1つしかない限り、別の文字列にダンプすることです。 2つのスペースがある場合、2番目の文字を新しい文字列に書き込まないでください。

どうすれば解決できますか?

16
Damian

以下は、質問と同じremove_extra_whitespace()署名を使用した、単純な非C++ 11ソリューションです。

#include <cstdio>

void remove_extra_whitespaces(char* input, char* output)
{
    int inputIndex = 0;
    int outputIndex = 0;
    while(input[inputIndex] != '\0')
    {
        output[outputIndex] = input[inputIndex];

        if(input[inputIndex] == ' ')
        {
            while(input[inputIndex + 1] == ' ')
            {
                // skip over any extra spaces
                inputIndex++;
            }
        }

        outputIndex++;
        inputIndex++;
    }

    // null-terminate output
    output[outputIndex] = '\0';
}

int main(int argc, char **argv)
{
    char input[0x255] = "asfa sas    f f dgdgd  dg   ggg";
    char output[0x255] = "NO_OUTPUT_YET";
    remove_extra_whitespaces(input,output);

    printf("input: %s\noutput: %s\n", input, output);

    return 1;
}

出力:

input: asfa sas    f f dgdgd  dg   ggg
output: asfa sas f f dgdgd dg ggg
8
villapx

すでにニースのソリューションがたくさんあります。重複を避けるための専用の_<algorithm>_に基づく代替案を提案します:unique_copy()

_void remove_extra_whitespaces(const string &input, string &output)
{
    output.clear();  // unless you want to add at the end of existing sring...
    unique_copy (input.begin(), input.end(), back_insert_iterator<string>(output),
                                     [](char a,char b){ return isspace(a) && isspace(b);});  
    cout << output<<endl; 
}
_

ライブデモです。 Cスタイルの文字列からより安全で強力なC++文字列に変更したことに注意してください。

Edit:コードでCスタイルの文字列を保持する必要がある場合、ほぼ同じコードを使用できますが、反復子の代わりにポインターを使用します。それがC++の魔法です。 別のライブデモ

25
Christophe

C++を使用しているため、この種の作業用に設計された標準ライブラリ機能を利用できます。 std::stringchar[0x255]の代わりに)と std::istringstream を使用して、ほとんどのポインター演算を置き換えることができます。

まず、文字列ストリームを作成します。

std::istringstream stream(input);

次に、そこから文字列を読み取ります。空白区切り文字を自動的に削除します:

std::string Word;
while (stream >> Word)
{
    ...
}

ループ内で、出力文字列を作成します。

    if (!output.empty()) // special case: no space before first Word
        output += ' ';
    output += Word;

この方法の欠点は、メモリを動的に割り当てることです(出力文字列が大きくなったときに実行される複数の再割り当てを含む)。

6
anatolyg

これを行うには多くの方法があります(たとえば、正規表現を使用します)が、これを行う1つの方法は std::copy_if 最後の文字がスペースであったかどうかを記憶するステートフルファンクターを使用:

#include <algorithm>
#include <string>
#include <iostream>

struct if_not_prev_space
{
    // Is last encountered character space.
    bool m_is = false;

    bool operator()(const char c)
    {                                      
        // Copy if last was not space, or current is not space.                                                                                                                                                              
        const bool ret = !m_is || c != ' ';
        m_is = c == ' ';
        return ret;
    }
};


int main()
{
    const std::string s("abc  sssd g g sdg    gg  gf into abc sssd g g sdg gg gf");
    std::string o;
    std::copy_if(std::begin(s), std::end(s), std::back_inserter(o), if_not_prev_space());
    std::cout << o << std::endl;
}
3
Ami Tavory

インプレース変更の場合は、消去削除テクニックを適用できます。

#include <string>
#include <iostream>
#include <algorithm>
#include <cctype>

int main()
{
    std::string input {"asfa sas    f f dgdgd  dg   ggg"};
    bool prev_is_space = true;
    input.erase(std::remove_if(input.begin(), input.end(), [&prev_is_space](unsigned char curr) {
        bool r = std::isspace(curr) && prev_is_space;
        prev_is_space = std::isspace(curr);
        return r;

    }), input.end());

    std::cout << input << "\n";
}

したがって、最初に余分なスペースをすべて文字列の最後に移動してから、それを切り捨てます。


C++の大きな利点は、コードをfewの変更のみでplain-c-static文字列に移植するのに十分な汎用性があることです。

void erase(char * p) {
    // note that this ony works good when initial array is allocated in the static array
    // so we do not need to rearrange memory
    *p = 0; 
}

int main()
{
    char input [] {"asfa sas    f f dgdgd  dg   ggg"};
    bool prev_is_space = true;
    erase(std::remove_if(std::begin(input), std::end(input), [&prev_is_space](unsigned char curr) {
        bool r = std::isspace(curr) && prev_is_space;
        prev_is_space = std::isspace(curr);
        return r;

    }));

    std::cout << input << "\n";
}

ここで興味深いremoveステップは、文字列表現に依存しません。 std::stringを変更せずに動作します。

3
Lol4t0

私は良いol 'scanfがやるという沈み込んだ感覚を持っています(実際、これはアナトリーのC++ソリューションに相当するCスクールです):

void remove_extra_whitespaces(char* input, char* output)
{
    int srcOffs = 0, destOffs = 0, numRead = 0;

    while(sscanf(input + srcOffs, "%s%n", output + destOffs, &numRead) > 0)
    {
        srcOffs += numRead;
        destOffs += strlen(output + destOffs);
        output[destOffs++] = ' '; // overwrite 0, advance past that
    }
    output[destOffs > 0 ? destOffs-1 : 0] = '\0';
}

scanfには魔法の組み込みスペーススキップ機能があるという事実を利用します。次に、おそらくあまり知られていない%n「変換」仕様は、scanfによって消費される文字の量を提供します。この機能は、次のように文字列から読み取るときに便利です。この解決策を不完全なものにする苦いドロップは、出力のstrlen呼び出しです(残念ながら、「実際にちょうど何バイトあるのかwritten "変換指定子」はありません) 。

最後に、outputに十分なメモリが存在することが保証されているため、scanfの使用は簡単です。そうでない場合、バッファリングとオーバーフロー処理のためにコードがより複雑になります。

2

std :: unique を使用して、2つの要素を等しくするものを定義する方法に従って、隣接する重複を単一のインスタンスに減らすことができます。

ここで、両方がwhitespace文字である場合、要素は等しいと定義しました。

inline std::string& remove_extra_ws_mute(std::string& s)
{
    s.erase(std::unique(std::begin(s), std::end(s), [](unsigned char a, unsigned char b){
        return std::isspace(a) && std::isspace(b);
    }), std::end(s));

    return s;
}

inline std::string remove_extra_ws_copy(std::string s)
{
    return remove_extra_ws_mute(s);
}

std :: unique は、重複を文字列の最後に移動し、イテレータをそれらの先頭に返して消去できるようにします。

さらに、mustで低レベル文字列を使用する必要がある場合は、ポインターで std :: unique を引き続き使用できます。

char* remove_extra_ws(char const* s)
{
    std::size_t len = std::strlen(s);

    char* buf = new char[len + 1];
    std::strcpy(buf, s);

    // Note that std::unique will also retain the null terminator
    // in its correct position at the end of the valid portion
    // of the string    
    std::unique(buf, buf + len + 1, [](unsigned char a, unsigned char b){
        return (a && std::isspace(a)) && (b && std::isspace(b));
    });

    return buf;
}
1
Galik

あなたはCスタイルを書いているので、ここであなたがしたいことをする方法があります。改行である'\r''\n'を削除できることに注意してください(ただし、これらの空白を考慮するかどうかはもちろんあなた次第です)。

この関数は、他の選択肢と同じかそれよりも高速である必要があり、std :: stringsで呼び出された場合でもメモリ割り当ては行われません(オーバーロードしました)。

char temp[] = " alsdasdl   gasdasd  ee";
remove_whitesaces(temp);
printf("%s\n", temp);

int remove_whitesaces(char *p)
{
    int len = strlen(p);
    int new_len = 0;
    bool space = false;

    for (int i = 0; i < len; i++)
    {
        switch (p[i])
        {
        case ' ': space = true;  break;
        case '\t': space = true;  break;
        case '\n': break; // you could set space true for \r and \n
        case '\r': break; // if you consider them spaces, I just ignore them.
        default:
            if (space && new_len > 0)
                p[new_len++] = ' ';
            p[new_len++] = p[i];
            space = false;
        }
    }

    p[new_len] = '\0';

    return new_len;
}

// and you can use it with strings too,

inline int remove_whitesaces(std::string &str)
{
    int len = remove_whitesaces(&str[0]);
    str.resize(len);
    return len; // returning len for consistency with the primary function
                // but u can return std::string instead.
}

// again no memory allocation is gonna take place,
// since resize does not not free memory because the length is either equal or lower

C++標準ライブラリを簡単に見ると、std :: stringまたは他のstd :: objectsを返す多くのC++関数が、基本的に適切に記述されたextern "C"関数のラッパーであることがわかります。したがって、C++アプリケーションでC関数を使用することを恐れないでください。よく書かれていて、それらをオーバーロードしてstd :: stringsなどをサポートすることができます。

たとえば、Visual Studio 2015では、std::to_stringは次のように記述されます。

inline string to_string(int _Val)
    {   // convert int to string
    return (_Integral_to_string("%d", _Val));
    }

inline string to_string(unsigned int _Val)
    {   // convert unsigned int to string
    return (_Integral_to_string("%u", _Val));
    }

_Integral_to_stringは、C関数sprintf_sのラッパーです

template<class _Ty> inline
    string _Integral_to_string(const char *_Fmt, _Ty _Val)
    {   // convert _Ty to string
    static_assert(is_integral<_Ty>::value,
        "_Ty must be integral");
    char _Buf[_TO_STRING_BUF_SIZE];
    int _Len = _CSTD sprintf_s(_Buf, _TO_STRING_BUF_SIZE, _Fmt, _Val);
    return (string(_Buf, _Len));
    }
1
Jts

組み込み関数を使用せずに余分な空白を削除するシンプルなプログラム。

#include<iostream>
#include<string.h>
#include<stdio.h>
using namespace std;

int main()
{
  char str[1200];
  int i,n,j,k, pos = 0 ;
  cout<<"Enter string:\n";
  gets(str);
  n = strlen(str);
  for(i =0;i<=n;i++)
  {
      if(str[i] == ' ')
      {
          for(j= i+1;j<=n;j++)
          {
                  if(str[j] != ' ')
                  {
                      pos = j;
                      break;
                  }
           }
         if(pos != 0 && str[pos] != ' ')
         {
            for(k =i+1;k< pos;k++)
             {   if(str[pos] == ' ')
                     break;
                 else{
                    str[k] = str[pos];
                    str[pos] = ' ';
                    pos++;
                 }

             }
         }

      }
  }
  puts(str); 
}
0
Abhishek Baghel

私はここでわずかに異なる問題のために終わった。他にどこに置くべきかわからないので、何が間違っていたのかを見つけたので、ここで共有します。私と交差しないでください。デバッグ時にスペースなしで表示されている間に、末尾に追加のスペースを出力する文字列がいくつかありました。 Windowsで形成される文字列は、他の要素に加えて文字列の長さを出力するVerQueryValue()などを呼び出します。結果をstrProductNameという名前の文字列に変換する次の行のiProductNameLen:

    strProductName = string((LPCSTR)pvProductName, iProductNameLen)

その後、末尾に\ 0バイトの文字列が生成されました。deデバッガーでは簡単に表示されませんでしたが、画面にスペースとして印刷されました。これに気づいたら、それはまったく難しいことではないので、このソリューションを演習として残しておきます。

0
Jan

これは、ポインターを使用しない、長めの(ただし簡単な)ソリューションです。さらに最適化できますが、動作します。

#include <iostream>
#include <string>
using namespace std;
void removeExtraSpace(string str);
int main(){
    string s;
    cout << "Enter a string with extra spaces: ";
    getline(cin, s);
    removeExtraSpace(s);
    return 0;
}
void removeExtraSpace(string str){
    int len = str.size();
    if(len==0){
        cout << "Simplified String: " << endl;
        cout << "I would appreciate it if you could enter more than 0 characters. " << endl;
        return;
    }
    char ch1[len];
    char ch2[len];
    //Placing characters of str in ch1[]
    for(int i=0; i<len; i++){
        ch1[i]=str[i];
    }
    //Computing index of 1st non-space character
    int pos=0;
    for(int i=0; i<len; i++){
        if(ch1[i] != ' '){
            pos = i;
            break;
        }
    }
    int cons_arr = 1;
    ch2[0] = ch1[pos];
    for(int i=(pos+1); i<len; i++){
        char x = ch1[i];
        if(x==char(32)){
            //Checking whether character at ch2[i]==' '
            if(ch2[cons_arr-1] == ' '){
                continue;
            }
            else{
                ch2[cons_arr] = ' ';
                cons_arr++;
                continue;
            }
        }
        ch2[cons_arr] = x;
        cons_arr++;
    }
    //Printing the char array
    cout << "Simplified string: " << endl;
    for(int i=0; i<cons_arr; i++){
        cout << ch2[i];
    }
    cout << endl;
}
0
Hans