how to extract integer from a string? c++ - c++

i have this line taken from a txt file (first line in the file):
#operation=1(create circle and add to picture) name X Y radius.
why does this code doesnt take the integer 1 and puts it into k?
Circle Circle::CreateCirc(Circle c){
int k;
ifstream myfile("cmd.txt");
if (!myfile.is_open())
cout<<"Unable to open the requested file "<<endl;
string line,line2="create circle";
for (int i=1;i<countrows();i++)
{
getline(myfile,line);
if (line.find(line2)!=string::npos)
{
istringstream ss(line);
ss>>k;
cout<<k<<endl;
}
}
return c;
}
instead im getting adress memory...help plz

Because the line doesn't start with a number. You'll need to skip over the #operation= part before extracting a number.
You should check the result of the extraction, and of getline, to help identify what's going wrong when these fail.
Also, if countrows() returns the expected number of rows in the file, then your loop would miss out the last one. Either loop from zero, or while i <= countrows(); or, if you want to process every line in the file, you could simply loop while (getline(myfile,line)).

If the actual text in the file you try to read starts with "#operation=1" and you want the number 1 from that, you can't use the simple input operator. It will read the character '#' first, which isn't a digit and so the parsing will fail and k will not be initialized. And if k is not initialized, it will be of indeterminate value, and reading that value will lead to undefined behavior and seemingly random output.
You need to check that the extraction worked:
if (ss >> k)
std::cout << k << '\n';
That won't solve your problem though, as like I said above, you can't use the simple input operator here. You need to parse the string using other methods. One way might be to find the equal character '=' and get a sub-string after that to try and extract the number.

try this:
Circle Circle::CreateCirc(Circle c){
const std::streamsize ALL = std::numeric_limits< std::streamsize >::max(); // #include <limits> needed
int k;
ifstream myfile("cmd.txt");
if (!myfile.is_open())
cout<<"Unable to open the requested file "<<endl;
for (int i=1;i<countrows(); ++i, myfile.ignore(ALL,'\n') ) // skip rest of the line
{
if( myfile.ignore(ALL,'=') >> k )
{
cout<<k<<endl;
}
else
break; // read error
}
return c;
}

EDIT: A way to do it not much bit a little closer to the way you were trying to do it using atoi() rather than streams.
#include <iostream>
#include <cstdlib> // for atoi()
int main(){
std::string str = "#operation=1(create circle and add to picture) name X Y radius.";
int k;
std::string line=str, line2="(create circle";
std::size_t fnd = line.find(line2);
if (fnd!=std::string::npos)
{
k = atoi(&str[fnd-1]); // int atoi(const char *str) == argument to integer
std::cout<< k << " " << str[fnd-1] << str[fnd] << " ";
}
}
There are a few ways to extract an integer from a string but i like to filter out the digit from the string;
#include <iostream>
int main(){
std::string str = "#operation=1(create circle and add to picture) name X Y radius.";
int k = 0;
// an array of our base10 digits to filter through and compare
const char digit[] = {'0','1','2','3','4','5','6','7','8','9'};
for(int s_filter = 0; s_filter<str.size(); ++s_filter){
for(int d_filter = 0; d_filter<10; ++d_filter){
// filter through each char in string and
// also filter through each digit before the next char
if(digit[d_filter] == str[s_filter]) {
// if so the char is equal to one of our digits
k = d_filter;// and d_filter is equal to our digit
break;
} else continue;
}
}
switch(k) {
case 1:
std::cout<< "k == 1";
// do stuff for operation 1..
return 0;
case 2:
std::cout<< "k != 1";
// do more stuff
break;
//case 3: ..etc.. etc..
default:
std::cout<< "not a digit";
return 1;
}
}

