Phân tích cú pháp ints / int-range được phân tách bằng dấu phẩy trong C ++
Cho một chuỗi trong C ++ chứa các phạm vi và các số đơn thuộc loại:
"2,3,4,7-9"
Tôi muốn phân tích cú pháp nó thành một vectơ có dạng:
2,3,4,7,8,9
Nếu các số được phân tách bằng a -thì tôi muốn đẩy tất cả các số trong phạm vi. Nếu không, tôi muốn đẩy một số duy nhất.
Tôi đã thử sử dụng đoạn mã này:
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 ) );
Vấn đề là nó không hoạt động cho các phạm vi. Nó chỉ lấy các số trong chuỗi, không phải tất cả các số trong phạm vi.
Trả lời
Ngoài @J. Ví dụ tuyệt vời của Schultke, tôi đề nghị sử dụng regexes theo cách sau:
#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;
}
Tất nhiên, bạn vẫn cần xác thực một chút, tuy nhiên, điều này sẽ giúp bạn bắt đầu.
Vấn đề của bạn bao gồm hai vấn đề riêng biệt:
- chia chuỗi thành nhiều chuỗi tại
, - thêm số hoặc dải số vào vectơ khi phân tích cú pháp mỗi chuỗi
Nếu lần đầu tiên bạn chia toàn bộ chuỗi bằng dấu phẩy, bạn sẽ không phải lo lắng về việc chia nó tại dấu gạch nối cùng một lúc. Đây là cách mà bạn gọi là phương pháp Chia rẽ và Chinh phục .
Tách vào ,
Câu hỏi này sẽ cho bạn biết làm thế nào bạn có thể chia chuỗi bằng dấu phẩy.
Phân tích cú pháp và thêm vào std::vector<int>
Khi bạn đã chia chuỗi bằng dấu phẩy, bạn chỉ cần chuyển các dải ô thành các số riêng lẻ bằng cách gọi hàm này cho mỗi chuỗi:
#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);
}
}
}
Lưu ý rằng việc tách ở dấu gạch ngang đơn giản hơn nhiều vì chúng ta biết rằng có thể có nhiều nhất một dấu gạch nối trong chuỗi. std::string::substrlà cách dễ nhất để làm điều đó trong trường hợp này. Hãy lưu ý rằng std::stoicó thể ném ra một ngoại lệ nếu số nguyên quá lớn để vừa với một int.
Tất cả các giải pháp rất tốt cho đến nay. Sử dụng C ++ và regex hiện đại, bạn có thể thực hiện một giải pháp tất cả trong một chỉ với rất ít dòng mã.
Làm sao? Đầu tiên, chúng tôi xác định một regex khớp với một số nguyên HOẶC một phạm vi số nguyên. Nó sẽ trông giống thế này
((\d+)-(\d+))|(\d+)
Thực sự rất đơn giản. Đầu tiên là phạm vi. Vì vậy, một số chữ số, theo sau là dấu gạch nối và một số chữ số khác. Sau đó là số nguyên: Một số chữ số. Tất cả các chữ số được đặt trong các nhóm. (niềng răng). Dấu gạch nối không nằm trong nhóm phù hợp.
Điều này rất dễ dàng mà không cần giải thích thêm.
Sau đó, chúng tôi gọi std::regex_searchtrong một vòng lặp, cho đến khi tìm thấy tất cả các kết quả phù hợp.
Đối với mỗi trận đấu, chúng tôi kiểm tra, nếu có các trận đấu phụ, nghĩa là một phạm vi. Nếu chúng tôi có các kết quả phù hợp phụ, một dải ô, thì chúng tôi thêm các giá trị giữa các kết quả phù hợp phụ (bao gồm) vào kết quả std::vector.
Nếu chúng ta chỉ có một số nguyên thuần túy, thì chúng ta chỉ thêm giá trị này.
Tất cả điều này tạo ra một chương trình rất đơn giản và dễ hiểu:
#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;
}
Nếu bạn có thêm câu hỏi, tôi rất sẵn lòng trả lời.
Ngôn ngữ: C ++ 17 Được biên dịch và thử nghiệm với MS Visual Studio 19 Community Edition
Cân nhắc xử lý trước chuỗi số của bạn và chia nhỏ chúng. Trong đoạn mã sau, transform()sẽ chuyển đổi một trong các dấu phân cách , -và +, thành khoảng trắng để std::istream_iteratorphân tích cú pháp int thành công.
#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);
}
Lưu ý rằng đoạn mã trên chỉ có thể chia các dấu phân cách độ dài 1 byte. Bạn nên tham khảo câu trả lời @ d4rk4ng31 nếu bạn cần các dấu phân cách phức tạp hơn và dài hơn.