Удалите лишние пробелы в С++
Я попытался написать 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;
}
Это не работает. Я попробовал несколько методов. Я пытаюсь выполнить итерацию строки буквой по букве и выгрузить ее в другую строку, если в строке есть только одно место; если есть два пробела, не записывайте второй символ в новую строку.
Как я могу это решить?
Ответы
Ответ 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);
}