intrigued_66
intrigued_66

Reputation: 17268

Read a file backwards?

Is there a way to read a file backwards, line by line, without having to go through the file from the beginning to start reading backwards?

Upvotes: 20

Views: 39759

Answers (7)

Ash
Ash

Reputation: 1

this might help.

#include <fstream>
#include <iostream>

using namespace std;

int main()
{
    ifstream myFile("my.txt");
    int count;
    cout << "Enter the number of lines u want to print ";
    cin >> count;
    char c;
    string str = "";
    for (int i = 1; i <= 10000; i++)
    {
        myFile.seekg(-i, std::ios::end);
        myFile.get(c);
        str += c;
        if (c == '\n')
        {
            reverse(str.begin(), str.end());
            count--;
            cout << str;
            str.clear();
        }
        if (count == 0)
        {
            break;
        }
    }
    cout << endl;

    return 0;
}

Upvotes: -1

Personage
Personage

Reputation: 484

My answer is similar to ones that use a vector to store the lines of the file, but I would instead use a list.

Imagine you have the following text in a file called input.txt:

hello
there
friend

I would read the file line-by-line, pushing each line not to the back of my list but to its front. Using this rather than push_back has the same effect as reading the contents of the file line-by-line into a vector and then reversing it or iterating through it backwards.

#include <iostream>
#include <fstream>
#include <list>
#include <string>
#include <iterator>
#include <algorithm>

int main(void) {
    std::ifstream file;
    file.open("input.txt");
    // Make sure the file opened properly

    std::list<std::string> list;
    std::string buffer;
    while (std::getline(file, buffer)) {
        list.push_front(buffer);
    }

    file.close();

    std::copy(
        list.begin(),
        list.end(),
        std::ostream_iterator<std::string>(std::cout, "\n")
    );

    return 0;
}

(Note that the bit at the bottom with std::copy is just to print the contents of the list with a newline character as a delimiter between elements.)

This then prints:

friend
there
hello

Upvotes: 0

Aadhil
Aadhil

Reputation: 470

Slightly improved version will be this:-
1)Seek to the last-1 position
2)Get the last-1 position
3)Read a char and print it;
4)seek 2 pos back;
5)repeat 3 &4 for last-1 times;

    ifstream in;
    in.open("file.txt");
    char ch;
    int pos;
    in.seekg(-1,ios::end);
    pos=in.tellg();
    for(int i=0;i<pos;i++)
    {
        ch=in.get();
        cout<<ch;
        in.seekg(-2,ios::cur);
    }
    in.close();

Upvotes: 6

Tejendra
Tejendra

Reputation: 1944

  1. Open the file for read, call fseek() to seek to the end of the file, then call ftell() to get the length of the file. Alternatively you can get the file length by calling stat() or fstat()

  2. Allocate a buffer pointer to the file size obtained in #1, above.

  3. Read the entire file into that buffer -- you can probably use fread() to read the file all in one shot (assuming the file is small enough).

  4. Use another char pointer to transverse the file from end to beginning of the buffer.

Upvotes: 4

Alexis Wilke
Alexis Wilke

Reputation: 20818

The short answer would be no. However, you can use the seek() function to move your pointer to where you want to go. Then read() some data from that point. If you know well how to manage buffers, then it should be pretty quick because you can read and cache the data and then search for the previous newline character(s). Have fun with \r\n which will be inverted...

-- Update: some elaboration on the possible algorithm --

This is not valid code, but it should give you an idea of what I'm trying to say here

File reads:

int fpos = in.size() - BUFSIZ;
char buf[BUFSIZ];
in.seek(fpos);
in.read(buf, BUFSIZ);
fpos -= BUFSIZ; // repeat until fpos < 0, although think of size % BUFSIZ != 0
// now buf has characters... reset buffer position
int bpos = BUFSIZ - 1;

Getting string:

// first time you need to call the read
if(bpos == -1) do_a_read();
// getting string
std::string s;
while(bpos >= 0 && buf[bpos] != '\n') {
  s.insert(0, 1, buf[bpos]);
  --bpos;
}
// if bpos == -1 and buf[0] != '\n' then you need to read another BUFSIZ chars
// and repeat the previous loop...

// before leaving, skip all '\n'
while(bpos >= 0 && buf[bpos] == '\n') {
  --bpos;
}
return s;

To ease with '\r', you could have a first pass that transforms all '\r' to '\n'. Otherwise, all the tests of '\n' need to also test for '\r'.

Upvotes: 4

hmjd
hmjd

Reputation: 122001

As per comment, a possible (quite simple) alternative would be read the lines into a vector. For example:

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

int main()
{
    std::ifstream in("main.cpp");

    if (in.is_open())
    {
        std::vector<std::string> lines_in_reverse;
        std::string line;
        while (std::getline(in, line))
        {
            // Store the lines in reverse order.
            lines_in_reverse.insert(lines_in_reverse.begin(), line);
        }
    }
}

EDIT:

As per jrok's and Loki Astari's comments, push_back() would be more efficient but the lines would be in file order, so reverse iteration (reverse_iterator) or std::reverse() would be necessary:

    std::vector<std::string> lines_in_order;
    std::string line;
    while (std::getline(in, line))
    {
        lines_in_order.push_back(line);
    }

Upvotes: 9

Roger Lipscombe
Roger Lipscombe

Reputation: 91925

Use a memory-mapped file and walk backwards. The OS will page in the needed parts of the file in reverse order.

Upvotes: 15

Related Questions