C ++ 'da virgülle ayrılmış tamsayıları / aralıkları ayrıştırma

Aug 17 2020

C ++ 'da aralıkları ve türün tek sayılarını içeren bir dize verildiğinde:

"2,3,4,7-9"

Onu formun bir vektörüne ayrıştırmak istiyorum:

2,3,4,7,8,9

Sayılar bir ile ayrılıyorsa -, aralıktaki tüm sayıları itmek istiyorum. Aksi takdirde tek bir sayıya basmak istiyorum.

Bu kod parçasını kullanmayı denedim:

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

Sorun, aralıklar için işe yaramamasıydı. Aralıktaki tüm sayıları değil, yalnızca dizedeki sayıları aldı.

Yanıtlar

3 d4rk4ng31 Aug 17 2020 at 08:32

@J dışında. Schultke'nin mükemmel örneği, normal ifadelerin aşağıdaki şekilde kullanılmasını öneririm:

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

Tabii ki, yine de biraz doğrulamaya ihtiyacınız var, ancak bu başlamanıza yardımcı olacaktır.

5 JanSchultke Aug 17 2020 at 07:54

Probleminiz iki ayrı problemden oluşmaktadır:

  1. dizeyi birden çok dizeye bölme ,
  2. her dizeyi ayrıştırırken bir vektöre sayıları veya sayı aralıklarını ekleme

İlk önce tüm dizeyi virgülle bölerseniz, aynı anda kısa çizgiyle bölme konusunda endişelenmenize gerek kalmaz. Bu, Böl ve Fethet yaklaşımı olarak adlandıracağınız şeydir .

Bölme ,

Bu soru size dizeyi virgülle nasıl bölebileceğinizi söylemelidir.

Ayrıştırma ve Ekleme std::vector<int>

Dizeyi virgülle böldüğünüzde, her dizge için bu işlevi çağırarak aralıkları tek tek sayılara dönüştürmeniz yeterlidir:

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

Bir tire ile bölmenin çok daha basit olduğuna dikkat edin çünkü dizede en fazla bir tire olabileceğini biliyoruz. std::string::substrbu durumda bunu yapmanın en kolay yolu. Unutmayın std::stoitamsayı bir içine oturması için çok büyük bir istisna atabilir int.

2 ArminMontigny Aug 17 2020 at 15:03

Şimdiye kadar hepsi çok güzel çözümler. Modern C ++ ve regex kullanarak, yalnızca birkaç satır kodla hepsi bir arada bir çözüm yapabilirsiniz.

Nasıl? İlk olarak, bir tamsayı VEYA bir tamsayı aralığı ile eşleşen bir normal ifade tanımlarız. Bunun gibi görünecek

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

Gerçekten çok basit. Önce menzil. Yani, birkaç rakam, ardından bir tire ve biraz daha rakam. Sonra düz tamsayı: Bazı rakamlar. Tüm rakamlar gruplar halinde yerleştirilir. (parantez). Kısa çizgi, eşleşen bir grupta değil.

Bunların hepsi o kadar kolaydır ki, daha fazla açıklamaya gerek yoktur.

Sonra std::regex_searchtüm eşleşmeler bulunana kadar bir döngü içinde çağırırız .

Her eşleşme için, alt eşleşme olup olmadığını, yani bir aralık olup olmadığını kontrol ederiz. Alt eşleşmelerimiz, bir aralığımız varsa, sonuçta alt eşleşmeler (dahil) arasındaki değerleri ekleriz std::vector.

Sadece düz bir tamsayımız varsa, o zaman sadece bu değeri ekleriz.

Bütün bunlar çok basit ve anlaşılması kolay bir program sağlar:

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

Daha fazla sorunuz olursa, cevap vermekten mutluluk duyarım.


Dil: C ++ 17 MS Visual Studio 19 Community Edition ile derlenmiş ve test edilmiştir

JohnPark Aug 17 2020 at 08:42

Numara dizenizi önceden işlemeyi ve ayırmayı düşünün. Aşağıdaki kodda, transform()int'in başarıyla ayrıştırılması için sınırlandırmalardan birini , -ve +bir boşluğa dönüştürülür std::istream_iterator.

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

Yukarıdaki kodun yalnızca 1 bayt uzunluğundaki sınırlamaları bölebileceğini unutmayın. Daha karmaşık ve daha uzun sınırlamalara ihtiyacınız varsa @ d4rk4ng31 yanıtına başvurmalısınız.