// find_num.cpp (cX) 2015 adolfo.dimare#gmail.com
// http://stackoverflow.com/questions/21115457/
#include <string> // std::string
#include <cctype> // isnum
/// Find the number in 'str' starting at position 'pos'.
/// Returns the position of the first digit of the number.
/// Returns std::string::npos when no further numbers appear within 'str'.
/// Returns std::string::npos when 'pos >= str.length()'.
size_t find_num( const std::string str, size_t pos ) {
size_t len = str.length();
bool isNegative = false;
while ( pos < len ) {
if ( isdigit(str[pos]) ) {
return ( isNegative ? pos-1 : pos );
}
else if ( str[pos]=='-' ) {
isNegative = true;
}
else {
isNegative = false;
}
++pos;
}
return std::string::npos;
}
#include <cassert> // assert()
#include <cstring> // strlen();
int main() {
std::string str;
str = "";
assert( std::string::npos == find_num( str, 0 ) );
assert( std::string::npos == find_num( str, 9 ) );
str = "#operation=1(create circle and add to picture) name X Y radius.";
assert( strlen("#operation=") == find_num( str, 0 ) );
str = "abcd 111 xyx 12.33 alpha 345.12e-23";
/// 0123456789.123456789.123456789.123456789.
assert( 5 == find_num( str, 0 ) );
assert( 13 == find_num( str, 5+3 ) );
assert( 25 == find_num( str, 20 ) );
str = "abcd-111 xyx-12.33 alpha-345.12e-23";
/// 0123456789.123456789.123456789.123456789.
assert( 4 == find_num( str, 0 ) );
assert( 12 == find_num( str, 5+3 ) );
assert( 24 == find_num( str, 20 ) );
str = "-1";
assert( 0 == find_num( str, 0 ) );
assert( 1 == find_num( str, 1 ) );
assert( std::string::npos == find_num( str, 2 ) );
assert( std::string::npos == find_num( str, strlen("-1") ) );
return 0;
}

Related

Unpredicted infinite for loop in c++ [duplicate]

