Tomáš Zato
Tomáš Zato

Reputation: 53119

Parse two or one number from std::string

I'm stuck at designing this function:

//Turns "[0-9]+,[0-9]+" into two integers. Turns "[0-9]+" in two *equal* integers
static void parseRange(const std::string, int&, int&);

I don't have access to regular expressions (which would require either C++11 or Boost library). I need to somehow find out if the string contains 2 integers and split it, then get each integer.

I guess I'd need strstr version that uses std::string to find out if there's a comma and where. I could, probably, operate with std::string::c_str value. Extensive searching led me to this (but I want to use std::string, not C string):

  void Generator::parseRange(const std::string str, int& min, int& max) {
      const char* cstr = str.c_str();
      const char* comma_pos;
      //There's a comma
      if((comma_pos=strstr(cstr, ","))!=NULL) { //(http://en.cppreference.com/w/cpp/string/byte/strstr)
          //The distance between begining of string and the comma???
          //Can I do this thing with pointers???
          //Is 1 unit of pointer really 1 character???
          unsigned int num_len = (comma_pos-cstr);
          //Create new C string and copy the first part to it (http://stackoverflow.com/q/8164000/607407)
          char* first_number=(char *)malloc((num_len+1)*sizeof(char));//+1 for \0 character
          //Make sure it ends with \0
          first_number[num_len] = 0;
          //Copy the other string to it
          memcpy(first_number, cstr, num_len*sizeof(char));
          //Use atoi
          min = atoi(first_number);
          max = atoi(comma_pos+1);
          //free memory - thanks @Christophe
          free(first_number);
      }
      //Else just convert string to int. Easy as long as there's no messed up input
      else {
          min = atoi(cstr); //(http://www.cplusplus.com/reference/cstdlib/atoi/)
          max = atoi(cstr);
      }
  }

I Googled a lot. You can't really say I didn't try. The function above works, but I'd prefer some less naive implementation, because what you see above is hardcore C code from the old times. And it all relies on fact that nobody messes up with input.

Upvotes: 1

Views: 1363

Answers (4)

kuroi neko
kuroi neko

Reputation: 8641

No need for std::whatever, it will only consume more memory for a no less unreadable code.

Try this circa 1980 C code, it should do the trick:

void generator::parse_range (const std::string input, int & min, int & max)
{
    const char * scan = input.c_str();
    min = (int) strtol (scan, &scan, 0);
    max = (*scan == ',') ? (int)strtol (scan+1, &scan, 0) : min;
    if (errno || *scan != '\0') panic ("you call that numbers?");
}

This will accept hex or octal inputs, though you can fix the base with the 3rd parameter.
You could also check errno after first conversion or test for long integer overflow, but I assume this is not the worst part of your problem :)

Upvotes: 1

Captain Obvlious
Captain Obvlious

Reputation: 20063

You can accomplish this by using the built in search facilities provided by std::string along with std::atoi without making copies or the need to use malloc or new to store parts of the string.

#include <cstdlib>
#include <string>

void Generator::parseRange(const std::string &str, int& min, int& max)
{
    //  Get the first integer
    min = std::atoi(&str[0]);

    // Check if there's a command and proces the second integer if there is one
    std::string::size_type comma_pos = str.find(',');
    if (comma_pos != std::string::npos)
    {
        max = std::atoi(&str[comma_pos + 1]);
    }
    //  No comma, min and max are the same
    else
    {
        max = min;
    }
}

Alternatively as others have pointed out you can use std::istringstream to handle the integer parsing. This will allow you to do additional input validation when parsing the integer values

#include <sstream>
#include <string>

bool Generator::parseRange(const std::string& str, int& min, int& max)
{
    std::istringstream sst(str);

    //  Read in the first integer
    if (!(sst >> min))
    {
        return false;
    }

    //  Check for comma. Could also check and error out if additional invalid input is
    //  in the stream
    if (sst.get() != ',')
    {
        max = min;
        return true;
    }

    //  Read in the second integer
    if (!(sst >> max))
    {
        return false;
    }

    return true;
}

Upvotes: 2

Jerry Coffin
Jerry Coffin

Reputation: 490048

You can do all the searching and separating pretty easily with std::string functionality.

int pos = str.find(',');

assert(pos != std::string::npos);

std::string first = str.substr(0, pos);
std::string second = str.substr(pos+1, -1);

Alternatively, you can pretty easily do the parsing with a stringstream. For example:

std::istringstream s(str);

int one, two;
char ch;

s >> one >> ch >> two;

assert(ch == ',');

Note that this also makes it easy to combine separating the strings and converting the individual pieces into numbers.

Upvotes: 1

Christophe
Christophe

Reputation: 73366

What with this more native version:

void Generator::parseRange(const std::string str, int& min, int& max) {
     stringstream sst(str); 
     if (!(sst>>min && sst.get()==',' && sst>>max)) 
         cerr<<"String has an invalid format\n"; 
     }

Upvotes: 1

Related Questions