3

I have a text file, which emulates the input to my script. Example of the file:

4 15 30
....
....
....
....
1
1 2
3
1 2
2 1
2 2

The first line contains some constants, let's say n, a, and b. Next n rows contain some map nXn. Then there is a line with m - number of new coordinates and then m rows of these coordinates. I need to read these lines consequently and output some results. I found that I can read the file using

string myText;
ifstream MyReadFile("01");
while (getline (MyReadFile, myText)) {
    cout << myText << "\n";
}
MyReadFile.close();

but I don't understand how to implement a more complex reading of the input, as I need.

pascalamin
  • 83
  • 1
  • 7
  • Reading the file line by line and parsing the lines to get the values is a reasonable approach. – drescherjm Dec 14 '21 at 16:26
  • If so, what's the best way to do it? For example, I initialized some variable `bool constReceived = false` which basically means whether I read the first line, and then I will need to check this condition every line I read, which, I feel, is very inefficient – pascalamin Dec 14 '21 at 16:28
  • If you showed code that attempted to solve the problem you may get help with your code. If the code you wrote works and you are worried about the design and efficiency you may want to ask at https://codereview.stackexchange.com/ instead. – drescherjm Dec 14 '21 at 16:57
  • According to your description, line 1 represents `n`, `a` and `b`, and lines 2-5 represent the "map", line 6 represents `m` and line 7 represents "new coordinates". But what do lines 8-11 represent? – Andreas Wenzel Dec 14 '21 at 17:47
  • @AndreasWenzel new set of coordinates – pascalamin Dec 14 '21 at 17:59
  • @pascalamin: According to your description, `m` in line 6 specifies the number of new coordinates, which is `1` in your case. Does that mean that after reading `m` lines, a new `m` is defined with a new set of coordinates? If that is the case, please add this information to the question, otherwise your sample input does not make sense. – Andreas Wenzel Dec 14 '21 at 18:12
  • @pascalamin: After `m` lines have been read, should the program continue by reading another `m` and then another `m` lines, until end-of-file is encountered? – Andreas Wenzel Dec 15 '21 at 00:36

3 Answers3

3

Based on your description, your input problem seems to consist of 4 stages:

  1. read one line which specifies n, a and b
  2. read n lines which represents the "map"
  3. read one line which specifies m
  4. read m lines which represents "new coordinates"

Although it is possible to solve the problem using only a single loop and using a variable to indicate in which stage the input currently is (which would have to be checked in every loop iteration), a simpler and more intuitive solution would be to simply write code for each individual stage one after another.

Since only stages 2 and 4 can consist of multiple lines, it makes sense to only use a loop for these two stages, and to only use a single call to std::getline for stages 1 and 3.

In your question, it seems unclear how the input in the file after stage 4 should be interpreted. My guess is that you want to want to jump back to stage 3 and continue processing input, until end-of-file is encountered.

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

void do_input()
{
    int n, a, b;
    int m;

    std::ifstream input_file( "input.txt" );
    std::string line;
    std::stringstream ss;


    //STAGE 1

    //attempt to read one line
    if ( !getline( input_file, line ) )
        throw std::runtime_error( "error reading from file" );

    //prepare stringstream object
    ss = std::stringstream( line );

    //attempt to read data
    ss >> n >> a >> b;
    if ( !ss )
        throw std::runtime_error( "error reading from file" );

    //display converted input
    std::cout << "STAGE 1: " <<
        "n = " << n << " " <<
        "a = " << a << " " <<
        "b = " << b << "\n" ;


    //STAGE 2

    // read n lines
    for ( int i = 0; i < n; i++ )
    {
        if ( !getline( input_file, line ) )
            throw std::runtime_error( "error reading from file" );

        //display input line
        std::cout << "STAGE 2: Found map line: " << line << "\n";
    }

    //continue reading input, until end-of-file is encountered
    while ( true )
    {

        //STAGE 3

        //attempt to read one line
        if ( !getline( input_file, line ) )
            break;

        //prepare stringstream object
        ss = std::stringstream( line );

        //attempt to read data
        ss >> m;
        if ( !ss )
            break;

        //display converted input
        std::cout << "STAGE 3: " <<
            "m = " << m << "\n";


        //STAGE 4

        // read m lines
        for ( int i = 0; i < m; i++ )
        {
            if ( !getline( input_file, line ) )
                throw std::runtime_error( "error reading from file" );

            //display input line
            std::cout << "STAGE 4: Found coordinates line: " << line << "\n";
        }
    }

    std::cout << "Could not read further input, stopping.\n";
}

