Почему фильтрация ASCII-значения 32 (‘ ‘) из stringstream с помощью следующей функции также отфильтровывает ‘n’ и ‘ t’?

#c #visual-studio-2019

#c #visual-studio-2019

Вопрос:

Я практиковал различные способы отфильтровывания нежелательных символов мусора из объекта nlohmann:: json в std::string, и я только что закончил следующую функцию, которая дала мне довольно хороший результат (игнорируя, что я, конечно, пишу неаккуратный код для начинающих):

 //This string is what I'm testing with as input.
std::string initial = "EA/1n             n                $9.34n            n                ";

std::string filter(std::string s) {
    std::stringstream ss(s);
    char c;
    std::string final_string = "";
    while (ss >> c) {
        if (c == ' ') {
            char temp_c = c;
            while (ss >> c) {
                if (c == ' ') {
                    continue;
                }
                else {
                    final_string  = temp_c;
                    break;
                }
            }
        }
        else {
            final_string  = c;
        }
    }
    return final_string;
}

//this is string that is produced when s is returned to main
result = "EA/1 $9.34"

 

Я не уверен, почему ‘ n’ и ‘ t’ также отфильтровываются, хотя я нацеливался только на ‘ ‘ на этом этапе тестирования. Я могу предоставить фотографию разбивки того, что именно показывает Visual Studio 2019, initial если это необходимо. Любая информация была бы наиболее полезной, поскольку я буду работать над другими строками с аналогичными составами в течение следующих нескольких дней.

ОТРЕДАКТИРОВАНО: я изменил переменную, которая возвращается из функции, с s на final_string . Извините за путаницу.

ОТРЕДАКТИРОВАННОЕ2: Я выбрал ответ Реми Лебо, потому что это привело меня к пониманию того, что моя фильтрация действительно не работала вообще, что научило меня более тщательно отлаживать, прежде чем тратить ваше время. Результат, к которому я пришел, был именно тем, что я хотел, что заставило меня поверить, что мой код был каким-то образом более эффективным, отсюда и мое замешательство. Теперь я буду знать лучше, ха-ха. В любом случае, ответ Реми помог мне решить то, что мне было интересно, и дал мне хороший совет для продвижения вперед.

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

1. Подсказка. Попробуйте использовать отладчик. Выполняется (c == ' ') ли условие хотя бы один раз?

2. ss >> std::noskipws ? Кроме того, вы возвращаете ту же строку, которую вам передали? Я не думаю, что это рассматривалось?

3. std::copy_if делает то, что вы хотите.

4. @S.M. не само по себе, это не так. Вам нужно было бы соединить его std::back_inserter() , например, с . Это также не будет обрабатывать случай, когда код OP использует внутренний цикл для минимизации прогонов из 2 пробелов в 1 пробел

5. @sweenish Если бы OP просто хотел удалить все пробелы, удаление-стирание сделало бы это действительно тривиальным, но удаление-стирание не будет делать то, что запрашивает OP, а именно сокращение всех последовательных пробелов в один пробел.

Ответ №1:

В этом коде:

 while (ss >> c) {
    if (c == ' ') {
 

if НИКОГДА не будет вычисляться как true , потому operator>> что по умолчанию пропускает начальные пробелы, которые включают пробелы, табуляции и разрывы строк. Так c что никогда не будет такого пробельного символа , как ' ' .

Либо:

  • используйте std::noskipws , как @WhozCraig , предложенный в комментариях:
 while (ss >> std::noskipws >> c)
 
 while (ss.get(c))
 

При этом в вашем коде есть другие ошибки.

Вы возвращаете неизмененное s вместо подготовленного final_string .

Похоже, что ваш внутренний while цикл пытается свести к минимуму количество пробелов 2 в 1 пробел для вывода. Что нормально, за исключением того, что теряется символ, который завершает обнаруженный запуск.

Попробуйте что-то более похожее на это:

 std::string filter(const std::string amp;s) {
    std::istringstream iss(s);
    char c;
    std::string final_string;
    while (iss.get(c)) {
        if (c == ' ') {
            while (iss.get(c)) {
                if (c != ' ') {
                    final_string  = ' ';
                    final_string  = c;
                    break;
                }
            }
        }
        else {
            final_string  = c;
        }
    }
    return final_string;
}
 

Наконец, при создании нового std::string таким образом, как правило, более эффективно использовать std::ostringstream вместо operator = (если вы reserve() std::string не заранее), например:

 std::string filter(const std::string amp;s) {
    std::istringstream iss(s);
    char c;
    std::ostringstream final_string;
    while (iss.get(c)) {
        if (c == ' ') {
            while (iss.get(c)) {
                if (c != ' ') {
                    final_string << ' ' << c;
                    break;
                }
            }
        }
        else {
            final_string << c;
        }
    }
    return final_string.str();
}
 

Ответ №2:

Проблема заключается в std::stringstream::operator>>() функции. Я уверен, что ‘n’, ‘ t’ и пробел экранируются в функции. Следующий код выдаст тот же результат, который вы уже получили.

 while (ss >> c) {
        
    final_string  = c;
}
return final_string
 

Будет лучше использовать базовый код, а не stringstream, как показано ниже.

 std::string filter(std::string s) {
    char c;
    int i = 0;
    std::string final_string = "";
    do{
        c = s[i];
        if (c != ' ') {
            final_string  = c;
        }
        i  ;
    } while (c != 0);
    return final_string;
}
 

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

1. Сначала я подумал то же самое, пока не перечитал код OP и не понял, что он не удаляет ВСЕ символы пробела, а только (безуспешно пытаясь) минимизировать количество пробелов 2 в 1 пробел.

Ответ №3:

Предполагая, что целью является сжатие последовательных запусков табуляций и пробелов до одного пробела, но сохранение новых строк, я бы, вероятно, поступил немного по-другому.

 #include <locale>
#include <iostream>
#include <algorithm>
#include <iterator>
#include <sstream>
#include <numeric>

class my_ctype : public std::ctype<char> {
    mask my_table[table_size];
public:
    my_ctype(size_t refs = 0) : std::ctype<char>(amp;my_table[0], false, refs){
        std::copy_n(classic_table(), table_size, my_table);
        my_table['n'] = mask();
    }
};

int main() {
    std::stringstream input("EA/1n             n                $9.34n            n                ");
    std::locale ss(std::locale::classic(), new my_ctype);
    input.imbue(ss);

    std::string word;
    while (input >> word)
      std::cout << '"' << word << ""n";
}
 

Это основано на том факте, что поток уже использует языковой стандарт, чтобы выяснить, являются ли символы пробелами, и использует это, чтобы решить, что читать в строке. Итак, здесь мы воспользуемся этим и просто слегка изменим его, чтобы классифицировать новые строки как не являющиеся пробелами, чтобы они были сохранены в выходных данных. Кроме этого, поток уже делает в значительной степени то, что мы хотим, зная, как игнорировать последовательные прогоны пробелов, поэтому мы просто используем это вместо дублирования.