Analysieren Sie durch Kommas getrennte Ints / Int-Bereiche in C ++

Aug 17 2020

Gegeben eine Zeichenfolge in C ++, die Bereiche und einzelne Zahlen der Art enthält:

"2,3,4,7-9"

Ich möchte es in einen Vektor der Form analysieren:

2,3,4,7,8,9

Wenn die Zahlen durch a getrennt sind, -möchte ich alle Zahlen im Bereich verschieben. Ansonsten möchte ich eine einzelne Nummer drücken.

Ich habe versucht, diesen Code zu verwenden:

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

Das Problem war, dass es für die Bereiche nicht funktionierte. Es wurden nur die Zahlen in der Zeichenfolge verwendet, nicht alle Zahlen im Bereich.

Antworten

3 d4rk4ng31 Aug 17 2020 at 08:32

Abgesehen von @J. Als Schultkes hervorragendes Beispiel schlage ich die Verwendung von Regexen folgendermaßen vor:

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

Natürlich benötigen Sie noch eine kleine Validierung, aber damit können Sie loslegen.

5 JanSchultke Aug 17 2020 at 07:54

Ihr Problem besteht aus zwei getrennten Problemen:

  1. Teilen der Zeichenfolge in mehrere Zeichenfolgen bei ,
  2. Hinzufügen von Zahlen oder Zahlenbereichen zu einem Vektor, wenn jede Zeichenfolge analysiert wird

Wenn Sie die gesamte Zeichenfolge zuerst durch ein Komma teilen, müssen Sie sich nicht darum kümmern, sie gleichzeitig durch einen Bindestrich zu teilen. Dies ist, was Sie einen Divide-and-Conquer- Ansatz nennen würden.

Aufteilen bei ,

Diese Frage sollte Ihnen sagen, wie Sie die Zeichenfolge durch ein Komma teilen können.

Analysieren und Hinzufügen zu std::vector<int>

Sobald Sie die Zeichenfolge durch ein Komma geteilt haben, müssen Sie nur noch Bereiche in einzelne Zahlen umwandeln, indem Sie diese Funktion für jede Zeichenfolge aufrufen:

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

Beachten Sie, dass das Teilen an einem Bindestrich viel einfacher ist, da wir wissen, dass die Zeichenfolge höchstens einen Bindestrich enthalten kann. std::string::substrist in diesem Fall der einfachste Weg, dies zu tun. Beachten Sie, dass std::stoidies eine Ausnahme auslösen kann, wenn die Ganzzahl zu groß ist, um in eine Ganzzahl zu passen int.

2 ArminMontigny Aug 17 2020 at 15:03

Alles sehr schöne Lösungen bisher. Mit modernem C ++ und Regex können Sie eine All-in-One-Lösung mit nur sehr wenigen Codezeilen erstellen.

Wie? Zunächst definieren wir einen regulären Ausdruck, der entweder einer Ganzzahl oder einem Ganzzahlbereich entspricht. Es wird so aussehen

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

Wirklich sehr einfach. Zuerst die Reichweite. Also einige Ziffern, gefolgt von einem Bindestrich und einigen weiteren Ziffern. Dann die einfache Ganzzahl: Einige Ziffern. Alle Ziffern werden in Gruppen eingeteilt. (Hosenträger). Der Bindestrich gehört nicht zu einer passenden Gruppe.

Dies ist alles so einfach, dass keine weitere Erklärung erforderlich ist.

Dann rufen wir std::regex_searcheine Schleife auf, bis alle Übereinstimmungen gefunden sind.

Für jede Übereinstimmung prüfen wir, ob es Unterübereinstimmungen gibt, dh einen Bereich. Wenn wir Unterübereinstimmungen, einen Bereich, haben, addieren wir die Werte zwischen den Unterübereinstimmungen (einschließlich) zu den Ergebnissen std::vector.

Wenn wir nur eine einfache Ganzzahl haben, addieren wir nur diesen Wert.

All dies ergibt ein sehr einfaches und leicht verständliches Programm:

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

Sollten Sie weitere Fragen haben, stehe ich Ihnen gerne zur Verfügung.


Sprache: C ++ 17 Kompiliert und getestet mit MS Visual Studio 19 Community Edition

JohnPark Aug 17 2020 at 08:42

Überlegen Sie, ob Sie Ihre Zahlenfolge vorverarbeiten und aufteilen möchten. transform()Konvertiert im folgenden Code eines der Delims , -und +in ein Leerzeichen, sodass std::istream_iteratorint erfolgreich analysiert wird.

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

Beachten Sie, dass der obige Code nur Delims mit einer Länge von 1 Byte aufteilen kann. Sie sollten sich auf die Antwort @ d4rk4ng31 beziehen, wenn Sie komplexere und längere Delims benötigen.