int main()
{
    try
    {
        do_input();
    }
    catch ( std::runtime_error &err )
    {
        std::cout << "Runtime error occurred: " << err.what() << std::endl;
    }
}

With the sample input specified in the question, the program has the following output:

STAGE 1: n = 4 a = 15 b = 30
STAGE 2: Found map line: ....
STAGE 2: Found map line: ....
STAGE 2: Found map line: ....
STAGE 2: Found map line: ....
STAGE 3: m = 1
STAGE 4: Found coordinates line: 1 2
STAGE 3: m = 3
STAGE 4: Found coordinates line: 1 2
STAGE 4: Found coordinates line: 2 1
STAGE 4: Found coordinates line: 2 2
Could not read further input, stopping.
Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
  • Sorry to say. But this answer is wrong. It does not read the n x n array. And read reads only the first set of coordinates. Maybe you misunderstood the format. But you do not output the last 3 lines of the input. And, for the new user,please delete the exception handling. I am not sure who upvotes wrong answers . . . – A M Dec 14 '21 at 20:52
  • @ArminMontigny: OP did specify that they wanted to do anything specific with the "nxn map", so, it seemed appropriate to simply read in every line of the map as a string, and to print it with an appropriate label. This does not make my answer "wrong". – Andreas Wenzel Dec 14 '21 at 21:24
  • @ArminMontigny: OP only specified the meanings of lines 1-7 in the question. OP did not state the meaning of the lines after `m` lines have been read. Therefore, it is not quite clear what the lines 8-11 are supposed to represent. However, my guess is that after reading `m` lines, a new value `m` should be read and a further `m` lines should be read, and that this should be repeated until end-of-file is encountered. Therefore, I have modified my program accordingly. – Andreas Wenzel Dec 15 '21 at 01:18
  • @pascalamin - the current version of Andreas Wenzel's reply sounds like a very good approach. Please "upvote" and "accept" this answer if you agree. – paulsm4 Dec 15 '21 at 01:24
  • @AndreasWenzel thanks a lot for this detailed solution. Sorry for the late reply – pascalamin Dec 24 '21 at 23:11
  • @AndreasWenzel I can't thank you enough. Very helpful solution! – pascalamin Dec 24 '21 at 23:34
  • @AndreasWenzel it's one of the most detailed and clear solutions I've seen – pascalamin Dec 24 '21 at 23:39
1

I'm afraid there is no magic there, you might have two different options:

Either get the whole line as text and parse it (as you are doing right now with get line)

Or direcly fetch for the type as if it was a std::cin:

std::ifstream stream("fileName");
while(!stream.eof()){
   int x; 
   stream >> x; //Read the next int in your file. 
   // do something with it
} 
stream.close();

If you know before hand the structure of your file you can directly read those values using >> operator as shown before.

If your structure is undefined I'm afraid your only option is to parse it as string and then try to find what's the actual value either checking if it's a number or boolean or ...