This question already has answers here:
How do unsigned integers work
(3 answers)
Closed 2 years ago.
I am writing a program to return first occurrence of the character and the frequency of that character in the string.
For loop in the function is executing infinite times and if condition and block is not executing even once.
What is the problem?
string::size_type find_ch(string &str,char ch,int& i_r)
{
string::size_type first=0;
for(auto i=str.size()-1;i>=0;i--)
{
cout<<"\nInside a for loop."<<endl;
if(str[i]==ch)
{
cout<<"Inside if."<<endl;
first=i+1;
i_r++;
}
}
return first;
}
This loop:
for(auto i = str.size() - 1; i>=0; i--)
will only exit when i is less than 0. But this is not a valid value for an unsigned int. The value will wrap to the maximum unsigned int, and you get an infinite loop.
Note that .size() on a std::string returns a size_t, which is basically an unsigned int type.
One way to fix this would be to cast the return type of .size() to an int, like this:
for(auto i = static_cast<int>(str.size()) - 1; i>=0; i--)
Note that it's important to do the cast before subtracting 1, otherwise you'll get the wrong answer when str is empty.
In c++20, you can avoid this issue entirely by calling the std::ssize() free function, which returns a signed version of the size.
The function definition in general is wrong.
For example if the given character is nit found then why does the function return 0 that is a valid position?
Returning the value first=i+1; will only confuse users of the function. The function shall return std::string::npos if the given character is not found.
Also it is entirely unclear why the loop starts from the end of the string while you need to return the first position of the character.
As for the infinite loop then in the loop there is used variable i that has the unsigned integer type std::string::size_type a value of which never can be negative.
for(auto i=str.size()-1;i>=0;i--)
^^^^^^^^^^^^^^^^^^^
That is the condition i >= 0 is always true by the definition.
The function should be defined the following way
std::pair<std::string::size_type, std::string::size_type> find_ch( const std::string &str, char ch )
{
auto n = str.find( ch );
std::pair<std::string::size_type, std::string::size_type> p( n, 0 );
if ( n != std::string::npos )
{
++p.second;
while ( ( n = str.find( ch, n + 1 ) ) != std::string::npos ) ++p.second;
}
return p;
}
Here is a demonstrative program.
#include <iostream>
#include <string>
#include <utility>
std::pair<std::string::size_type, std::string::size_type> find_ch( const std::string &str, char ch )
{
auto n = str.find( ch );
std::pair<std::string::size_type, std::string::size_type> p( n, 0 );
if ( n != std::string::npos )
{
++p.second;
while ( ( n = str.find( ch, n + 1 ) ) != std::string::npos ) ++p.second;
}
return p;
}
int main()
{
std::string s( "C++ is not the same as C" );
auto p = find_ch( s, 'C' );
if ( p.first != std::string::npos )
{
std::cout << p.first << ": " << p.second << '\n';
}
return 0;
}
The program output is
0: 2
If you are not allowed to use methods of the class std::string then just substitute calls of the method find in the function above to while loops as it is shown below.
#include <iostream>
#include <string>
#include <utility>
std::pair<std::string::size_type, std::string::size_type> find_ch( const std::string &str, char ch )
{
std::pair<std::string::size_type, std::string::size_type> p( std::string::npos, 0 );
std::string::size_type n = 0;
while ( n < str.size() && str[n] != ch ) ++n;
if ( n != str.size() )
{
p.first = n;
++p.second;
while ( ++n != str.size() )
{
if( str[n] == ch ) ++p.second;
}
}
return p;
}
int main()
{
std::string s( "C++ is not the same as C" );
auto p = find_ch( s, 'C' );
if ( p.first != std::string::npos )
{
std::cout << p.first << ": " << p.second << '\n';
}
return 0;
}
Here is an answer similar to #Vlad From Moscow, but uses string functions, and the algorithm std::count.
#include <algorithm>
#include <string>
#include <utility>
#include <iostream>
std::pair<int,int> find_ch(const std::string &str, char ch)
{
std::pair<int, int> ret;
auto pos = str.find_first_of(ch);
if ( pos == std::string::npos )
return {-1,0}; // not found
return { pos, std::count(str.begin() + pos, str.end(), ch) };
}
int main()
{
auto pr = find_ch("abccabc", 'b');
std::cout << "character b is at position " << pr.first << ". Character count is " << pr.second << "\n";
pr = find_ch("abccabc", 'c');
std::cout << "character c is at position " << pr.first << ". Character count is " << pr.second;
}
Output:
character b is at position 1. Character count is 2
character c is at position 2. Character count is 3
Each line of the function basically describes what is being done:
find_first_of the character in the string. If found then return that position and the std::count of that character starting at the first occurrence.
Note the brevity and self-documented way the function is written. A C++ programmer could look at that code and immediately know what it does, due to the names of the functions that are being called.
Writing loops going backwards (as you originally did) with variables incremented here and there, the programmer has to sit down and go through the code to figure out what it is doing, and what the purpose of the function is.

character array validation in C++

