Анализировать разделенные запятыми целые / целые диапазоны в C ++
Учитывая строку в C ++, содержащую диапазоны и одиночные числа типа:
"2,3,4,7-9"
Я хочу разобрать его на вектор формы:
2,3,4,7,8,9
Если числа разделены -
знаком, я хочу вставить все числа в диапазоне. В противном случае я хочу нажать одно число.
Я пробовал использовать этот фрагмент кода:
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 ) );
Проблема заключалась в том, что для диапазонов не работало. Потребовались только числа в строке, а не все числа в диапазоне.
Ответы
Помимо @J. Отличный пример Шултке, я предлагаю использовать регулярные выражения следующим образом:
#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;
}
Конечно, вам все еще нужна небольшая проверка, однако это поможет вам начать.
Ваша проблема состоит из двух отдельных проблем:
- разделение строки на несколько строк в
,
- добавление чисел или диапазонов чисел к вектору при синтаксическом анализе каждой строки
Если вы сначала разделите всю строку через запятую, вам не придется беспокоиться о разделении ее через дефис одновременно. Это то, что вы бы назвали подходом « разделяй и властвуй» .
Разделение на ,
Этот вопрос должен рассказать вам, как можно разделить строку запятой.
Разбор и добавление в 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
.
Пока все очень хорошие решения. Используя современный 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
Подумайте о предварительной обработке вашей числовой строки и разделении их. В следующем коде transform()
преобразует один из разделителей ,
-
и +
в пробел, чтобы 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 байт. Вам следует обратиться к ответу @ d4rk4ng31, если вам нужны более сложные и длинные разделители.