web-dev-qa-db-fra.com

Supprimer les espaces blancs supplémentaires en C++

J'ai essayé d'écrire un script qui supprime les espaces blancs mais je n'ai pas réussi à le finir. 

En gros, je veux transformer abc sssd g g sdg gg gf en abc sssd g g sdg gg gf.

Dans des langages comme PHP ou C #, ce serait très facile, mais pas en C++, à ce que je vois. Ceci est mon code:

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

Ça ne marche pas J'ai essayé plusieurs méthodes. Ce que je cherche à faire est d’itérer la chaîne lettre par lettre et de la transférer dans une autre chaîne tant qu’il n’ya qu’un espace dans une ligne; s'il y a deux espaces, n'écrivez pas le deuxième caractère dans la nouvelle chaîne.

Comment puis-je résoudre ça?

14
Damian

Voici une solution simple, non-C++ 11, utilisant la même signature remove_extra_whitespace() que dans la question:

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

Sortie:

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

Il y a déjà beaucoup de solutions de Nice. Je vous propose une alternative basée sur un <algorithm> dédié destiné à éviter les doublons consécutifs: 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; 
}

Voici un D&EACUTE;MO EN DIRECT. Notez que je suis passé des chaînes de style c aux chaînes C++ plus sûres et plus puissantes. 

Edit: si votre code nécessite de conserver des chaînes de style c, vous pouvez utiliser presque le même code mais avec des pointeurs au lieu d'itérateurs. C'est la magie du C++. Voici une autre démo live . 

22
Christophe

Puisque vous utilisez C++, vous pouvez tirer parti des fonctionnalités de bibliothèque standard conçues pour ce type de travail. Vous pouvez utiliser std::string (au lieu de char[0x255]) et std::istringstream , qui remplacera l'essentiel de l'arithmétique du pointeur.

Commencez par créer un flux de chaîne:

std::istringstream stream(input);

Ensuite, lisez les chaînes de celui-ci. Cela supprimera automatiquement les délimiteurs d'espaces:

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

Dans la boucle, construisez votre chaîne de sortie:

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

L'inconvénient de cette méthode est qu'elle alloue de la mémoire de manière dynamique (y compris plusieurs réaffectations, effectuées lorsque la chaîne de sortie augmente).

6
anatolyg

Il existe de nombreuses façons de le faire (par exemple, en utilisant des expressions régulières), mais vous pouvez le faire en utilisant std::copy_if avec un foncteur avec état en rappelant si le dernier caractère était un espace:

#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

pour une modification sur place, vous pouvez appliquer la technique d'effacement et de suppression:

#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](char curr) {
        bool r = std::isspace(curr) && prev_is_space;
        prev_is_space = std::isspace(curr);
        return r;

    }), input.end());

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

Ainsi, vous déplacez d’abord tous les espaces supplémentaires à la fin de la chaîne, puis vous le tronquez.


Le grand avantage du C++ est qu’il est suffisamment universel pour porter votre code dans des chaînes plain-c-static avec seulement peu modifications:

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](char curr) {
        bool r = std::isspace(curr) && prev_is_space;
        prev_is_space = std::isspace(curr);
        return r;

    }));

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

L’étape remove assez intéressante ici est indépendante de la représentation sous forme de chaîne. Cela fonctionnera avec std::string sans aucune modification.

2
Lol4t0

J'ai l'impression que le bon vieux scanf fera l'affaire (en fait, c'est l'équivalent C de la solution d'Anatoly en 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';
}

Nous exploitons le fait que scanf possède des fonctionnalités magiques de saut d’espace intégrées. Nous utilisons ensuite la spécification %n "conversion", peut-être moins connue, qui indique la quantité de caractères consommés par scanf. Cette fonctionnalité est souvent utile lors de la lecture de chaînes, comme ici. La goutte amère qui rend cette solution moins que parfaite est l’appel strlen sur la sortie (il n’existe malheureusement pas d'indicateur de conversion "Combien d'octets ai-je en fait juste écrit").

Enfin, l'utilisation de scanf est facile ici car il est garanti que suffisamment de mémoire existe à output; si ce n'était pas le cas, le code deviendrait plus complexe en raison de la mise en mémoire tampon et de la gestion des débordements.

1
Peter A. Schneider

Puisque vous écrivez en style C, voici une façon de faire ce que vous voulez ... Notez que vous pouvez supprimer les '\r' et '\n' qui sont des sauts de ligne (mais bien sûr, cela vous appartient si vous considérez ces espaces ou non).

Cette fonction devrait être aussi rapide ou rapide que toute autre alternative et aucune allocation de mémoire n’a lieu même quand elle est appelée avec std :: strings (je l’ai surchargée).

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

Si vous examinez brièvement la bibliothèque C++ Standard, vous remarquerez que beaucoup de fonctions C++ qui renvoient std :: string, ou d’autres objets std :: sont en fait des enveloppes pour une fonction "C" externe bien écrite. Donc n'ayez pas peur d'utiliser les fonctions C dans les applications C++, si elles sont bien écrites et que vous pouvez les surcharger pour supporter std :: strings et autres.

Par exemple, dans Visual Studio 2015, std::to_string est écrit exactement comme ceci: 

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

et _Integral_to_string est une enveloppe pour une fonction 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

Programme simple pour supprimer les espaces blancs supplémentaires sans utiliser aucune fonction intégrée.

#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

Je me suis retrouvé ici pour un problème légèrement différent. Puisque je ne sais pas où le mettre ailleurs et que j'ai découvert ce qui n'allait pas, je le partage ici. Ne vous fâchez pas avec moi, s'il vous plaît ... J'ai quelques chaînes qui imprimaient des espaces supplémentaires à leurs extrémités, tout en apparaissant sans espaces lors du débogage. Les chaînes formées dans les appels Windows, telles que VerQueryValue (), qui, à côté d’autres éléments, génère une longueur de chaîne, comme par exemple. iProductNameLen dans la ligne suivante convertissant le résultat en une chaîne nommée strProductName:

    strProductName = string((LPCSTR)pvProductName, iProductNameLen)

puis produit une chaîne avec un octet\0 à la fin, qui ne s’affiche pas facilement dans le débogueur, mais est affichée à l’écran sous forme d’espace. Je laisserai la solution de ceci comme un exercice, car ce n'est pas difficile du tout, une fois que vous en êtes conscient.

0
Jan

Voici une solution longue (mais facile) qui n’utilise pas de pointeur . Elle peut être optimisée mais bon, ça marche.

#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