I have to validate my data "123-AB-12345" as correct using character array. I set char array size to 13 including '\0'. The function must return false if the condition is not satisfied. ALL I done is that the program validates these 12 chracters but IT DOESN'T RETURN FALSE WHEN I PASS MORE VALUES like as "123-AB-123456789" and it is returning true. My program is follwing:
#include<iostream>
using namespace std;
bool isValidBookId(char bookId[13]);
int main()
{
char book[13];
cin.getline(book,13);
bool id = isValidBookId(book);
cout<<id;
}
bool isValidBookId( char bookId[13] ) {
/* Valid: 098-EN-98712 */
if ( bookId[12] != '\0' )
return false;
if ( bookId[3] != '-' )
return false;
if ( bookId[6] != '-' )
return false;
for ( int i = 0; i < 3; i++ ) {
if ( bookId[i] < '0' || bookId[i] > '9' ) {
return false;
}
}
for ( int i = 4; i < 6; i++ ) {
if ( bookId[i] < 'A' || bookId[i] > 'Z' ) {
return false;
}
}
for ( int i = 7; i < 12 || bookId[12]!='\0'; i++ ) {
if(bookId[13]!='\0'){
return false;
}
if ( bookId[i] < '0' || bookId[i] > '9' ) {
return false;
}
}
return true;
}
I don't know why this condition is not working.
if ( bookId[12] != '\0' )
return false;
Looking at your code, the only explanation is that the last character of your array is null. Try specifying a delimiter character like this:
cin.getline(book, 13, '\n');
I'd refer to this link:
"A null character ('\0') is automatically appended to the written sequence if n is greater than zero, even if an empty string is extracted."
Your issue is in your input function:
You only read up to 12 characters. So you cannot have more than 12 characters.
You might use std::string
bool isValidBookId(const std::string&s) {
static const std::regex r{R"(^\d{3}-[A-Z]{2}-\d{5}$)"};
return std::regex_match(std::begin(s), std::end(s), r);
}
int main()
{
std::string s;
while (std::getline(std::cin, s))
{
std::cout << s << ": " << isValidBookId(s) << std::endl;
}
}
Demo
Or bigger buffer:
bool isValidBookId(const char (&s)[14]) {
static const std::regex r{R"(^\d{3}-[A-Z]{2}-\d{5}\0$)"};
return std::regex_match(std::begin(s), std::end(s) - 1, r);
}
int main()
{
char s[14];
while (true)
{
bool b = !!std::cin.getline(s, 14);
if (s[0] == '\0') break;
std::cout << " " << s << ": " << isValidBookId(s) << std::endl;
if (!b) {
std::cin.clear();
std::cin.ignore(255, '\n');
}
}
}
Demo
All conditions are correct. Your problem is created initially when entering data.
cin.getline(book,13);
The 'getline' method accepts any number of characters (of course within reason), but allocates space only for the first 12 characters and the 13th will always be only '\ 0'. If you want to write more characters, let me enter more characters.
The correct option is:
bool isValidBookId(char bookId[100]); // now there is a restriction of not 13 characters, but 100
int main()
{
char book[100]; // now there is a restriction of not 13 characters, but 100
cin.getline(book,100); // now there is a restriction of not 13 characters, but 100
}
bool isValidBookId( char bookId[100] ) // now there is a restriction of not 13 characters, but 100
{...}
As pointed above,
cin.getline(book,13) wont save more than 12 characters in your array.
Instead replace your code with :
char book[100]; // To save upto 99 characters
cin.getline(book,100);
And change
isValidBookId(char bookId[13])
to
isValidBookId( char bookId[100] )
and remove all the checks of
bookId[12]!='\0' inside this isValidBookId function, as there can be any character at 12th index.

C++ Extracting the integer in a string with multiple delimiters

