Analisar ints / int-intervalos separados por vírgula em C ++

Aug 17 2020

Dada uma string em C ++ contendo intervalos e números únicos do tipo:

"2,3,4,7-9"

Quero analisá-lo em um vetor da forma:

2,3,4,7,8,9

Se os números forem separados por a -, quero empurrar todos os números do intervalo. Caso contrário, quero empurrar um único número.

Tentei usar este código:

const char *NumX = "2,3,4-7";
std::vector<int> inputs;
std::istringstream in( NumX );
std::copy( std::istream_iterator<int>( in ), std::istream_iterator<int>(),
           std::back_inserter( inputs ) );

O problema era que não funcionava para os intervalos. Levou apenas os números da string, não todos os números do intervalo.

Respostas

3 d4rk4ng31 Aug 17 2020 at 08:32

Além de @J. Excelente exemplo de Schultke, sugiro o uso de regexes da seguinte maneira:

#include <algorithm>
#include <iostream>
#include <regex>
#include <string>
#include <vector>

void process(std::string str, std::vector<int>& num_vec) {
    str.erase(--str.end());
    for (int i = str.front() - '0'; i <= str.back() - '0'; i++) {
        num_vec.push_back(i);                                                     
    }
}

int main() {
    std::string str("1,2,3,5-6,7,8");
    str += "#";
    std::regex vec_of_blocks(".*?\,|.*?\#");
    auto blocks_begin = std::sregex_iterator(str.begin(), str.end(), vec_of_blocks);
    auto blocks_end = std::sregex_iterator();
    std::vector<int> vec_of_numbers;
    for (std::sregex_iterator regex_it = blocks_begin; regex_it != blocks_end; regex_it++) {
        std::smatch match = *regex_it;
        std::string block = match.str();
        if (std::find(block.begin(), block.end(), '-') != block.end()) {
            process(block, vec_of_numbers);
        }
        else {
            vec_of_numbers.push_back(std::atoi(block.c_str()));
        }
    }
    return 0;
}

Claro, você ainda precisa de um pouco de validação, no entanto, isso o ajudará a começar.

5 JanSchultke Aug 17 2020 at 07:54

Seu problema consiste em dois problemas separados:

  1. dividir a corda em várias cordas em ,
  2. adicionar números ou intervalos de números a um vetor ao analisar cada string

Se você primeiro dividir a string inteira com uma vírgula, não precisará se preocupar em dividi-la com um hífen ao mesmo tempo. Isso é o que você chamaria de abordagem de dividir e conquistar .

Dividindo em ,

Esta pergunta deve lhe dizer como você pode dividir a string em uma vírgula.

Análise e adição a std::vector<int>

Depois de dividir a string em uma vírgula, você só precisa transformar os intervalos em números individuais chamando esta função para cada string:

#include <vector>
#include <string>

void push_range_or_number(const std::string &str, std::vector<int> &out) {
    size_t hyphen_index;
    // stoi will store the index of the first non-digit in hyphen_index.
    int first = std::stoi(str, &hyphen_index);
    out.push_back(first);

    // If the hyphen_index is the equal to the length of the string,
    // there is no other number.
    // Otherwise, we parse the second number here:
    if (hyphen_index != str.size()) {
        int second = std::stoi(str.substr(hyphen_index + 1), &hyphen_index);
        for (int i = first + 1; i <= second; ++i) {
            out.push_back(i);
        }
    }
}

Observe que dividir em um hífen é muito mais simples porque sabemos que pode haver no máximo um hífen na string. std::string::substré a maneira mais fácil de fazer isso neste caso. Esteja ciente de que std::stoipode lançar uma exceção se o inteiro for muito grande para caber em um int.

2 ArminMontigny Aug 17 2020 at 15:03

Todas as soluções muito boas até agora. Usando C ++ e regex modernos, você pode fazer uma solução completa com apenas algumas linhas de código.

Quão? Primeiro, definimos uma regex que corresponde a um inteiro OU a um intervalo de inteiros. Isso parecerá assim

((\d+)-(\d+))|(\d+)

Realmente muito simples. Primeiro, o intervalo. Então, alguns dígitos, seguidos de um hífen e mais alguns dígitos. Em seguida, o número inteiro simples: Alguns dígitos. Todos os dígitos são colocados em grupos. (suspensórios). O hífen não está em um grupo correspondente.

Isso tudo é tão fácil que nenhuma explicação adicional é necessária.

Então chamamos std::regex_searchem um loop, até que todas as correspondências sejam encontradas.

Para cada correspondência, verificamos se há sub-correspondências, ou seja, um intervalo. Se tivermos sub-correspondências, um intervalo, então adicionamos os valores entre as sub-correspondências (inclusive) ao resultado std::vector.

Se tivermos apenas um inteiro simples, adicionaremos apenas este valor.

Tudo isso dá um programa muito simples e fácil de entender:

#include <iostream>
#include <string>
#include <vector>
#include <regex>

const std::string test{ "2,3,4,7-9" };

const std::regex re{ R"(((\d+)-(\d+))|(\d+))" };
std::smatch sm{};

int main() {
    // Here we will store the resulting data
    std::vector<int> data{};

    // Search all occureences of integers OR ranges
    for (std::string s{ test }; std::regex_search(s, sm, re); s = sm.suffix()) {

        // We found something. Was it a range?
        if (sm[1].str().length())

            // Yes, range, add all values within to the vector  
            for (int i{ std::stoi(sm[2]) }; i <= std::stoi(sm[3]); ++i) data.push_back(i);
        else
            // No, no range, just a plain integer value. Add it to the vector
            data.push_back(std::stoi(sm[0]));
    }
    // Show result
    for (const int i : data) std::cout << i << '\n';
    return 0;
}

Se você tiver mais perguntas, terei prazer em responder.


Linguagem: C ++ 17 Compilado e testado com MS Visual Studio 19 Community Edition

JohnPark Aug 17 2020 at 08:42

Considere pré-processar sua sequência numérica e dividi-los. No código a seguir, transform()iria converter um dos delims, , -e +, em um espaço para que std::istream_iteratorparse int com sucesso.

#include <cstdlib>
#include <algorithm>
#include <string>
#include <vector>
#include <iostream>
#include <sstream>

int main(void)
{
    std::string nums = "2,3,4-7,9+10";
    const std::string delim_to_convert = ",-+";  // , - and +
    std::transform(nums.cbegin(), nums.cend(), nums.begin(),
            [&delim_to_convert](char ch) {return (delim_to_convert.find(ch) != string::npos) ? ' ' : ch; });

    std::istringstream ss(nums);
    auto inputs = std::vector<int>(std::istream_iterator<int>(ss), {});

    exit(EXIT_SUCCESS);
}

Observe que o código acima pode dividir apenas delims de comprimento de 1 byte. Você deve consultar a resposta @ d4rk4ng31 se precisar de deliminações mais complexas e mais longas.