Analizar ints / int-rangos separados por comas en C ++

Aug 17 2020

Dada una cadena en C ++ que contiene rangos y números únicos del tipo:

"2,3,4,7-9"

Quiero analizarlo en un vector de la forma:

2,3,4,7,8,9

Si los números están separados por un, -entonces quiero presionar todos los números en el rango. De lo contrario, quiero presionar un solo número.

Intenté 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 ) );

El problema fue que no funcionó para los rangos. Solo tomó los números en la cadena, no todos los números en el rango.

Respuestas

3 d4rk4ng31 Aug 17 2020 at 08:32

Aparte de @J. Excelente ejemplo de Schultke, sugiero el uso de expresiones regulares de la siguiente manera:

#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;
}

Por supuesto, todavía necesita un poco de validación, sin embargo, esto lo ayudará a comenzar.

5 JanSchultke Aug 17 2020 at 07:54

Su problema consta de dos problemas separados:

  1. dividir la cadena en varias cadenas en ,
  2. agregar números o rangos de números a un vector al analizar cada cadena

Si primero divide toda la cadena en una coma, no tendrá que preocuparse por dividirla en un guión al mismo tiempo. Esto es lo que llamaría un enfoque de dividir y conquistar .

Dividiendo en ,

Esta pregunta debería decirle cómo puede dividir la cadena en una coma.

Analizar y agregar a std::vector<int>

Una vez que haya dividido la cadena en una coma, solo necesita convertir los rangos en números individuales llamando a esta función para cada cadena:

#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);
        }
    }
}

Tenga en cuenta que dividir en un guión es mucho más simple porque sabemos que puede haber como máximo un guión en la cadena. std::string::substres la forma más sencilla de hacerlo en este caso. Tenga en cuenta que std::stoipuede generar una excepción si el número entero es demasiado grande para caber en un int.

2 ArminMontigny Aug 17 2020 at 15:03

Todas muy buenas soluciones hasta ahora. Usando C ++ moderno y expresiones regulares, puede hacer una solución todo en uno con solo unas pocas líneas de código.

¿Cómo? Primero, definimos una expresión regular que coincide con un número entero O con un rango de números enteros. Se verá así

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

Realmente muy simple. Primero la gama. Entonces, algunos dígitos, seguidos de un guión y algunos dígitos más. Luego, el entero simple: algunos dígitos. Todos los dígitos se agrupan. (tirantes). El guión no está en un grupo coincidente.

Todo esto es tan fácil que no se necesitan más explicaciones.

Luego llamamos std::regex_searchen un bucle, hasta que se encuentran todas las coincidencias.

Para cada coincidencia, verificamos si hay subcoincidencias, es decir, un rango. Si tenemos sub-coincidencias, un rango, entonces agregamos los valores entre las sub-coincidencias (inclusive) al resultado std::vector.

Si solo tenemos un número entero, agregamos solo este valor.

Todo esto da un programa muy simple y 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;
}

Si tiene más preguntas, estaré encantado de responder.


Idioma: C ++ 17 Compilado y probado con MS Visual Studio 19 Community Edition

JohnPark Aug 17 2020 at 08:42

Considere preprocesar su cadena de números y dividirlos. En el siguiente código, transform()convertiría uno de los delims, , -y +, en un espacio para std::istream_iteratoranalizar int correctamente.

#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);
}

Tenga en cuenta que el código anterior solo puede dividir delimitaciones de longitud de 1 byte. Debe consultar la respuesta @ d4rk4ng31 si necesita delimitaciones más complejas y más largas.