views:

2178

answers:

10

I would like to read an input file in C++, for which the structure (or lack of) would be something like a series of lines with text = number, such as

input1 = 10
input2 = 4
set1 = 1.2
set2 = 1.e3

I want to get the number out of the line, and throw the rest away. Numbers can be either integers or doubles, but I know when they are one or other.

I also would like to read it such as

input1 =    10
input2=4
set1   =1.2
set2= 1.e3

so as to be more robust to the user. I think this means that it shouldn't be red in a formatted fashion.

Anyway, is there a smart way to do that?

I have already tried the following, but with minimal knowledge of what I've been doing, so the result was as expected... no success.

    #include <stdio.h>
    #include <stdlib.h>
    #include <float.h>
    #include <math.h>
    #include <iostream>
    #include <fstream>
    #include <iomanip>
    #include <cstdlib>
    #include <boost/lexical_cast.hpp>
    #include <string>

    using namespace std;
    using namespace boost;

    int main(){

            string tmp;
            char temp[100];

            int i,j,k;

            ifstream InFile("input.dat");

            //strtol
            InFile.getline(temp,100);
            k=strtol(temp,0,10);
            cout << k << endl;

            //lexical_cast
            InFile.getline(temp,100);
            j = lexical_cast<int>(temp);
            cout << j << endl;

            //Direct read
            InFile >> tmp >> i;
            cout << i << endl;

            return 0;
    }
+2  A: 

C FTW (modified to handle doubles)

#include <stdio.h>

int
main ()
{
    double num;

    while (!feof (stdin))
         if (1 == fscanf (stdin, "%*[^=] = %lf", &num))
            printf ("%g\n", num);

    return 0;
}
Bklyn
`num` needs to be a float or double and you need %f or %lf, if you want to parse his input file.
AShelly
use %g for format
Arkadiy
+2  A: 

Do I get it right: You take the line, extract the number from it and print it out (or use it somewhere else, whatever)? If so, you can use the string functions and istringstream:

//these are your codes, removing some though
#include <stdio.h>
#include <stdlib.h>
#include <float.h>
#include <math.h>
#include <iostream>
#include <fstream>
#include <string>
//adding
#include <sstream>

using namespace std;

int main()
{
string line="", extract="";
int placeofop=0, input;
ifstream InFile("input.dat");

while(!InFile.eof())
{
     getline(InFile, line);
     placeofop = line.find("=");
     extract = line.substr(placeofop, (line.length()-placeofop));
     istringstream trythis(extract);
     trythis >> input;
     cout << input << endl;
}

return 0;
}

I'm not 100% sure if I remember the functions right but this should work if I got all of them right.

edit: I realized I could edit my posts :) I reversed the <<'s and >>'s as in the comment; something I keep doing all the time... Gives no compiling errors now.

Dunya Degirmenci
Needs error checking. :-) +1 for answering 1 min before me.
Frank Krueger
Oh.. dammit, I always do that. Reverse the <<'s and >>'s please! :)
Dunya Degirmenci
+2  A: 

Off the top of my head:

vector<double> vals(istream &in) {
    vector<double> r;
    string line;

    while (getline(f, line)) {
        const size_t eq = line.find('=');
        if (eq != string::npos) {
            istringstream ss(line.substr(eq + 1));
            double d = 0;
            ss >> d;
            if (ss) r.push_back(d);
            else throw "Line contains no value";
        }
        else {
            throw "Line contains no =";
        }
    }

    return r;
}

int main(int argc, char *argv[]) {
    vector<double> vs = vals(ifstream(argv[1]));
}
Frank Krueger
A: 

Here's my quickest STL solution:

#include <fstream>
#include <list>
#include <locale>
void foo()
{
std::fstream f("c:\\temp\\foo.txt", std::ios_base::in);
std::list<double> numbers;
while (!f.eof())
{ 
 int c = f.get();
 if (std::isdigit(c, std::locale::classic()) ||
  c == '+' ||
  c == '-' ||
  c == '.')
 {
  f.putback(c);
  double val;
  f >> val;
  if (f.fail()) {
   f.clear(f.eof() ? std::ios_base::eofbit : std::ios_base::goodbit);
   continue;
  }
  else
  {
   numbers.push_back(val);
  }   
 }
}
}
David Gladfelter
Seems like an awful lot of work for something so simple...
Bklyn
A: 

Just tested this... it works, and doesn't require anything outside of the C++ standard library.

#include <iostream>
#include <map>
#include <string>
#include <algorithm>
#include <iterator>
#include <cctype>
#include <sstream>

using namespace std; // just because this is an example...

static void print(const pair<string, double> &p)
{
    cout << p.first << " = " << p.second << "\n";
}

static double to_double(const string &s)
{
    double value = 0;
    istringstream is(s);
    is >> value;
    return value;
}

static string trim(const string &s)
{
    size_t b = 0;
    size_t e = s.size();
    while (b < e && isspace(s[b])) ++b;
    while (e > b && isspace(s[e-1])) --e;
    return s.substr(b, e - b);
}

static void readINI(istream &is, map<string, double> &values)
{
    string key;
    string value;

    while (getline(is, key, '='))
    {
        getline(is, value, '\n');
        values.insert(make_pair(trim(key), to_double(value)));
    }
}

int main()
{
    map<string, double> values;
    readINI(cin, values);
    for_each(values.begin(), values.end(), print);
    return 0;
}

