Удалить пробелы из строки на месте?

#c

#c

Вопрос:

Я видел это в «списке вопросов для интервью». Заставил меня задуматься.

Конечно, не обязательно ограничиваться пробелами, легко обобщается на «удаление некоторого определенного символа из строки на месте».

Мое решение:

 void stripChar(char *str, char c = ' ') {
  int x = 0;
  for (int i=0;i<strlen(str);i  ) {
    str[i-x]=str[i];
    if (str[i]==c) x  ;
  }
  str[strlen(str)-x] = '';
}
  

Я сомневаюсь, что есть более эффективный, но есть ли более элегантное решение?

редактировать: совершенно забыл, что я оставил strlen там, это определенно неэффективно

Ответ №1:

C не имеет аргументов по умолчанию, и если вы программируете на C , вам следует использовать std::string и remove_if from <algorithm> .

Вы определенно можете сделать это более эффективным, исключив вызовы strlen , которые превращают алгоритм O (N) в алгоритм O (N 2) и совершенно не нужны — вы все равно просматриваете строку, поэтому просто ищите NUL самостоятельно.

Вы также можете сделать это более C-идиоматичным, используя два указателя вместо индексации массива. Я бы сделал это так:

 void strip_char(char *str, char strip)
{
    char *p, *q;
    for (q = p = str; *p; p  )
        if (*p != strip)
            *q   = *p;
    *q = '';
}
  

Комментарии:

1. Вау, это так просто и элегантно.

Ответ №2:

Прежде всего, i<strlen(str) это всегда неэффективная идиома для зацикливания на строке. Правильное условие цикла просто str[i] , т.Е. Цикл до str[i] тех пор, пока не будет нулевым терминатором.

С учетом сказанного, вот самый простой / самый краткий алгоритм, который я знаю:

 for (size_t i=0, j=0; s[j]=s[i]; j =!isspace(s[i  ]));
  

Примечание: Мое решение касается вопроса, написанного в теме (пробел), а не тела (конкретного символа). Вы можете легко адаптировать его, если это необходимо.

Комментарии:

1. Wow j =!isspace(s[i ]) действительно умный.

2. Еще более лаконично while(s[!(s[i]==32)?j :j]=s[i ]); , но вы должны настроить переменные вне его так. Тем не менее, я бы посоветовал OP никогда не использовать ни один из них в каком-либо реальном коде, конечно.

3. @SethCarnegie: или даже (s[i]-32)

4. Причина, по которой я написал это с помощью цикла for, заключается в том, чтобы сохранить его чистым и читаемым. Условие цикла и операция увеличения сохраняются на своих местах; единственное, что немного неканоническое, — это выполнение присваивания в условии цикла, и причина, по которой я помещаю его туда, а не в (пустое) тело цикла, заключается в том, что присвоение должно выполняться даже в последнем (завершающем)итерация.

Ответ №3:

 void prepend(char* s,char ch){
    int len = strlen(s);
    memmove(s, s   1, len - 1);
    s[len - 1] = 'x0';
}


void RemoveWhitespace(char* InStr, char ch){
     int n(0);

if (InStr == NULL){
    return;
}
else if ((*InStr) == 'x0'){
    return;
}   
else if ((*InStr) != ch){
    RemoveWhitespace(InStr   1,ch);
}
else{
    while ((*InStr) == ch){
        prepend(InStr,InStr[0]);
        n  ;
    }
    RemoveWhitespace(InStr   n,ch);
}
}
  

Комментарии:

1. @Steven Lu, рекурсия может привести к переполнению стека, но поскольку все остальные ответы были итеративными, я решил опубликовать эту рекурсивную реализацию. Кроме того, я знаю, что memmove и strlen — дорогостоящие операции.