1

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.

Tomáš Zato
  • 50,171
  • 52
  • 268
  • 778

4 Answers4

2

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;
}
Captain Obvlious
  • 19,754
  • 5
  • 44
  • 74
  • The `istringstream` solution is the most perfect. I'd only add temporary variable that restores `min` to it's original state if invalid `"666,"` is passed (because it's not so clear what was intended to be entered). Otherwise perfect, thank you. – Tomáš Zato Dec 08 '14 at 01:36
1

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"; 
     }
Christophe
  • 68,716
  • 7
  • 72
  • 138
  • Will it put anything in the integers if it fails? I have actually some default values in them so it's best if it fails and does not change the value. – Tomáš Zato Dec 08 '14 at 01:12
  • It won't overwrite the integer that fails. However if the first integer is valid it will be stored, even if the second is invalid. – Christophe Dec 08 '14 at 09:08
1

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.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
1

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

kuroi neko
  • 8,479
  • 1
  • 19
  • 43
  • 1
    I suppose this is the fastest solution. Thanks for showing that C is not dead yet :) – Tomáš Zato Dec 08 '14 at 01:37
  • 1
    if no valid format is recognized, errno will be set to EINVAL and strtol will return 0. You'll have to do the check to preserve your default values. As for C being alive or dead, well, blame Mr Stourstrup and his friends for being unable to provide a decent string handling package in the last 25 years or so... – kuroi neko Dec 08 '14 at 01:43