EDIT: I just read the original question and noticed I'm not producing an exact answer. If you don't care about the key names, juts discard them. Also, why do you need to identify the difference between integer values and floating-point values? Is 1000 an integer or a float? What about 1e3 or 1000.0? It's easy enough to check if a given floating-point value is integral, but there is a clas of numbers that are both valid integers and valid floating-point values, and you need to get into your own parsing routines if you want to deal with that correctly.

Tom
Integers I'll be using for switches, and doubles as physical properties, so I'll always have doubles like 1.3e3 or 0.2e-5 or whatever. The sequence of integers and floats will be known to me, so I won't worry with it...
Biga
A: 

Consider Perl? (/ducks)

Arkadiy
+1 When your hammer is C++, everything looks like a thumb.
Chris Lutz
+5  A: 

Simply read one line at a time.
Then split each line on the '=' sign. Use the stream functionality do the rest.

#include <sstream>
#include <fstream>
#include <iostream>
#include <string>

int main()
{
    std::ifstream    data("input.dat");
    std::string      line;

    while(std::getline(data,line))
    {
        std::stringstream    str(line);
        std::string          text;

        std::getline(str,text,'=');

        double   value;
        str >> value;
    }
}

With error checking:

#include <sstream>
#include <fstream>
#include <iostream>
#include <string>

int main()
{
    std::ifstream    data("input.dat");
    std::string      line;

    while(std::getline(data,line))
    {
        std::stringstream    str(line);
        std::string          text;
        double               value;

        if ((std::getline(str,text,'=')) &&  (str >> value))
        {
            // Happy Days..
            // Do processing.
            continue; // To start next iteration of loop.
        }
        // If we get here. An error occurred.
        // By doing nothing the line will be ignored.
        // Maybe just log an error.
    }
}
Martin York
That's pretty clean. Thanks!
Biga
I guess I'm the only one who still bothers with error checking these days. @Biga, whenever accepting user input you need to have all your guards up. Check that `str` above is still valid to know whether the value was actually read.
Frank Krueger
Its not as if adding error checking to the above code is difficult! But it looks a lot nicer than your :-)
Martin York
Agreed. :-) It's not hard, but @Biga already showed that he doesn't understand the STL libraries that well. We're doing no service to him by omitting error detection code.
Frank Krueger
Curious also that you chose to use `if continue` instead of `if else`. When teaching C++, I try to get the students to limit their use of `continue` - I find that indent-based branching is easier to comprehend than tracing `breaks` and `continues` by hand.
Frank Krueger
A: 

now that you are already using boost with lexical_cast, just parse each line with boost::split() and boost::is_any_of() into 1 2-element vector, with token_compress turned on.

the following code illustrates the parse, but skips the numeric conversion, which could be solved easily with boost lexical_cast.

#include <fstream>
#include <sstream>
#include <string>
#include <iostream>
#include <vector>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/foreach.hpp>  

using std::string;
using std::cout;
using std::ifstream;
using std::stringstream;  
using std::vector; 

std::string file_to_string()
{
    ifstream    data("data.txt");
    stringstream s;
    s << data.rdbuf();
    return s.str();
}

void print_parameter(vector<string>& v)
{
    cout << v_para[0];
    cout << "=";
    cout << v_para[1];
    cout << std::endl;   
}

vector<string> string_to_lines(const string& s)
{
    return  v_lines;
}


int main()
{

    vector<string> v_lines;
    boost::split(v_lines, file_to_string(), boost::is_any_of("\n"), boost::token_compress_on);

    vector<string> v_para;
    BOOST_FOREACH(string& line, v_lines)
    {
        if(line.empty()) continue;

        boost::split(v_para, line, boost::is_any_of(" ="), boost::token_compress_on);

        // test it
        print_parameter(v_para);
    }
}
t.g.
+1  A: 

If you are devising this format, I would suggest adopting the INI file format. The lightweight syntaxed INI format includes sections (allows you to have a little more structure in the format) which may or may not be desirable in your case:

I.e.

[section_1] 
variable_1=value1
variable_2=999 
[sectionA]
variable_A=value A 
variable_B=111

The external links on this wikipedia page list a number of libraries that can be used for working with these types of files that extend/replace the basic GetPrivateProfileString functions from the Windows API and support other platforms. Most of these would handle the space padded = sign (or at least before the = since a space after the = may be intentional/significant. Some of these libraries might also have an option to omit [sections] if you don't want that (my own C++ class for handling INI like format files has this option).

The advantage to these libraries and/or using the Windows API GetPrivateProfileXXX functions is that your program can access specific variables (I.e. get or set the value for variable_A from sectionA) without your program having to write/scan/rewrite the entire file.

Roger Nelson
+3  A: 

There are already some fine solutions here. However, just to throw it out there, some comments implied that Boost Spirit is an inappropriate solution for this problem. I'm not sure I completely disagree. However, the following solution is very terse, readable (if you know EBNF) and error-tolerant. I'd consider using it.

#include <fstream>
#include <string>
#include <boost/spirit.hpp>

using namespace std;
using namespace boost::spirit;

int main()
{
    ifstream       data("input.dat");
    string         line;
    vector<double> numbers;

    while(getline(data,line))
    {
        parse(line.c_str(), 
            *(+~ch_p('=') >> ch_p('=') >> real_p[push_back_a(numbers)]), 
            space_p);
    }
}
MattyT