I am trying to extract the integers from a string. What could be wrong here?
I only get the first value. How can I get it working even with zero's in the string?
string str="91,43,3,23,0;6,9,0-4,29,24";
std::stringstream ss(str);
int x;
while(ss >> x)
{
cout<<"GOT->"<<x<<endl;
char c;
ss >> c; //Discard a non space char.
if(c != ',' || c != '-' || c != ';')
{
ss.unget();
}
}
Look very closely at this line:
if(c != ',' || c != '-' || c != ';')
Note that this condition is always true, so you are always ungeting the punctuation character. The next read will then always fail as it reads punctuation when a number is expected. Changing the ||'s to &&'s should fix the problem.
Of course, your code assumes that str is formatted in a very particular way and might break when given a differently-formatted str value. Just be aware of that.
u can get this done with boost split.
int main() {
std::stringstream ss;
std::string inputString = "91,43,3,23,0;6,9,0-4,29,24";
std::string delimiters("|,:-;");
std::vector<std::string> parts;
boost::split(parts, inputString, boost::is_any_of(delimiters));
for(int i = 0; i<parts.size();i++ ) {
std::cout <<parts[i] << " ";
}
return 0;
}
Output (Just integers) :- 91 43 3 23 0 6 9 0 4 29 24
This will change the string into char and write off : , ; -
#include <iostream>
#include <string>
using namespace std;
int main(){
string str = "91,43,3,23,0;6,9,0-4,29,24";
str.c_str(); // ex: string a; --> char a[];
char a[99];
int j = 0;
int x;
for(int i = 0; i < str.length(); i++){
if (str[i]!=',' && str[i]!=';' && str[i]!='-'){
a[j] = str[i];
j++;
}
}
return 0;
}
Hope this will help you.
This suits my purpose where in I can extract the integers and also add the delimiters if necessary. Works with different formatted strings as well.
(I dont have boost lib, hence preferring this method. )
int main()
{
string str="2,3,4;0,1,3-4,289,24,21,45;2";
//string str=";2;0,1,3-4,289,24;21,45;2"; //input2
std::stringstream ss(str);
int x=0;
if( str.length() != 0 )
{
while( !ss.eof() )
{
if( ss.peek()!= ',' && ss.peek()!=';' && ss.peek()!='-') /*Delimiters*/
{
ss>>x;
cout<<"val="<<x<<endl;
/* TODO:store integers do processing */
}
ss.get();
}
}
}
You can also try:
vector<int> SplitNumbersFromString(const string& input, const vector<char>& delimiters)
{
string buff{""};
vector<int> output;
for (auto n : input)
{
if (none_of(delimiters.begin(), delimiters.end(), [n](const char& c){ return c == n; }))
{
buff += n;
}
else
{
if (buff != "")
{
output.push_back(stoi(buff));
buff = "";
}
}
}
if (buff != "") output.push_back(stoi(buff));
return output;
}
vector<char> delimiters = { ',', '-', ';' };
vector<int> numbers = SplitNumbersFromString("91,43,3,23,0;6,9,0-4,29,24", delimiters);

trying to implement a recursive version of detecting a palindrome within a string using C++. having some trouble here

