简体   繁体   中英

How to find values at .txt file in C++?

I need to extract complex values from the.txt file and assign it to 4 arrays(4 complex matrix 3*3) and after do +, -, *, /, * ( -0.8+1.6i ) operations between them. What do I need to use to find and get these complex values in the txt file?

it's a short version of the.txt file.

# file.txt 
# some text bla bla 123
# same
-2.3-14.6i,9.1
7.1+2.8i-7.1-11.7i
# bla bla 

Now what I did.

struct comp
    {
        double x, y;
    };

    comp arr[100];
    ifstream infile("C:\\Users\\Nick Leeker\\Desktop\\data.txt");

    if (!infile)    // Does file open correctly?
    {
        cout << "error -1\n\n";
        return -1;
    }
    else
    {
        cout << "file is open\n\n";
        return 1;
    }

First, the C++ library provides a std::complex that you can use instead of using your comp struct. Using the standard complex type will provide a great deal more functionality than your struct . However, there is nothing wrong with using your struct comp for learning purposes in parsing the information you need from your file.

Whether you use the std::complex provided or your own struct comp , if you want to store all complex values so they are available you will want to create a std::vector to hold all the values rather than a plain old array of comp . (let the STL handle the memory management and storage -- it will be much less error prone than writing it on your own from scratch)

Don't use Magic-Numbers or Hardcode Filenames in your code. (that is why main() takes the arguments int main (int argc, char **argv) . Pass your filename as an argument to main() or take the filenames as input. (it's fine to provide a default filename if one isn't given) To validate you have an argument given for the filename and declaring your std::vector<comp> and opening and validating you file is open cand be done as follows:

...
struct comp {       /* your struct of complex type */
    double x, y;
};

