2016-02-09 2 views
13

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

В основном я хочу преобразовать abc sssd g g sdg gg gf в abc 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; 
} 

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

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

+2

это C++, вот как я скомпилирую прогон 'clear; rm -f test2.exe; g ++ -o test2.exe test2.cpp; ./test2.exe; ' – Damian

+4

Не кодируйте C-стиль на C++! Используйте функции языка. – Olaf

+1

Во-первых, код на самом деле C, а не C++, хотя вы его компилируете как таковой. Во-вторых, ваш 'substr' указывает на случайное место в памяти, так как вы не инициализируете его, и вы вызываете * неопределенное поведение *. Вы можете получить символ из строки c, просто проиндексируя его, поэтому нет необходимости в 'strncpy'. Ваше мышление также правильно, вам просто нужно выполнить копирование части в другую строку. –

ответ

7

Вот простой, не-C++ 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 
+0

Нет проблем. Обратите внимание также, что 'remove_extra_whitespaces() 'предполагает, что конечная строка не будет переполнять память, выделенную для' output', и если это произойдет, вы, скорее всего, получите ошибку сегментации. – villapx

3

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

yes, 'string'>' char [0x255] ', я согласен, но я хочу придерживаться' char * ', потому что весь код находится в' char * '..., это можно сделать? – Damian

+0

Не уверен, что вы хотели написать мне этот комментарий, но см. ['String :: c_str'] (http://www.cplusplus.com/reference/string/string/c_str/). –

+0

это оставляет дополнительное пространство в конце строки, если оно заканчивается пробелом. Не уверен, что требования к переключению OP нуждаются в том, чтобы позаботиться о ... – jaggedSpire

4

Поскольку вы используете 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; 

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

+0

yes, 'string'>' char [0x255] ', я согласен, но я хочу придерживаться' char * ', потому что весь код находится в' char * '... – Damian

+1

Вы можете конвертировать назад и вперед - от' char * 'до' string' от конструктора и обратно 'c_str()' и 'strcpy'. Много ненужной работы для процессора, но меньше головной боли для вас. – anatolyg

+0

@anatolyg: Если это сделано в нужном месте в нужное время, возможно, потребуется немного больше дополнительной работы для оптимизатора. – Deduplicator

2

для на месте модификации можно применить стирающего удалить метод:

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

Таким образом, вы сначала переместить все лишние пробелы в конце строки, а затем обрезать его.


Большое преимущество 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](char curr) { 
     bool r = std::isspace(curr) && prev_is_space; 
     prev_is_space = std::isspace(curr); 
     return r; 

    })); 

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

Интересным достаточно remove шага здесь string- независимое представление. Он будет работать с std::string без каких-либо изменений.

+0

yes, 'string'>' char [0x255] ', я согласен, но я хочу придерживаться' char * ', потому что весь код находится в 'char *' ... – Damian

+1

Приятно, но статическое 'prev_is_space' не будет сброшено, если вы будете выполнять этот блок несколько раз (в цикле или в функции или в нескольких потоках). Чтобы это безопасно работало, вам нужно будет захватить локальный bool, который вы можете сбросить при необходимости. – Christophe

+0

@ Кристофе, я вижу. Благодарю. – Lol4t0

1

Поскольку вы пишете c-style, вот способ делать то, что вы хотите. Обратите внимание, что вы можете удалить '\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++, вы заметите, что многие C++ функции, которые возвращают зЬй :: строку или другой зЬй :: объекты являются в основном обертка для хорошо написана экстерном " 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)); 
    } 
+0

hmm, очень интересно, так что в основном ваши' int remove_whitesaces (char * p) ', не нужно брать два параметра, просто изменить его« на лету »с силой указателей, не так ли? – Damian

+0

Да, потому что длина вывода всегда будет равна или меньше входной длины, поэтому нет необходимости создавать другой объект. Я также перегружал его для поддержки std :: строк (и снова не происходит выделения памяти). Я думал, что вы согласитесь с моим ответом, поскольку он на самом деле настраивается (и не принимает табуляции ('\ t'), которые считаются пробелами почти всеми, и при необходимости он может игнорировать разрывы строк. – Jts

20

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

Edit: если хранение строк с стилем требуется в вашем коде, вы можете использовать практически один и тот же код, но с указателями вместо итераторов. Это волшебство C++. Вот another live demo.

+0

Это хороший вариант. Хотя у него должна быть подпись оригинала, возможно. – Deduplicator

+0

@Deduplicator да, я редактировал, чтобы рекомендовать переключение на 'std :: string' – Christophe

+0

да, я тоже согласен, строка лучше, но все« скрипты »написаны (2000 строк), используя' char * '... и этот скрипт должен работать на 'centos 4, 5.1',' debian 4', 'unix based systems' ... и так далее, и лучше использовать самые простые функции, чтобы не получить« segmentation fault »... – Damian

1

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

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

+0

'sscanf' - это функция, которая также может использоваться в' ANSI C (plain C) '? – Damian

+0

@ Дамиан О, да, это так. Это часть стандарта C (и вместе с ним, часть стандарта POSIX для Unix-подобных систем). –

+0

спасибо, вы знаете, C очень старый язык программирования, он дает мне головные боли все время ... посмотрите на это: http://stackoverflow.com/questions/35873677/segmentation-fault-on-malloc-function – Damian

0

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

#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

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

strProductName = string((LPCSTR)pvProductName, iProductNameLen) 

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

Смежные вопросы