file reading issues

Am trying to make a function that displays the last 10 lines of a file and I thought the way to do this is to read the file backwards using seekg and ios::beg but its not working I think am in the right direction but i need some tips on how i can fix this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
Lab6_FileReader.h

[code]#pragma once
#include <iostream>
#include <fstream>
#include <iomanip>
#include <string>

using namespace std;
class Lab6_FileReader
{
private:
	string filename;
	int numrecords;

public:
	Lab6_FileReader(string _filename);
	void displayFirst10records();
	void displayLast10records();
	void displayAllRecords();
	int getNumRrecords();
};

Lab6_FileReader.cpp

#include "Lab6_FileReader.h"
/*
	Constructor
	Read file and determine the number of records
*/
Lab6_FileReader::Lab6_FileReader(string _name)
{
	numrecords = 0;
	string oneRec;
	filename = _name;
	ifstream ifile(_name);

	if(!ifile)
	{
		cout << "File open failed..program terminating..\n";
		system("pause");
		exit(0);
	}

	while (getline(ifile, oneRec))
		numrecords++;

	ifile.close();
}
/*
return class private variable holding number of records

*/
int Lab6_FileReader::getNumRrecords()
{
	return numrecords;
}
/*
	Display first 10 records with line numbers. If the file has fewer than 10 records,
	display all records

*/
void Lab6_FileReader::displayFirst10records()
{
	ifstream i_file(filename);
	string display;
	int counter = 0;

	cout << "\n" << filename << ":  FIRST 10 records in file \n\n";

	for (int i = 0; i < 10; i++)
	{
		getline(i_file, display);
		
		cout << setw(2) << (counter + 1) << ".\t" << display << endl;
		counter++;
		

	}
	
}
void Lab6_FileReader::displayLast10records()
{
	ifstream i_file(filename);
	string display;
	int recsize = sizeof(filename);
	int counter = 0;
	cout << "\n" << filename << ":  LAST 10 records in file \n\n";

	for (int recnum = -1; recnum < 9; recnum++)
	{
		getline(i_file, display);
		int bytelocation = (recnum - 1) * recsize;
		filename.seekg(bytelocation, ios::beg);

		cout << setw(2) << (counter + 1) << ".\t" << display << endl;
		counter++;
	}

}
void Lab6_FileReader::displayAllRecords()
{
	ifstream ifile(filename);
	int displayed_lines = 1;
	string arec;

	cout <<"\n" << filename << ": ALL records in the file with line numbers, 10 at a time\n\n";

	while (getline(ifile, arec))
	{
		if(displayed_lines % 10 == 0 && displayed_lines > 0)
			system("pause");

		cout << setw(2) << (displayed_lines + 1) << ".\t" << arec << endl;
		displayed_lines++;
	}

	cout << "\n\n-----------------------------------------\n\n";
	ifile.close();
}

class_tester cpp

#include <iostream>
#include <iomanip>
#include <fstream>
#include <string>
using namespace std;
#include "Lab6_FileReader.h"

void display_file(string fname);
int main()
{
	display_file("Lab6_A.txt");
	display_file("Lab6_B.txt");

	system("pause");
	return 0;
}

void display_file(string fname)
{
	Lab6_FileReader myfile(fname);

	cout << "\n" << fname << " :  # of records in file = " 
		<< myfile.getNumRrecords() <<"\n";

	myfile.displayFirst10records();

	myfile.displayLast10records();

	myfile.displayAllRecords();
	system("pause");
}

[/code]
Last edited on
What has recsize to do with filename?
sizeof(filename) will return the size of the string object not the content (which would be filename.size()). But again: How could the size of filename be related to the size of the record?

The problem with your approach is that you cannot determine the postition to start if you don't know the exact record size.

One approach could be:

Read 10 lines (determined be counter). If you reach the limit each line is moved on position toward the fromt until the first line which is dismissed. In a second loop show the remaining 10 lines.

Another approach:

Read all lines in a vector and show the last 10 only.
Last edited on
Hello.

I would recommend the following code to read files:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<fstream>
#include<sstream>
#include<string>
#include<cstdlib>
#include<iostream>

readFile(const std::string& filePath, std::string& fileContent) {
	std::ifstream inFile(filePath, std::ios::binary);
	if (inFile.is_open()) {
		auto const start_pos{ inFile.tellg() };
		inFile.ignore(std::numeric_limits<std::streamsize>::max());
		std::streamsize char_count{ inFile.gcount() };
		inFile.seekg(start_pos);
		fileContent = std::string(static_cast<std::size_t>(char_count), '0');
		inFile.read(&fileContent[0], static_cast<std::streamsize> (fileContent.size()));
		inFile.close();
	}//end of if
	else {
		std::cout << "Unable to open file\n";
		std::exit(EXIT_FAILURE);
	}//end of else
}

