Заменить подстрок в строке после определенной подстроки

Суть: есть обычная строка std::string, в которой находится произвольной длины список чисел через запятую (например: "1,2,32454,0,67,123"). Нужно, чтобы после нуля, все последующие числа так же заменялись на 0 (т.е. "1,2,32454,0,0,0").

Как это можно сделать? Может, в boost-е есть что-то необходимое?


Ответы (1 шт):

Автор решения: Harry

Так устроит?

int main(int argc, char * argv[])
{
    string ins{"1,2,32454,0,67,123"}, s;

    bool z = false;
    ranges::copy(views::join(split_view(ins,","sv) |
                 views::transform([&z](auto q) {
                     string s(q.begin(),q.end());
                     if (z || stoi(s)==0) { z = true; return "0,"s; }
                     else return s+","; })),
                 back_inserter(s));
    s.pop_back();

    cout << s;
}

Чисто теоретически (не могу у себя проверить) должно при поддержке C++23 работать

int main(int argc, char * argv[])
{
    string ins{"1,2,32454,0,67,123"}, s;

    bool z = false;
    ranges::copy(views::join_with(split_view(ins,","sv) |
                 views::transform([&z](auto q) {
                     string s(q.begin(),q.end());
                     if (z || stoi(s)==0) { z = true; return "0"s; }
                     else return s; }),","),
                 back_inserter(s));
    cout << s;
}

но тут я никакой гарантии не даю...

@D4rki, это, конечно, из пушки по воробьям. Проще каким-нибудь strtok'ом поделить строку на части и сшить наново с учетом нолей...

Update 1

Как я к этому пришел... Глобальное решение — делим строку по запятым, встретив 0, запоминаем, дальше все заменяем на 0, и собираем строку вновь.

Итак, деление. Это делается с помощью

split_view(ins,","sv)

sv — потому что функция требует string_view, без этого не понимает, что надо, и не работает.

Итак, мы получили набор string_view, представляющих числа. Дальше мы преобразуем их в строки, которые либо совпадают с исходными, либо нули (после обнаружения первого нуля). Сначала я думал сделать на выходе числа, но потом решил, что особого смысла в двойном преобразовании нет. Хотя строку все равно пришлось строить из string_view — чтобы получить целочисленное значение. Почему не сравнение со строкой? Да чтоб не упустить случай 000, например.

Это преобразование выполняется с помощью transform и лямбда-функции, которая захватывает переменную z — флаг, был ли уже нуль или нет:

[&z](auto q) {
    string s(q.begin(),q.end());
    if (z || stoi(s)==0) { 
        z = true;           // Выставляем флаг.
        return "0,"s;       // Теперь нули 
    } 
    else return s+","; 
}                           // Строка, что и была

Можно сэкономить и переписать, чтоб конвертирования в строку при z == true не было вовсе. Хотите — займитесь... Запятые добавил, потому что надо же разделитель, а готовой функции копирования с разделителем не нашел.

Далее собираем все это в одну последовательность с помощью views::join(), которую легко собрать в одну строку с помощью ranges::copy(), который копирует последовательность с использованием в качестве целевого итератора back_inserter(s), где s — целевая строка.

Последний штрих — удаление последней запятой s.pop_back();. Всё.

Если использовать join_with(), то вроде бы можно просто указать запятую как разделитель, но это я проверить у себя не смог, не реализовано это в VC++2019.

Update 2

Но если бы это было реально нужно мне, я бы написал что-то вроде

string trans(const string& in)
{
    size_t pos = in.npos;
    if (in.starts_with("0,")) pos = 0;
    if (pos == in.npos) pos = in.find(",0,");
    if (pos == in.npos) return in;
    if (pos) ++pos;
    size_t commas = count(in.begin()+pos,in.end(),',');
    string res = in.substr(0,pos);
    for(int i = 0; i <= commas; res += i++ == commas ? "0" : "0,");
    return res;
}

и не мучился :)

→ Ссылка