Aleix Rius
  • 74
  • 2
  • 9
  • 4
    I would not recommend this: `while(!stream.eof()){` related; [https://stackoverflow.com/questions/5605125/why-is-iostreameof-inside-a-loop-condition-i-e-while-stream-eof-cons](https://stackoverflow.com/questions/5605125/why-is-iostreameof-inside-a-loop-condition-i-e-while-stream-eof-cons) – drescherjm Dec 14 '21 at 16:24
  • Fair enough, as always eof considers that you know the structure of your file and expects to be well formated. – Aleix Rius Dec 14 '21 at 16:28
  • The check for failure of `operator >>` should be done before using the result, not afterwards. Also, end-of-file is not the only type of failure that can occur. Therefore, it would probably be more appropriate to call [`ifstream::fail`](https://en.cppreference.com/w/cpp/io/basic_ios/fail) instead of `ifstream::eof`. This can most easily be accomplished by writing `while( stream >> x ){`, which uses [`ifstream::operator bool`](https://en.cppreference.com/w/cpp/io/basic_ios/operator_bool). – Andreas Wenzel Dec 14 '21 at 16:33
  • This not an answer at all. Maybe better to delete it. You make no reference to the given file format. I am wondering, who upvotes that . . . – A M Dec 14 '21 at 20:53
  • IMO, [std::getline](https://www.cplusplus.com/reference/string/string/getline/) is a much better way to accomplish what the OP is looking for.. – paulsm4 Dec 15 '21 at 01:21
0

I initially did not want to answer this question, because the requirements are a little bit unclear. I saw in other answers that the interpretation, or solution approach was to use std::getline to read a line and then output that. My assumptions is that the user may want to have the values, to further operate on them. But as said, this is my assumption and others may have other assumptions. Only the OP can clarify.

Based on my observations and my very personal interpretation, there are 2 possible approaches.

  1. The input could come from a “competitive programming” page. The given data look somehow like that. Then, the input can always be seen as correct and very simply be read step by step. Or
  2. There is some serious background and we can read the data in some structured way.

In both cases it is the structure of the “nXn” map is unclear. It could be a 2-dimensional char array or a 2-dimensional array of values, for example of type integer. In any case there are obviously n rows with n columns of something.

Let us first assume simple script, as often used on “competitive programming pages”.

The important point is that we obviously need some dynamic data structures, because, first the input describes, how many data follow, then we need to create a container for that and read the data into that container.

For a dynamic container we normally use a std::vector (a little bit depending on the later use case). On “competitive programming” pages VLAs (Variable Length Arrays) are very popular. But, since they are not C++ compliant, I will not use them.

There is also one fixed data structure, which is called “coordinate” by the OP, and obviously always contains 2 data. Maybe even an x or y coordinate for the map. But this is a guess.

Anyway, with these assumptions, we can come up with a potential solution approach.

  1. In the first line, we have 3 values. The dimension of the following map and 2 other properties that we do not know now
  2. Then we have a nXn map, with a little bit unknown content.
  3. A vector of vector with coordinates

So, one (of many) possible solution could be:

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

const std::string filename{"r:\\01"};

int main() {

    // Open the file and check, if it could be opened
    if (std::ifstream inputFileStream{ filename }; inputFileStream) {

        // Read basic parameters
        if (size_t dimensionOfMap{}, valueA{}, valueB{}; inputFileStream >> dimensionOfMap >> valueA >> valueB) {

            // Define the 2d map and set the dimension
            using MapDataType = char;
            std::vector<std::vector<MapDataType>> nXnMap(dimensionOfMap, std::vector<MapDataType>(dimensionOfMap, {}));

            // Read the comlete map
            for (std::vector<MapDataType> &row : nXnMap) for (char &col : row) inputFileStream >> col;

            // Next we want to read the coordinates. . We will store them in a vector of vector of pairs
            using Coordinate = std::pair<int, int>;
            std::vector < std::vector<Coordinate>> coordinates{};
            
            // We will first always read the number of coordinates and the the coordinates itself
            for (size_t numberOfCoordinates{}; inputFileStream >> numberOfCoordinates;) {

                // Add a new coordinate set
                coordinates.push_back({});

                // Now read the coordinates in a loop
                for (size_t index{}; index < numberOfCoordinates; ++index)
                    if (std::pair<int, int> temp{}; inputFileStream >> temp.first >> temp.second)
                        coordinates.back().push_back(temp);
                    else std::cerr << "\n*** Error: Could not read coordinates.\n\n";
            }
            // ----------------------------------------------------------------------------------------------------------------------------
            // Some Debug output
            std::cout << "\n\nDimension of map: " << dimensionOfMap << "\tValue a: " << valueA << "\tValue b: " << valueB << "\n\nMap:\n";
            for (const std::vector<MapDataType>& row : nXnMap) {
                for (char col : row) std::cout << col << ' ';
                std::cout << '\n';
            }
            // Coordinates
            std::cout << "\n\nCoordinates:\n";
            for (const std::vector<Coordinate>& coordinate : coordinates) {
                for (const auto& [x, y] : coordinate) std::cout << '[' << x << ',' << y << ']' << ' ';
                std::cout << '\n';
            }
        }
        else std::cerr << "\n*** Error: Could not read basic parameters.\n\n";
    }
    else std::cerr << "\n*** Error: Could not open input file  '" << filename << "'.\n\n";
}

If we want to follow a little bit more the object-oriented approach, then we would introduce classes with data and methods, operating on those data.

We would split down the big task into smaller tasks and especially encapsulate the read and write operations in the classes with the corresponding data.

The following data need to be abstracted and implemented as a class.

  1. General Data
  2. The Map
  3. A List with coordinates
  4. The coordinate

All classes know, how to read and write their data from/to a stream. And, because of the object-oriented approach, only the classes should know that.

That makes later changes to one dedicated class possible, without interfering with other parts of the code.

We define the classes in reverse order, so that we do not need any forward declarations.

Please see the power of C++. Start always with small and easy parts and later combine them.

Have a look at main. You will just see a one liner to read all data.

#include <iostream>
#include <vector>
#include <fstream>

struct Coordinate {

    // Data part
    int x{};
    int y{};

    // Simple extractor and inverter overlaods
    friend std::istream& operator >> (std::istream& is, Coordinate& c) { return is >> c.x >> c.y; }
    friend std::ostream& operator << (std::ostream& os, const Coordinate& c) { return os << '[' << c.x << ',' << c.y << ']'; }
};

struct CoordinateGroup {

    // Data part
    std::vector<Coordinate> coordinateGroup{};

    // Simple extractor and inverter overlaods
    friend std::istream& operator >> (std::istream& is, CoordinateGroup& cg) {
        cg.coordinateGroup.clear();
        if (size_t numberOfCoordinates{}; is >> numberOfCoordinates) {
            for (size_t k{}; k < numberOfCoordinates; ++k)
                if (Coordinate temp{}; is >> temp)
                    cg.coordinateGroup.push_back(temp);
        }
        return is;
    }
    friend std::ostream& operator << (std::ostream& os, const CoordinateGroup& cg) {
        for (const Coordinate c : cg.coordinateGroup) os << c << ' ';
        return os;
    }
};

struct Coordinates {

    // Data part
    std::vector<CoordinateGroup> coordinates{};

    // Simple extractor and inverter overlaods
    friend std::istream& operator >> (std::istream& is, Coordinates& c) {
        for (CoordinateGroup cg; is >> cg; c.coordinates.push_back(cg));
        return is;
    }
    friend std::ostream& operator << (std::ostream& os, const Coordinates& c) {
        for (const CoordinateGroup& cg : c.coordinates) os << cg << '\n';
        return os;
    }
};

struct NxNmap {
    using MapDataType = char;

    // Data part
    std::vector<std::vector<MapDataType>> map{};
    // Set dimensions of map
    void setDimension(size_t d) { map.resize(d, std::vector<MapDataType>(d)); }

    // Simple extractor and inverter overlaods
    friend std::istream& operator >> (std::istream& is, NxNmap& m) {
        for (std::vector<MapDataType>& row : m.map) for (char& col : row) is >> col;
        return is;
    }
    friend std::ostream& operator << (std::ostream& os, const NxNmap& m) {
        for (const std::vector<MapDataType>& row : m.map) {
            for (const char col : row) os << col;
            os << '\n';
        }
        return os;
    }
};
struct General {

    // Data part
    size_t dimensionOfMap{};
    int valueA{};
    int valueB{};

    // Simple extractor and inverter overlaods
    friend std::istream& operator >> (std::istream& is, General& g) {return is >> g.dimensionOfMap >> g.valueA >> g.valueB; }
    friend std::ostream& operator << (std::ostream& os, const General& g) {
        return os << "Map Dimension: " << g.dimensionOfMap << "\tValue a: " << g.valueA << "\tValue b: " << g.valueB;
    }
};
struct Data {

    // Data part
    General general{};
    NxNmap nXnMap{};
    Coordinates coordinates{};

    // Simple extractor and inverter overlaods
    friend std::istream& operator >> (std::istream& is, Data& d) {
        if (is >> d.general) {
            d.nXnMap.setDimension(d.general.dimensionOfMap);
            is >> d.nXnMap >> d.coordinates;
        }
        return is;
    }
    friend std::ostream& operator << (std::ostream& os, const Data& d) {
        return os << d.general << "\n\nMap:\n" << d.nXnMap << "\n\nCoordinates:\n" << d.coordinates << "\n\n";
    }
};


// Test/Driver Code
const std::string filename{ "01" };

int main() {

    // Open the file and check, if it could be opened
    if (std::ifstream inputFileStream{ filename }; inputFileStream) {

        // Define data struct
        Data data{};

        // Read all data with a one liner
        inputFileStream >> data;

        // Show all data. One liner.
        std::cout << data;
    }
    else std::cerr << "\n*** Error: Could not open input file  '" << filename << "'.\n\n";
}

With this input in a file 01

4 15 30
....
....
....
....
1
1 2
3
1 2
2 1
2 2

we will get the following output:

Map Dimension: 4        Value a: 15     Value b: 30

Map:
....
....
....
....


Coordinates:
[1,2]
[1,2] [2,1] [2,2]

Just as a reminder again

  • There are one million possible solutions
  • Everybody can do what he wants
A M
  • 14,694
  • 5
  • 19
  • 44