this code is safe and fast for big files.

What i would advice to do is:
-each line has a \n at the end right?
So once you finish reading the file just count the \n from the end until you hit 10 and than just std::cout the substring which conrains the 10 lines.

I am by no means an expert and surely it can be done better but this is the safest way i know of. Good luck :)
Last edited on
A rule i always set myself is that its always better to work on files once they are transfered into a string, its safer and less error prone.
One way would be to count the number of lines first. When you read the file a second time you skip all the lines not needed.

If you are allowed to use a vector read all the lines in a vector and display then the last 10 lines.
@Thomas1965
Your way is simpler i believe, although we recommended almost the same thing :)
Just have one question i asked in another post:
Wouldn't an std::deque<char> be faster for that purpose ? if he doesn't reserve space for vector it will severely impact performance on long lines as many allocations must be made in the process.
I like @Thomas1965's idea, but here are some other ideas:
- read the file line by line into strings and keep a rolling container of the last (up-to) 10 strings;
- do something clever with stream iterators? (I don't actually know what!)

I tried seekg and searching for \n to save having to store an entire file and read large files only once:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <fstream>
using namespace std;

int main()
{
   const int NUMLINES = 10;
   char c;
   ifstream in( "temp.cpp", ios::binary );

   in.seekg( -1, ios::end );                     // position to read last character
   int counter = -1;                             // assumes that you will pick up a NL at end of file
   while ( counter < NUMLINES )
   {
      in.get( c );  
      if ( !in ) { in.clear();  in.seekg( 0, ios::beg );  break; }   // deal with <= NUMLINES or not found
      if ( c == '\n' ) counter++;                                    // found a new line
      if ( counter != NUMLINES ) in.seekg( -2, ios::cur );           // move back to previous character
   }

   c = in.peek();   if ( c == '\r' ) in.seekg( 1, ios::cur );        // deal with CR-LF

   while ( in.get( c ) ) cout.put( c );                              // output rest of file
}




One based on @Thomas1965's idea. I have to admit, it's easier to understand.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main()
{
   const int NUMLINES = 10;
   int nlines, first;
   string line;

   // First pass counts lines
   ifstream in( "temp.cpp" );
   nlines = 0;
   while( getline( in, line ) ) nlines++;
   in.close();

   // Second pass only outputs the desired number
   in.open( "temp.cpp" );
   first = nlines - NUMLINES + 1;   if ( first < 1 ) first = 1;
   nlines = 0;
   while( getline( in, line ) ) 
   {
      nlines++;
      if ( nlines >= first ) cout << line << '\n';
   }
   in.close();
}


Last edited on
I'l advice you to read:
http://cpp.indi.frih.net/blog/2014/09/how-to-read-an-entire-file-into-memory-in-cpp/

It has plenty of advice on file reading and why it should be done certain ways.


I tried seekg and searching for \n to save having to store an entire file and read large files only once:

#include <iostream>
#include <fstream>
using namespace std;

int main()
{
const int NUMLINES = 10;
char c;
ifstream in( "temp.cpp", ios::binary );

in.seekg( -1, ios::end ); // position to read last character
int counter = -1; // assumes that you will pick up a NL at end of file
while ( counter < NUMLINES )
{
in.get( c );
if ( !in ) { in.clear(); in.seekg( 0, ios::beg ); break; } // deal with <= NUMLINES or not found
if ( c == '\n' ) counter++; // found a new line
if ( counter != NUMLINES ) in.seekg( -2, ios::cur ); // move back to previous character
}

c = in.peek(); if ( c == '\r' ) in.seekg( 1, ios::cur ); // deal with CR-LF

while ( in.get( c ) ) cout.put( c ); // output rest of file
}


I don't think its a good way either
Last edited on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <string>
#include <deque>
#include <fstream>

std::deque< std::pair<std::size_t,std::string> > tail( std::ifstream file, std::size_t n = 10 )
{
    std::deque< std::pair<std::size_t,std::string> > lines ;

    std::string str ;
    std::size_t slno = 0 ;

    while( lines.size() < n && std::getline( file, str ) ) lines.emplace_back( ++slno, str ) ;

    while( std::getline( file, str ) )
    {
        lines.pop_front() ;
        lines.emplace_back( ++slno, str ) ;
    }

    return lines ;
}

int main()
{
    for( const auto& pair : tail( std::ifstream(__FILE__), 15 ) )
        std::cout << pair.first << ". " << pair.second << '\n' ;
}

http://coliru.stacked-crooked.com/a/a244ca41099ec52f
Topic archived. No new replies allowed.