Подтвердить что ты не робот

Удалите лишние пробелы в С++

Я попытался написать script, который удаляет лишние пробелы, но мне не удалось его закончить.

В основном я хочу преобразовать abc sssd g g sdg gg gf в abc sssd g g sdg gg gf.

В таких языках, как PHP или С#, это было бы очень просто, но не на С++, я вижу. Это мой код:

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

Это не работает. Я попробовал несколько методов. Я пытаюсь выполнить итерацию строки буквой по букве и выгрузить ее в другую строку, если в строке есть только одно место; если есть два пробела, не записывайте второй символ в новую строку.

Как я могу это решить?

4b9b3361

Ответ 1

Здесь простое, не-С++ 11 решение, использующее ту же подпись remove_extra_whitespace(), что и в вопросе:

#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

Ответ 2

Уже есть много хороших решений. Я предлагаю вам альтернативу, основанную на выделенном <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; 
}

Вот демо-версия live demo. Обратите внимание, что я изменил с строк в стиле c на более безопасные и мощные строки C++.

Изменить: если в вашем коде требуется сохранение строк в стиле c, вы можете использовать почти такой же код, но с указателями вместо итераторов. Это магия C++. Вот еще одно живое демо.

Ответ 3

Поскольку вы используете C++, вы можете воспользоваться функциями стандартной библиотеки, разработанными для такого рода работы. Вы можете использовать std::string (вместо char[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;

Недостатком этого метода является то, что он распределяет память динамически (включая несколько перераспределений, выполняемых при увеличении выходной строки).

Ответ 4

Существует много способов сделать это (например, используя регулярные выражения), но одним из способов сделать это можно воспользоваться 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;
}

Ответ 5

для модификации на месте вы можете применить метод удаления-удаления:

#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++ является то, что он достаточно универсален для переноса вашего кода в обычные c-статические строки с несколькими изменениями:

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 без изменений вообще.

Ответ 6

У меня есть ощущение тонуса, что добрый ol 'scanf будет делать (на самом деле это C-школа эквивалентна решению Anatoly С++):

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 "conversion", которая дает нам количество символов, потребляемых scanf. Эта функция часто пригодится при чтении из строк, например здесь. Горькое падение, которое делает это решение менее совершенным, - это вызов strlen на выходе (нет, к сожалению, "сколько байтов я фактически только что написал" ).

Последнее, в последнюю очередь, использование scanf здесь просто, потому что в output будет существовать достаточная память; если бы это было не так, код стал бы более сложным из-за обработки буферизации и переполнения.

Ответ 7

Поскольку вы пишете c-style, вот способ делать то, что вы хотите. Обратите внимание, что вы можете удалить '\r' и '\n', которые являются разрывами строк (но, конечно, это зависит от вас, если вы считаете эти пробелы или нет).

Эта функция должна быть такой же быстрой или быстрой, как любая другая альтернатива, и распределение памяти не происходит даже при вызове с std:: string (я перегрузил ее).

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

Если вы кратко рассмотрите библиотеку С++ Standard, вы заметите, что многие функции С++, возвращающие std::string, или другие объекты std:: в основном представляют собой оболочку для хорошо написанной внешней функции "C". Поэтому не бойтесь использовать C-функции в С++-приложениях, если они хорошо написаны, и вы можете перегрузить их для поддержки std:: string и т.д.

Например, в 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));
    }

Ответ 8

Вы можете использовать std::unique, который сводит соседние дубликаты к одному экземпляру в соответствии с тем, как вы определяете, что делает два элемента равными.

Здесь я определил элементы как равные, если они оба являются пробельными символами:

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 перемещает дубликаты в конец строки и возвращает итератор в начало, чтобы их можно было удалить.

Кроме того, если вы должны работать со строками низкого уровня, вы все равно можете использовать 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;
}

Ответ 9

Ну вот длинное (но простое) решение, которое не использует указатели. Его можно оптимизировать, но он работает.

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

Ответ 10

Я закончил здесь для немного другой проблемы. Поскольку я не знаю, где еще это сказать, и я узнал, что не так, я разделяю его здесь. Не со мной, пожалуйста. У меня было несколько строк, которые бы печатали дополнительные пространства на своих концах, а при отладке отображались без пробелов. Строки, которые формируются в окнах, называются VerQueryValue(), которые, помимо другого материала, выводят длину строки, например. iProductNameLen в следующей строке, преобразующей результат в строку с именем strProductName:

    strProductName = string((LPCSTR)pvProductName, iProductNameLen)

затем создала строку с байтом \0 в конце, которая не отображалась легко в отладчике, а печаталась на экране в виде пробела. Я оставлю решение этого как упражнение, так как это не сложно, как только вы это осознаете.

Ответ 11

Простая программа для удаления лишних пробелов без использования встроенных функций.

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