C ++에서 쉼표로 구분 된 ints / int-ranges 구문 분석

Aug 17 2020

범위와 종류의 단일 숫자를 포함하는 C ++의 문자열이 제공됩니다.

"2,3,4,7-9"

다음과 같은 형식의 벡터로 구문 분석하고 싶습니다.

2,3,4,7,8,9

숫자가 a -로 구분 되면 범위의 모든 숫자를 밀어 넣고 싶습니다. 그렇지 않으면 하나의 숫자를 밀고 싶습니다.

이 코드를 사용해 보았습니다.

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

문제는 범위에서 작동하지 않는다는 것입니다. 범위의 모든 숫자가 아니라 문자열의 숫자 만 취했습니다.

답변

3 d4rk4ng31 Aug 17 2020 at 08:32

@J를 제외하고. Schultke의 훌륭한 예는 다음과 같은 방식으로 정규식 사용을 제안합니다.

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

물론, 여전히 약간의 유효성 검사가 필요하지만 시작하면됩니다.

5 JanSchultke Aug 17 2020 at 07:54

문제는 두 가지 개별 문제로 구성됩니다.

  1. 문자열을 여러 문자열로 분할 ,
  2. 각 문자열을 구문 분석 할 때 벡터에 숫자 또는 숫자 범위 추가

처음에 전체 문자열을 쉼표로 분할하는 경우 동시에 하이픈으로 분할하는 것에 대해 걱정할 필요가 없습니다. 이것이 바로 Divide-and-Conquer 접근 방식입니다.

분할 ,

이 질문 은 쉼표로 문자열을 분할하는 방법을 알려줍니다.

구문 분석 및 추가 std::vector<int>

쉼표로 문자열을 분할 한 후에는 각 문자열에 대해이 함수를 호출하여 범위를 개별 숫자로 바꾸면됩니다.

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

하이픈으로 분할하는 것은 문자열에 최대 하나의 하이픈이있을 수 있다는 것을 알기 때문에 훨씬 더 간단합니다. std::string::substr이 경우 가장 쉬운 방법입니다. 그주의 std::stoi정수가에 맞게 너무 큰 경우 예외를 던질 수 있습니다 int.

2 ArminMontigny Aug 17 2020 at 15:03

지금까지 모든 아주 좋은 솔루션. 최신 C ++ 및 정규식을 사용하면 몇 줄의 코드만으로 올인원 솔루션을 수행 할 수 있습니다.

어떻게? 먼저 정수 또는 정수 범위와 일치하는 정규식을 정의합니다. 이렇게 보일 것입니다

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

정말 간단합니다. 먼저 범위. 따라서 일부 숫자, 하이픈 및 더 많은 숫자가 뒤 따릅니다. 그런 다음 일반 정수 : 일부 자릿수. 모든 숫자는 그룹으로 분류됩니다. (바지 멜빵). 하이픈이 일치하는 그룹에 없습니다.

이 모든 것이 너무 쉬워서 추가 설명이 필요하지 않습니다.

그런 다음 std::regex_search모든 일치 항목을 찾을 때까지 루프를 호출 합니다.

각 일치에 대해 범위를 의미하는 하위 일치가 있는지 확인합니다. 하위 일치, 범위가있는 경우 하위 일치 (포함) 사이의 값을 결과에 추가 std::vector합니다.

일반 정수만 있으면이 값만 더합니다.

이 모든 것이 매우 간단하고 이해하기 쉬운 프로그램을 제공합니다.

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

더 많은 질문이 있으면 기꺼이 답변 해드립니다.


언어 : C ++ 17 MS Visual Studio 19 Community Edition으로 컴파일 및 테스트 됨

JohnPark Aug 17 2020 at 08:42

숫자 문자열을 사전 처리하고 분할하는 것을 고려하십시오. 다음 코드에서 transform()delims 중 하나를 변환 할 , -+그래서 공간으로, std::istream_iterator성공적으로 구문 분석 INT.

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

위의 코드는 1 바이트 길이의 delim 만 분할 할 수 있습니다. 더 복잡하고 더 긴 delims가 필요한 경우 @ d4rk4ng31 답변을 참조해야합니다.