0

If "\n" brings us in .txt file to the next line, how can we go backwards for a few lines?

I must input the date into console, after that while(!myfile.eof()) checks for all lines matching up input date and prints them on screen. But what, if i need to cout previous 2 lines before the date cout?

Here's part of my code where i need to get previous line

void searchByDate(){
        system("cls");
        string line;
        string text;
        int counts = 0;
        string date;

            searching.open("info.txt", ios::app | ios::in | ios::out);

            cout << "Please enter a valid date (DD/MM/GG): ";
            cin >> date;

            if (searching.fail()){
                cerr << "ERROR! Cannot open file" << endl;
                exit(1);
            }

            while(!searching.eof()){
                getline(searching, line);
                if(line == date){
                    getline (searching, text);
                    cout << text << endl;
                    counts++;
                }
            }

            cout << counts << " flights were found in current date." << endl;
        searching.close();

}

Also, the console message that pops up is

Please enter a valid date (DD/MM/GG): 06/02/18
20:30:50
10:00:00
21:59:00
3 flights were found in current date.
Ozzie
  • 11
  • 3
  • 1
    You could keep all of the lines in a container, and print the previous two lines when you find the date. Also, .eof is a bad condition. – Arnav Borborah Feb 06 '18 at 18:43
  • Can i have an quick example for that? – Ozzie Feb 06 '18 at 18:44
  • Not unless you didn't remember each line's starting position in the file, Check [`std::ifstream::tellg()`](http://en.cppreference.com/w/cpp/io/basic_istream/tellg). Eg, something like with a `std::vector` where the vector index indicates the line number - 1. –  Feb 06 '18 at 18:45
  • _`while(!myfile.eof())`_ Heavily related: [Why is iostream::eof inside a loop condition considered wrong?](https://stackoverflow.com/questions/5605125/why-is-iostreameof-inside-a-loop-condition-considered-wrong) –  Feb 06 '18 at 18:49
  • Read the file line by line with `std::getline` into a `std::string`. `push_back` the `string` into a `std::vector`. – user4581301 Feb 06 '18 at 18:49
  • @user4581301 May be overklll for linewise navigation within an `istream`. –  Feb 06 '18 at 18:50
  • It's easy though. If memory's a concern run a three element circular buffer of strings. `std::deque` would be good for this. – user4581301 Feb 06 '18 at 18:52
  • @TheDude -- Your `tellg` approach would still benefit from being implemented as a circular buffer (instead of a circular buffer of text, it would be a circular buffer of tellg() positions). – PaulMcKenzie Feb 06 '18 at 19:04
  • I vote for the circular buffer. @OP are you up to the task? Also, I don't think this question should be downvoted -- it is a good question that requires much more than thinking of input and output statements. – PaulMcKenzie Feb 06 '18 at 19:12
  • So, as i mentioned before, in my console it pops up time for that airplane to fly away, but.. i need to get another line that shows me the flight takeoff and landing cities, e.g. Abu Dhabi(UAE) - Moscow (RUS) in 20:30:50 – Ozzie Feb 06 '18 at 19:19
  • @Ozzie -- We know what your goal is -- the issue is that you need to think further than just file reading. A buffer of 2 lines is what you need to be filling / refilling on each line read. – PaulMcKenzie Feb 06 '18 at 19:23
  • @PaulMcKenzie What _circular buffer_? Just index the line starting .positions (see my answer). Hauling through big streams might be optimized using `std::vector::reserve()`. –  Feb 06 '18 at 19:34
  • The reason for the circular buffer is so that you don't store millions of lines of useless information (whether they be strings or integers). All that is wanted is the last `n` lines of information (where `n` in this case is 2) if a particular line has just been read with some sort of identifier (a date). – PaulMcKenzie Feb 06 '18 at 19:42
  • @PaulMcKenzie I believe my approach leaves you open for doing all kinds of range extractions, single line jump, etc. –  Feb 06 '18 at 21:06
  • @Ozzie are you familiar with the [XY Problem](http://xyproblem.info/)? I think your question may fall into that category. You have a problem and you asked us how to implement your intended solution which is non-trivial. Please take a look at [my answer](https://stackoverflow.com/questions/48649804/how-can-i-navigate-in-a-stdifstream-using-line-positions/48652781#48652781) which will solve your original problem without having to traverse back through your input stream. – Justin Randall Feb 07 '18 at 17:15

2 Answers2

1

What I referred to in my comment, was that you could index an input stream just remembering the starting offsets of the lines.

The std::istream::tellg() and std::istream::seekg() functions allow you to navigate to arbitrary positions in an active and ready std::istream.

Here's a working example code:

A small bunch of standard library headers involved:

#include <sstream>
#include <string>
#include <vector>
#include <iostream>
#include <algorithm>
#include <cstddef>

A here document to establish a std::istream:

static const std::string theInput{R"inp(Lorem ipsum dolor sit amet,
consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
dolore eu fugiat nulla pariatur.

06/02/18

Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia
deserunt mollit anim id est laborum.
)inp"};

The main routine to index the line starting positions, and navigate within them:

int main()
{
    std::vector<std::size_t> line_positions;
    std::istringstream input_stream(theInput);
    std::string current_line;
    std::size_t theDatePos = -1u;

    // Collect all line starting positions
    do {
        line_positions.push_back(input_stream.tellg());
        std::getline(input_stream,current_line);
        if(current_line == "06/02/18") {
            theDatePos = line_positions.back();
        }
    } while(input_stream);

    // At this point the istream's eof bit is set, so to work furter
    // with it we need to clear() and reset the state.
    input_stream.clear();

    int current_line_number = line_positions.size();

    std::cout << "current_line: " << current_line_number << ". '" 
              << current_line << "'" << std::endl;

    if(theDatePos != -1u) {
        int date_line_number = 1;
        std::find_if(std::begin(line_positions),std::end(line_positions),
         [&date_line_number,theDatePos](const size_t& pos) {
             if(pos != theDatePos) {
                 ++date_line_number;
                 return false;
             }
             return true;
         });
        std::cout << "The date '06/02/18' was found at line number " 
                  << date_line_number << std::endl;
    }

    // Jump to line 3 and read it to the current line
    input_stream.seekg(line_positions[2]);
    std::getline(input_stream,current_line);
    std::cout << "current_line:  3. '" << current_line << "'" << std::endl;

    // Jump to line 5 and read it to the current line
    input_stream.seekg(line_positions[4]);
    std::getline(input_stream,current_line);
    std::cout << "current_line:  5. '" << current_line << "'" << std::endl;   

    // Jump back to line 2 and read it to the current line
    input_stream.seekg(line_positions[1]);
    std::getline(input_stream,current_line);
    std::cout << "current_line:  2. '" << current_line << "'" << std::endl;   
}

Output:

current_line: 14. ''
The date '06/02/18' was found at line number 10
current_line:  3. 'sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'
current_line:  5. 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut'
current_line:  2. 'consectetur adipiscing elit,'

The technique pointed out above might be helpful to navigate fast within big input streams, saving a minimum of information.
Keeping all the lines as std::string instances might be overkill.


Some nice algo abstraction left as exercise based on that model:

Provide functions that extract a single line or a range of lines from your line indexed std::istream:

 // Extract a single line based on a given line number (position)
 std::string getLineAtPos 
     ( std::istream& is, const std::vector<std::size_t>& linePositions
     , std::size linePos
     );

 // Extract a contiguous range of lines based on a given pair of line numbers 
 // (.first == low, .second == high)
 std::vector<std::string> getLineRange
     ( std::istream& is
     , const std::vector<std::size_t>& linePositions
     , std::pair<std::size_t,std::size_t>& lineRange
     );
  • ughm, what's the point of typing that akward text inside of that code? I mean the one after static const std::string theInput – Ozzie Feb 06 '18 at 20:46
  • @Ozzie _"what's the point of typing that akward text inside of that code?"_ To avoid ugly `\n` and end line escapes all over, of course: You might inform yourself about [_raw string literals_](http://en.cppreference.com/w/cpp/language/string_literal). –  Feb 06 '18 at 20:50
  • @Ozzie You probably didn't catch the abstraction from `std::ifstream` over `std::istringstream` (that _"awkward text"_) to `std::istream.` I leave it out as an excercise for you to apply that with your particular text input format use cases. –  Feb 06 '18 at 20:55
0

You are asking us the wrong question. What you want is to be able to search your data set for the matching criteria. The problem you're having is that you may not have found a match until you've already read past the beginning of your data set.

The simplest solution for this type of problem is to define a class for your input file's data set and use it to encapsulate your data for further processing as needed.

You could overload the istream and ostream operators and encapsulate your I/O operations in a class. This is a pretty clean way to achieve the desired behavior but beware there is no error checking on the input stream when building your object from the input. Adding error checking and input validation is an exercise to the reader.

Here we have a class called Flights that overloads the input and output stream operators.

class Flights
{
    friend std::ostream& operator << (std::ostream& oss, const Flights& f)
    {
        oss << "Departs: " << f._depart << " - "
            << "Arrives: " << f._arrive << "  Time: "
            << f._time << std::endl;
        return oss;
    }

    friend std::istream& operator >> (std::istream& iss, Flights& f)
    {
        std::string line;
        std::getline(iss, line);
        f._depart = line;
        std::getline(iss, line);
        f._arrive = line;
        std::getline(iss, line);
        f._date = line;
        std::getline(iss, line);
        f._time = line;
        return iss;
    }

    public:

        const std::string & getDeparture() const { return _depart; }
        const std::string & getArrival() const { return _arrive; }
        const std::string & getDate() const { return _date; }
        const std::string & getTime() const { return _time; }

    private:

        std::string _depart;   ///< departure city
        std::string _arrive;   ///< arrival city
        std::string _date;     ///< date to match user input
        std::string _time;     ///< departure time for flight
};

Now it's trivial to implement the business logic which will parse the input file and populate your local Flights object. If the user input date matches the date of that object, we will add it to our data store of matches. Since we overloaded the output stream, to output the results simply std::cout << object; Added bonus! If you need to search your data set by other criteria (like arrival city or departure city) you can easily write a function to accomplish that using this Flights class.

int main()
{
    std::string date;
    std::cout << "Please enter a valid date (DD/MM/GG): ";
    std::cin >> date;

    std::vector<Flights> flightList;
    std::ifstream infile("flights.txt");
    if (infile.is_open())
    {
        Flights flight;
        while (infile >> flight)
        {
            if (flight.getDate() == date)
            {
                flightList.push_back(flight);
            }
        }
        infile.close();
    }

    // print the flight data you found for that date
    std::cout << flightList.size() << " flights were found in current date." << std::endl;
    for (size_t i = 0; i < flightList.size(); ++i)
    {
        std::cout << flightList[i];
    }

    return 0;
}

The above code was tested on an input file that looked like the following:

flights.txt

Abu Dhabi (UAE)
Moscow (RUS)
06/01/18
24:00
Cape Town (CPT)
Johannesburg (HLA)
06/02/18
18:05
Buenos Aires (AEP)
Rio de Janeiro (GIG)
06/03/18
15:07
Justin Randall
  • 2,243
  • 2
  • 16
  • 23