int main (int argc, char **argv) {

    if (argc < 2) {     /* validate 1 argument given for filename */
        std::cerr << "error: filename required as argument"
                    "usage: " << argv[0] << " filename\n";
        return 0;
    }
    std::vector<comp> cmplx;            /* vector of comp */
    std::string arr;                    /* string to read each line */
    std::ifstream infile (argv[1]);     /* open file given as 1st argument */

    if (!infile.good()) {   /* validate file open */
        std::cerr << "error: file open failed '" << argv[1] << "'.\n";
        return 1;   /* don't return negative values to the shell */
    }
    ...

To read the complex values from your file, you will want to read a line of data at a time into arr . You then want to create a std::stringstream from arr that will allow you to read from the stringstream separating the strings containing complex values by using a ',' delimiter. (you can't use the ',' delimiter for the read from the file, getline would not know where the line ends and skip right by ignoring the '\n' at the end of each line looking for the next ',' )

So you simply put the line in the stringstream and read from the stringstream (there is only one line worth of data in the stringstream, so there is no chance of reading beyond the end). For example:

    ...
    while (getline (infile, arr)) {     /* read each line */
        if (arr.at(0) == '#')           /* if comment line, get next */
            continue;
        std::string s;                  /* string to read from ss */
        std::stringstream ss(arr);      /* create stringstream from arr */
        ...

Just as you are looping reading lines with getline from the file with the loop above, you now just use a second loop in like manner to read the strings separated by ',' from the stringstream. Within the loop you will want to get the length of the current string, keep a variable of the offset from the beginning of the string for use with substr to tell stod where to start reading for your next value, and you will want to declare a pos (position) value to be filled by std::stod telling you how many characters were used in the conversion of current part of the line to a double so you can add that to your offset to know where to start reading the next. See std::stof, std::stod, std::stold .

You will also want to declare a temporary struct comp instance to fill with the values converted to double that you can then add to your vector if the parse to both x, y values are successful:

        ...
        while (getline (ss, s, ',')) {  /* read with ',' delimiter */
            size_t  len = s.length(),   /* length to check if done reading */
                    off = 0,            /* offset from beginning of s */
                    pos = 0;            /* chars reported used by stod */
            comp tmp;                   /* temporary struct to fill */
            ...

When converting with std::stod you must use a try {...} catch (exception) {...} to catch any error during conversion so you can handle the error properly. After you read the real-part ( x ), you will update the offset in s with the number of characters used in the conversion (reported by pos ) to know where to start your conversion for the imaginary part (if there is no imaginary part, just set the value to zero), eg

            ...
            try {   /* you must validate conversion with exceptions */
                tmp.x = stod (s.substr(off), &pos);
            }
            catch (const std::exception & e) {  /* error in 1st conversion */
                std::cerr << "error invalid tmp.x: "<< e.what() << '\n';
                continue;   /* get next potential complex no. */
            }
            off += pos;         /* real part obtained, update offset with pos */
            if (len > off) {    /* check chars remain to parse imaginary */
                try {   /* validate with exception */
                    tmp.y = stod (s.substr(off), &pos);
                }
                catch (const std::exception & e) {  /* warn on failed parse */
                    std::cerr << "no value for tmp.y: "<< e.what() << '\n';
                }
            }
            else        /* if no chars for imaginary part, set to zero */
                tmp.y = 0;
            ...

( note: since you clarified that there will be a ',' between the imaginary numbers, you can simply use s instead of s.substr(off) in the call to convert the real-part to double. The use of substr was needed if the ',' was optional to allow use of the delimiter of 'i' and then check if the next character was ',' which would require the use of off == 1 on the first call)

All that remains is adding the temporary struct comp you filled to your vector, eg

            ...
            cmplx.push_back(tmp);   /* add temporary comp to vector */
        }
    }

That is basically it. You have your std::vector<comp> cmplx filled will all the complex values read from the file. You can simply output them to verify the results, eg

    ...
    std::cout << "results\n\n";     /* output results */
    for (auto& c : cmplx)
        std::cout << "(" << c.x << ", " << c.y << "i)\n";
}

Putting it altogether you would have:

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

struct comp {       /* your struct of complex type */
    double x, y;
};

int main (int argc, char **argv) {

    if (argc < 2) {     /* validate 1 argument given for filename */
        std::cerr << "error: filename required as argument"
                    "usage: " << argv[0] << " filename\n";
        return 0;
    }
    std::vector<comp> cmplx;            /* vector of comp */
    std::string arr;                    /* string to read each line */
    std::ifstream infile (argv[1]);     /* open file given as 1st argument */

    if (!infile.good()) {   /* validate file open */
        std::cerr << "error: file open failed '" << argv[1] << "'.\n";
        return 1;   /* don't return negative values to the shell */
    }

    while (getline (infile, arr)) {     /* read each line */
        if (arr.at(0) == '#')           /* if comment line, get next */
            continue;
        std::string s;                  /* string to read from ss */
        std::stringstream ss(arr);      /* create stringstream from arr */
        while (getline (ss, s, ',')) {  /* read with ',' delimiter */
            size_t  len = s.length(),   /* length to check if done reading */
                    off = 0,            /* offset from beginning of s */
                    pos = 0;            /* chars reported used by stod */
            comp tmp;                   /* temporary struct to fill */
            try {   /* you must validate conversion with exceptions */
                tmp.x = stod (s.substr(off), &pos);
            }
            catch (const std::exception & e) {  /* error in 1st conversion */
                std::cerr << "error invalid tmp.x: "<< e.what() << '\n';
                continue;   /* get next potential complex no. */
            }
            off += pos;         /* real part obtained, update offset with pos */
            if (len > off) {    /* check chars remain to parse imaginary */
                try {   /* validate with exception */
                    tmp.y = stod (s.substr(off), &pos);
                }
                catch (const std::exception & e) {  /* warn on failed parse */
                    std::cerr << "no value for tmp.y: "<< e.what() << '\n';
                }
            }
            else        /* if no chars for imaginary part, set to zero */
                tmp.y = 0;

            cmplx.push_back(tmp);   /* add temporary comp to vector */
        }
    }

    std::cout << "results\n\n";     /* output results */
    for (auto& c : cmplx)
        std::cout << "(" << c.x << ", " << c.y << "i)\n";
}

Example Use/Output

With your data in the file dat/cmplx.txt , running the program and parsing values from the file would result in:

$ ./bin/parse_complex dat/cmplx.txt
results

(-2.3, -14.6i)
(9.1, 0i)
(7.1, 2.8i)
(-7.1, -11.7i)

Look things over and let me know if you have further questions.

Edit - Actual Input File Differs In Format

After looking at the input file on past.openSUSE.org, the only change I saw that needed adjustments was the change from checking the start of the comment from the first column to anywhere in the line. I've updated the test to: if (arr.find("#"):= std::string::npos)

The complete code would be:

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

struct comp {       /* your struct of complex type */
    double x, y;
};

int main (int argc, char **argv) {

    if (argc < 2) {     /* validate 1 argument given for filename */
        std::cerr << "error: filename required as argument"
                    "usage: " << argv[0] << " filename\n";
        return 0;
    }
    std::vector<comp> cmplx;            /* vector of comp */
    std::string arr;                    /* string to read each line */
    std::ifstream infile (argv[1]);     /* open file given as 1st argument */

    if (!infile.good()) {   /* validate file open */
        std::cerr << "error: file open failed '" << argv[1] << "'.\n";
        return 1;   /* don't return negative values to the shell */
    }

    while (getline (infile, arr)) {     /* read each line */
            if (arr.find("#") != std::string::npos) /* if comment line, get next */
            continue;
        std::string s;                  /* string to read from ss */
        std::stringstream ss(arr);      /* create stringstream from arr */
        while (getline (ss, s, ',')) {  /* read with ',' delimiter */
            size_t  len = s.length(),   /* length to check if done reading */
                    off = 0,            /* offset from beginning of s */
                    pos = 0;            /* chars reported used by stod */
            comp tmp;                   /* temporary struct to fill */
            try {   /* you must validate conversion with exceptions */
                tmp.x = stod (s.substr(off), &pos);
            }
            catch (const std::exception & e) {  /* error in 1st conversion */
                std::cerr << "error invalid tmp.x: "<< e.what() << '\n';
                continue;   /* get next potential complex no. */
            }
            off += pos;         /* real part obtained, update offset with pos */
            if (len > off) {    /* check chars remain to parse imaginary */
                try {   /* validate with exception */
                    tmp.y = stod (s.substr(off), &pos);
                }
                catch (const std::exception & e) {  /* warn on failed parse */
                    std::cerr << "no value for tmp.y: "<< e.what() << '\n';
                }
            }
            else        /* if no chars for imaginary part, set to zero */
                tmp.y = 0;

            cmplx.push_back(tmp);   /* add temporary comp to vector */
        }
    }

    std::cout << "results\n\n";     /* output results */
    for (auto& c : cmplx)
        std::cout << "(" << c.x << ", " << c.y << "i)\n";
}

Example Use/Output

Copying and pasting the file from paste.opensuse.org into the file dat/complexfile.txt , I get the following output:

$ ./bin/parse_complex2 dat/complexfile.txt
results

(-2.3, -14.6i)
(9.1, -2.5i)
(7.1, 2.8i)
(14.5, 12.2i)
(2.6, -13.2i)
(11.7, 0.7i)
(14, 1.6i)
(4.1, -10i)
(-7.3, 3.9i)
(9.9, 4.9i)
(5.6, -1.2i)
(15, 7.4i)
(-11.7, -4.2i)
(-8.2, 5.8i)
(9, -12i)
(13.8, 3.7i)
(-10.1, -8.9i)
(13.9, 6.4i)
(-3.2, -11.2i)
(-10.8, 13.9i)
(0.3, 1i)
(-1.1, -13.9i)
(-0.2, 0.1i)
(-11.8, 0.6i)
(-7, -6.9i)
(-14, 2.5i)
(-12.6, 10.7i)
(-0.3, 0.5i)

If you are still having problems, we will need to compare contents of the file at the byte level. A hexdump of the input file as copied is hexdump -Cv file

Example Building With VS, Running On Windows

Just to satisfy myself, I went to a windows box, opened the VS Developers Command Prompt (latest version of VS17) and built the program, eg

C:\Users\david\Documents\dev\src-cpp>cl /W3 /wd4996 /Ox /EHsc /Foobj /Febin/parse_complex2 /Tp parse_complex2.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.16.27034 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

parse_complex2.cpp
Microsoft (R) Incremental Linker Version 14.16.27034.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:bin/parse_complex2.exe
obj.obj

I then went to matrix and clicked Download and saved the file under the default filename in the dat directory below where I built the executable, eg

C:\Users\david\Documents\dev\src-cpp>dir dat
 Volume in drive C is Windows
 Volume Serial Number is ECC4-9A8B

 Directory of C:\Users\david\Documents\dev\src-cpp\dat

11/12/2019  05:06 PM    <DIR>          .
11/12/2019  05:06 PM    <DIR>          ..
11/12/2019  05:06 PM             4,916 40665451.txt
               1 File(s)          4,916 bytes
               2 Dir(s)  1,787,090,620,416 bytes free

Then I ran the program (I always put executables in the bin directory below the current):

C:\Users\david\Documents\dev\src-cpp>bin\parse_complex2.exe dat\40665451.txt
results

(-2.3, -14.6i)
(9.1, -2.5i)
(7.1, 2.8i)
(14.5, 12.2i)
(2.6, -13.2i)
(11.7, 0.7i)
(14, 1.6i)
(4.1, -10i)
(-7.3, 3.9i)
(9.9, 4.9i)
(5.6, -1.2i)
(15, 7.4i)
(-11.7, -4.2i)
(-8.2, 5.8i)
(9, -12i)
(13.8, 3.7i)
(-10.1, -8.9i)
(13.9, 6.4i)
(-3.2, -11.2i)
(-10.8, 13.9i)
(0.3, 1i)
(-1.1, -13.9i)
(-0.2, 0.1i)
(-11.8, 0.6i)
(-7, -6.9i)
(-14, 2.5i)
(-12.6, 10.7i)
(-0.3, 0.5i)

All fine no problems. So I am really perplexed as to why you are having issues reading the file and getting the correct results. Try the above, step-by-step and let me know if you are still having problems. We will get it solved....

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM