Parsing only numbers from istream in C++
Asked Answered
C

6

6

I have a bunch of input files that look like the following:

(8,7,15)
(0,0,1) (0,3,2) (0,6,3)
(1,0,4) (1,1,5)

I need to write a function that parses these inputs one number at a time, so I need to be able to separate the input by numbers, e.g.: 8, then 7, then 15, then 0, another 0, so on.

The only way I've thought of so far is to use istream.get() which returns the next character's ASCII code, which I can convert back to its character format by casting it to char. Then I'd check if the character was a number or not (so the brackets are ignored) but this way, any double (or triple) digit numbers are only read one digit at a time.

What would be the best way to achieve this?

By the way, I must use istream. It's part of the specification that I'm not allowed to change

Thanks

Coquelicot answered 19/8, 2011 at 6:5 Comment(4)
Is there anything wrong with reading double or triple digit numbers one character at a time? All you have to do is multiply the number read so far by 10 and then add the value of the next digit. Put that in a loop and you're done.Decapod
Thanks john, that's basically the manual way of doing it, I was hoping there would exist somewhere in STL something that would help me to do this much nicer!Coquelicot
Well you could mess aruond with istream::unget which returns the last read character to the string. That way you could unget the first digit and then use >>. But frankly the manual way is the nice way.Decapod
Or, since your input seems quite regular you can read the puncutation into dummy variables. Something like in >> lparen >> num1 >> comma1 >> num2 >> comma2 >> num3 >> rparen; where lparen etc are declared as char. But such code is quite brittle, I would do it the manual way.Decapod
D
3

Here's some code, you can adapt to meet your precise needs

for (;;)
{
  int ch = in.get();
  if (ch == EOF)
    break;
  if (isdigit(ch))
  {
    int val = ch - '0';
    for (;;)
    {
      ch = in.get();
      if (!isdigit(ch))
        break;
      val *= 10;
      val += ch - '0';
    }
    // do something with val
  }
}

This is untested code.

Decapod answered 19/8, 2011 at 6:14 Comment(2)
Thanks John! This code looks a bit nicer than Nawaz' solution, I think I like this a bit more, but I'll have to test both version to make up my mindCoquelicot
Actually I think they're very similar. They both essentially ignore the punctuation. The decision you have to make is whether you are going to try to read and validate the punctuation. That's obviously more complex. I posted the above code because I thought you didn't know how to calculate an integer value by reading one character at a time. But I can see now I was wrong in that.Decapod
J
7

This is one solution:

struct integer_only: std::ctype<char> 
{
    integer_only(): std::ctype<char>(get_table()) {}

    static std::ctype_base::mask const* get_table()
    {
        static std::vector<std::ctype_base::mask> 
            rc(std::ctype<char>::table_size,std::ctype_base::space);

        std::fill(&rc['0'], &rc['9'+1], std::ctype_base::digit);
        return &rc[0];
    }
};

int main() {
        std::cin.imbue(std::locale(std::locale(), new integer_only()));
        std::istream_iterator<int> begin(std::cin);
        std::istream_iterator<int> end;
        std::vector<int> vints(begin, end);
        std::copy(vints.begin(), vints.end(), std::ostream_iterator<int>(std::cout, "\n"));
        return 0;
}

Input:

(8,7,15)
(0,0,1) (0,3,2) (0,6,3)
(1,0,4) (1,1,5)

Output:

8 7 15 0 0 1 0 3 2 0 6 3 1 0 4 1 1 5 

Online demo : http://ideone.com/Lwx9y

In the above, you've to replace std::cin with the file stream after opening the file successfully, as:

 std::ifstream file("file.txt");
 file.imbue(std::locale(std::locale(), new integer_only()));
 std::istream_iterator<int> begin(file);
 std::istream_iterator<int> end;
 std::vector<int> vints(begin, end); //container of integers!

Here, vints is a vector which contains all the integers. You would like work with vints to do something useful. Also, you can use it where int* is expected as:

void f(int *integers, size_t count) {}

f(&vints[0], vints.size()); //call a function which expects `int*`.

Similar trick can be applied when reading only words from a file. Here is an example:

Judiciary answered 19/8, 2011 at 6:15 Comment(9)
That code alters the stream so it considers all non-digits as whitespace! I'm not sure if I'm impressed or not. Seems a bit quick and dirty to me.Decapod
Thanks for this! will test it out soon. btw, the file is read as standard input, so no need to open the file :)Coquelicot
Actually I think I agree with john on this, I'm not sure if I like the idea of altering the stream, and having to declare a struct just to achieve this relatively simple task, seems a bit overkill compared to john's method!Coquelicot
@Arvin: What is wrong with providing your own locale to the stream? Why the function imbue() exists in the first place if not to allow programmers to change the locale? Also, I wonder if the problem was so simple task, then why did you ask it in the first place? :-/Judiciary
@Nawaz: A quick question. You can adapt the above code to handle signed numbers? Presumably you would add '+' and '-' to your table as ctype_base::punct. Or does the istream code automatically handle signedness?Decapod
Note: On some systems if you imbue a file-stream after it has been opened the imbue is ignored. Thus you should imbue the stream before opening it.Ticktack
@john: This solution of course has some limitation, that it will work only with non-negative integers.Judiciary
@Martin: Yes. I knew that but didn't mention in my answer to make the solution simple, and because I didn't see any system yet where it would fail.Judiciary
Hmm yes you're right, I suppose there's nothing wrong with it other than my being uncomfortable using locales since I'm not familiar with them. The reason I ask was because I'm a C++ newbie so I'm not yet able to handle simple tasks like these (It's simple for my purposes since I don't need to validate anything). I'll look into locales and imbue() soon to see if they're easy enough to understand!Coquelicot
D
3