Having trouble trying to implement a recursive version for detecting a palindrome. Cannot get correct output :(
#include <iostream>
#include <string>
using namespace std;
bool testPalindrome( string, unsigned int length, int begin );
int main()
{
string test;
cout << "Enter what you wish to test for a palindrome: ";
cin >> test;
unsigned int len = test.length(); // acquire length of string
int beginning = 0; // set variable to point to beginning of string
if ( testPalindrome( test, len, beginning ) )
cout << "PALINDROME!" << endl;
else
cout << "NOTHING" << endl;
}
The code above is the main function I am using to test the palindrome function I am implementing. Below, I have also provided the code I wrote to detect a palindrome.
// implemented recursive function to check for a palindrome
bool testPalindrome( string name, unsigned int len, int begin )
{
// conditional to determine if beginning char position is equal to last char
if ( begin >= len )
return true; // if so, return true
// check if characters are equal, if not return false
else if ( name[ begin ] != name[ len ] )
return false;
// general case, call function with positions of characters being tested
// shifted by one slot each, respectively
else
return testPalindrome( name, ( len - 1 ), ( begin + 1 ) );
}
You haven't done a good job explaining what exactly your problem is, but I suspect your issue is that you're indexing len into name instead of len - 1. Indexing into strings is zero-indexed and not one-indexed so the index len is invalid.
#include <iostream>
#include <string>
using namespace std;
bool isPalindrome(string S, int len, int index) {
while (index <= len / 2) {
return (S[index] == S[len - index - 1]) && isPalindrome(S, len, index + 1);
}
return true;
}
int main() {
string S = "racecar";
if (isPalindrome(S, S.size(), 0)) {
cout << "Is a Palindrome\n";
} else {
cout << "Not a Palindrome\n";
}
return 0;
}
This should do it!
In the code that you wrote, while calling the function, use :
testPalindrome(name, len - 1, 0);
instead of:
testPalindrome(name, len, 0);
And since you are passing len - 1 as the argument, change type of len from unsigned int to int to prevent errors with NULL strings.
And add a condition checker :
if (len == -1) {
return true;
}
NULL strings are palindromes. without this condition your code will through SEGMENTATION FAULT for NULL strings.
Change the center line in testPalindrome() to
else if ( name[ begin ] != name[ len - 1 ] )
because name[len] reads a character behind the end of your string.

Complex algorithm to extract numbers/number range from a string

I am working on a algorithm where I am trying the following output:
Given values/Inputs:
char *Var = "1-5,10,12,15-16,25-35,67,69,99-105";
int size = 29;
Here "1-5" depicts a range value, i.e. it will be understood as "1,2,3,4,5" while the values with just "," are individual values.
I was writing an algorithm where end output should be such that it will give complete range of output as:
int list[]=1,2,3,4,5,10,12,15,16,25,26,27,28,29,30,31,32,33,34,35,67,69,99,100,101,102,103,104,105;
If anyone is familiar with this issue then the help would be really appreciated.
Thanks in advance!
My initial code approach was as:
if(NULL != strchr((char *)grp_range, '-'))
{
int_u8 delims[] = "-";
result = (int_u8 *)strtok((char *)grp_range, (char *)delims);
if(NULL != result)
{
start_index = strtol((char*)result, (char **)&end_ptr, 10);
result = (int_u8 *)strtok(NULL, (char *)delims);
}
while(NULL != result)
{
end_index = strtol((char*)result, (char**)&end_ptr, 10);
result = (int_u8 *)strtok(NULL, (char *)delims);
}
while(start_index <= end_index)
{
grp_list[i++] = start_index;
start_index++;
}
}
else if(NULL != strchr((char *)grp_range, ','))
{
int_u8 delims[] = ",";
result = (unison_u8 *)strtok((char *)grp_range, (char *)delims);
while(result != NULL)
{
grp_list[i++] = strtol((char*)result, (char**)&end_ptr, 10);
result = (int_u8 *)strtok(NULL, (char *)delims);
}
}
But it only works if I have either "0-5" or "0,10,15". I am looking forward to make it more versatile.
Here is a C++ solution for you to study.
#include <vector>
#include <string>
#include <sstream>
#include <iostream>
using namespace std;
int ConvertString2Int(const string& str)
{
stringstream ss(str);
int x;
if (! (ss >> x))
{
cerr << "Error converting " << str << " to integer" << endl;
abort();
}
return x;
}
vector<string> SplitStringToArray(const string& str, char splitter)
{
vector<string> tokens;
stringstream ss(str);
string temp;
while (getline(ss, temp, splitter)) // split into new "lines" based on character
{
tokens.push_back(temp);
}
return tokens;
}
vector<int> ParseData(const string& data)
{
vector<string> tokens = SplitStringToArray(data, ',');
vector<int> result;
for (vector<string>::const_iterator it = tokens.begin(), end_it = tokens.end(); it != end_it; ++it)
{
const string& token = *it;
vector<string> range = SplitStringToArray(token, '-');
if (range.size() == 1)
{
result.push_back(ConvertString2Int(range[0]));
}
else if (range.size() == 2)
{
int start = ConvertString2Int(range[0]);
int stop = ConvertString2Int(range[1]);
for (int i = start; i <= stop; i++)
{
result.push_back(i);
}
}
else
{
cerr << "Error parsing token " << token << endl;
abort();
}
}
return result;
}
int main()
{
vector<int> result = ParseData("1-5,10,12,15-16,25-35,67,69,99-105");
for (vector<int>::const_iterator it = result.begin(), end_it = result.end(); it != end_it; ++it)
{
cout << *it << " ";
}
cout << endl;
}
Live example
http://ideone.com/2W99Tt
This is my boost approach :
This won't give you array of ints, instead a vector of ints
Algorithm used: (nothing new)
Split string using ,
Split the individual string using -
Make a range low and high
Push it into vector with help of this range
Code:-
#include<iostream>
#include<vector>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
int main(){
std::string line("1-5,10,12,15-16,25-35,67,69,99-105");
std::vector<std::string> strs,r;
std::vector<int> v;
int low,high,i;
boost::split(strs,line,boost::is_any_of(","));
for (auto it:strs)
{
boost::split(r,it,boost::is_any_of("-"));
auto x = r.begin();
low = high =boost::lexical_cast<int>(r[0]);
x++;
if(x!=r.end())
high = boost::lexical_cast<int>(r[1]);
for(i=low;i<=high;++i)
v.push_back(i);
}
for(auto x:v)
std::cout<<x<<" ";
return 0;
}
You're issue seems to be misunderstanding how strtok works. Have a look at this.
#include <string.h>
#include <stdio.h>
int main()
{
int i, j;
char delims[] = " ,";
char str[] = "1-5,6,7";
char *tok;
char tmp[256];
int rstart, rend;
tok = strtok(str, delims);
while(tok != NULL) {
for(i = 0; i < strlen(tok); ++i) {
//// range
if(i != 0 && tok[i] == '-') {
strncpy(tmp, tok, i);
rstart = atoi(tmp);
strcpy(tmp, tok + i + 1);
rend = atoi(tmp);
for(j = rstart; j <= rend; ++j)
printf("%d\n", j);
i = strlen(tok) + 1;
}
else if(strchr(tok, '-') == NULL)
printf("%s\n", tok);
}
tok = strtok(NULL, delims);
}
return 0;
}
Don't search. Just go through the text one character at a time. As long as you're seeing digits, accumulate them into a value. If the digits are followed by a - then you're looking at a range, and need to parse the next set of digits to get the upper bound of the range and put all the values into your list. If the value is not followed by a - then you've got a single value; put it into your list.
Stop and think about it: what you actually have is a comma
separated list of ranges, where a range can be either a single
number, or a pair of numbers separated by a '-'. So you
probably want to loop over the ranges, using recursive descent
for the parsing. (This sort of thing is best handled by an
istream, so that's what I'll use.)
std::vector<int> results;
std::istringstream parser( std::string( var ) );
processRange( results, parser );
while ( isSeparator( parser, ',' ) ) {
processRange( results, parser );
}
with:
bool
isSeparator( std::istream& source, char separ )
{
char next;
source >> next;
if ( source && next != separ ) {
source.putback( next );
}
return source && next == separ;
}
and
void
processRange( std::vector<int>& results, std::istream& source )
{
int first = 0;
source >> first;
int last = first;
if ( isSeparator( source, '-' ) ) {
source >> last;
}
if ( last < first ) {
source.setstate( std::ios_base::failbit );
}
if ( source ) {
while ( first != last ) {
results.push_back( first );
++ first;
}
results.push_back( first );
}
}
The isSeparator function will, in fact, probably be useful in
other projects in the future, and should be kept in your
toolbox.
First divide whole string into numbers and ranges (using strtok() with "," delimiter), save strings in array, then, search through array looking for "-", if it present than use sscanf() with "%d-%d" format, else use sscanf with single "%d" format.
Function usage is easily googling.
One approach:
You need a parser that identifies 3 kinds of tokens: ',', '-', and numbers. That raises the level of abstraction so that you are operating at a level above characters.
Then you can parse your token stream to create a list of ranges and constants.
Then you can parse that list to convert the ranges into constants.
Some code that does part of the job:
#include <stdio.h>
// Prints a comma after the last digit. You will need to fix that up.
void print(int a, int b) {
for (int i = a; i <= b; ++i) {
printf("%d, ", i);
}
}
int main() {
enum { DASH, COMMA, NUMBER };
struct token {
int type;
int value;
};
// Sample input stream. Notice the sentinel comma at the end.
// 1-5,10,
struct token tokStream[] = {
{ NUMBER, 1 },
{ DASH, 0 },
{ NUMBER, 5 },
{ COMMA, 0 },
{ NUMBER, 10 },
{ COMMA, 0 } };
// This parser assumes well formed input. You have to add all the error
// checking yourself.
size_t i = 0;
while (i < sizeof(tokStream)/sizeof(struct token)) {
if (tokStream[i+1].type == COMMA) {
print(tokStream[i].value, tokStream[i].value);
i += 2; // skip to next number
}
else { // DASH
print(tokStream[i].value, tokStream[i+2].value);
i += 4; // skip to next number
}
}
return 0;
}