Here's some code, you can adapt to meet your precise needs

for (;;)
{
  int ch = in.get();
  if (ch == EOF)
    break;
  if (isdigit(ch))
  {
    int val = ch - '0';
    for (;;)
    {
      ch = in.get();
      if (!isdigit(ch))
        break;
      val *= 10;
      val += ch - '0';
    }
    // do something with val
  }
}

This is untested code.

Decapod answered 19/8, 2011 at 6:14 Comment(2)
Thanks John! This code looks a bit nicer than Nawaz' solution, I think I like this a bit more, but I'll have to test both version to make up my mindCoquelicot
Actually I think they're very similar. They both essentially ignore the punctuation. The decision you have to make is whether you are going to try to read and validate the punctuation. That's obviously more complex. I posted the above code because I thought you didn't know how to calculate an integer value by reading one character at a time. But I can see now I was wrong in that.Decapod
E
2

try to read a number. if that fails, clear error state and try to read a char (and ignore it). repeat these two steps until reading a char fails, in which case you are at EOF or true failure.

it might be optimized by recognizing ')' and then reading until '('.

but i don't think it's worth it.

cheers & hth.,

Essonite answered 19/8, 2011 at 6:15 Comment(0)
K
2

Another solution:

#include <string>
#include <ostream>
#include <fstream>
#include <iostream>

struct triple
{
    long a;
    long b;
    long c;
};

std::ostream& operator << (std::ostream& os, const triple& value)
{
    return os << value.a << "/" << value.b << "/" << value.c;
}

int main()
{
    std::ifstream stream("Test.txt");
    if (!stream)
    {
        std::cout << "could not open the file" << std::endl;
    }

    std::string dummy;
    triple value;
    while (std::getline(stream, dummy, '(') >> value.a &&
           std::getline(stream, dummy, ',') >> value.b &&
           std::getline(stream, dummy, ',') >> value.c)
    {
        std::cout << value << std::endl;
    }
}
Kohl answered 19/8, 2011 at 7:0 Comment(0)
O
1
int getFirstPos(const string& str)

{
int pos=0,PosHit=0;
bool bfind=false;
if((PosHit=str.find(','))!=string::npos){
    if(!bfind)  pos=PosHit;
    pos=pos>PosHit?PosHit:pos;
    bfind=true;
}
if((PosHit=str.find('('))!=string::npos){
    if(!bfind)  pos=PosHit;
    pos=pos>PosHit?PosHit:pos;
    bfind=true;
}
if((PosHit=str.find(')'))!=string::npos){
    if(!bfind)  pos=PosHit;
    pos=pos>PosHit?PosHit:pos;
    bfind=true;
}
return bfind?pos:string::npos;

}

void main()

{
    ifstream ifile("C:\\iStream.txt");
    string strLine;
    vector<double> vecValue;    //store the datas
    while(getline(ifile,strLine)){
        if(strLine.size()==0)
            continue;
        int iPos=0;
        while((iPos=getFirstPos(strLine))!=string::npos)
            strLine[iPos]=' ';
        istringstream iStream(strLine);
        double dValue=0;
        while(iStream>>dValue)
            vecValue.push_back(dValue);
    }
    //output the result!
    vector<double>::iterator it;
    for (it=vecValue.begin(); it!=vecValue.end()  ; ++it){
        cout<<setprecision(3)<<*it<<endl;
    }
}
Osteoclasis answered 19/8, 2011 at 7:16 Comment(0)
A
0

Nowadays, in the year 2022, we have more powerful language elements available.

We can use if statements with initializer, std::ifstream which will open the file with its constructor and close it automatically, the std::istreambuf_iterator which makes it easy to read a complete file through the std::strings range constructor, the regex library and especially the std::sregex_token_iterator (see here) and powerful algorithms from the algorithm_library, r.g. std::transform.

With the above mentioned language elements, we can come up with a very short and powerful solution for your task.

This will make life simpler . . .

Please see:

#include <iostream>
#include <fstream>
#include <sstream>
#include <iterator>
#include <regex>
#include <algorithm>
#include <vector>

const std::regex re{ R"(\d+)" };

int main() {
    // Open file and check, if it could be opened
    if (std::ifstream ifs{ "r:\\numbers.txt" }; ifs) {

        // Read complete source file into string
        std::string text(std::istreambuf_iterator<char>(ifs), {});

        // Get numbers (digits only)
        std::vector<int> numbers{};
        std::transform(std::sregex_token_iterator(text.begin(), text.end(), re), {}, std::back_inserter(numbers), [](const std::string& s) {return stoi(s);});

        // Do something with those numbers
        // . . .

        // Debug output
        for (const int i : numbers) std::cout << i << ' ';
    }
    else std::cerr << "\n***Error: Could not open input file\n";
}
Albatross answered 5/12, 2022 at 13:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.