print out the last 10 lines of a file - c++

I want to have the option to print out the last 10 lines of a textfile . with this program I've been able to read the whole textfile, but I can't figure out how to manipulate the array in which the textfile is saved, any help?
// Textfile output
#include<fstream>
#include<iostream>
#include<iomanip>
using namespace std;
int main() {
int i=1;
char zeile[250], file[50];
cout << "filename:" << flush;
cin.get(file,50); ///// (1)
ifstream eingabe(datei , ios::in); /////(2)
if (eingabe.good() ) { /////(3)
eingabe.seekg(0L,ios::end); ////(4)
cout << "file:"<< file << "\t"
<< eingabe.tellg() << " Bytes" ////(5)
<< endl;
for (int j=0; j<80;j++)
cout << "_";
cout << endl;
eingabe.seekg(0L, ios::beg); ////(6)
while (!eingabe.eof() ){ ///(7)
eingabe.getline(zeile,250); ///(8)
cout << setw(2) << i++
<< ":" << zeile << endl;
}
}
else
cout <<"dateifehler oder Datei nicht gefunden!"
<< endl;
return 0;
}

Try this:
#include <list>
#include <string>
#include <iostream>
#include <fstream>
#include <algorithm>
#include <iterator>
// A class that knows how to read a line using operator >>
struct Line
{
std::string theLine;
operator std::string const& () const { return theLine; }
friend std::istream& operator>>(std::istream& stream, Line& l)
{
return std::getline(stream, l.theLine);
}
};
// A circular buffer that only saves the last n lines.
class Buffer
{
public:
Buffer(size_t lc)
: lineCount(lc)
{}
void push_back(std::string const& line)
{
buffer.insert(buffer.end(),line);
if (buffer.size() > lineCount)
{
buffer.erase(buffer.begin());
}
}
typedef std::list<std::string> Cont;
typedef Cont::const_iterator const_iterator;
typedef Cont::const_reference const_reference;
const_iterator begin() const { return buffer.begin(); }
const_iterator end() const { return buffer.end();}
private:
size_t lineCount;
std::list<std::string> buffer;
};
// Main
int main()
{
std::ifstream file("Plop");
Buffer buffer(10);
// Copy the file into the special buffer.
std::copy(std::istream_iterator<Line>(file), std::istream_iterator<Line>(),
std::back_inserter(buffer));
// Copy the buffer (which only has the last 10 lines)
// to std::cout
std::copy(buffer.begin(), buffer.end(),
std::ostream_iterator<std::string>(std::cout, "\n"));
}

Basically, you are not saving the file contents to any array. The following sample will give you a head start:
#include <iostream>
#include <vector>
#include <string>
int main ( int, char ** )
{
// Ask user for path to file.
std::string path;
std::cout << "filename:";
std::getline(std::cin, path);
// Open selected file.
std::ifstream file(path.c_str());
if ( !file.is_open() )
{
std::cerr << "Failed to open '" << path << "'." << std::endl;
return EXIT_FAILURE;
}
// Read lines (note: stores all of it in memory, might not be your best option).
std::vector<std::string> lines;
for ( std::string line; std::getline(file,line); )
{
lines.push_back(line);
}
// Print out (up to) last ten lines.
for ( std::size_t i = std::min(lines.size(), std::size_t(10)); i < lines.size(); ++i )
{
std::cout << lines[i] << std::endl;
}
}
It would probably be wiser to avoid storing the whole file into memory, so you could re-write the last 2 segments this way:
// Read up to 10 lines, accumulating.
std::deque<std::string> lines;
for ( std::string line; lines.size() < 0 && getline(file,line); )
{
lines.push_back(line);
}
// Read the rest of the file, adding one, dumping one.
for ( std::string line; getline(file,line); )
{
lines.pop_front();
lines.push_back(line);
}
// Print out whatever is left (up to 10 lines).
for ( std::size_t i = 0; i < lines.size(); ++i )
{
std::cout << lines[i] << std::endl;
}

The eof() function does not do what you and it seems a million other C++ newbies think it does. It does NOT predict if the next read will work. In C++ as in any other language, you must check the status of each read operation, not the state of the input stream before the read. so the canonical C++ read line loop is:
while ( eingabe.getline(zeile,250) ) {
// do something with zeile
}
Also, you should be reading into a std::string, and get rid of that 250 value.

Do a circular buffer with 10 slots and while reading the file lines, putting them into this buffer. When you finish thr file, do a position++ to go to the first element and print them all.
Pay attention for null values if the file has less than 10 lines.

Have an array of strings with size 10.
Read the first line and store into the array
Continue reading till the array is full
Once the array is full delete the first entry so that you can enter new line
Repeate step 3 and 4 till the file is finished reading.

I investigate proposed approaches here and describe all in my blog post. There is a better solution but you have to jump to the end and persist all needed lines:
std::ifstream hndl(filename, std::ios::in | std::ios::ate);
// and use handler in function which iterate backward
void print_last_lines_using_circular_buffer(std::ifstream& stream, int lines)
{
circular_buffer<std::string> buffer(lines);
std::copy(std::istream_iterator<line>(stream),
std::istream_iterator<line>(),
std::back_inserter(buffer));
std::copy(buffer.begin(), buffer.end(),
std::ostream_iterator<std::string>(std::cout));
}

Related

Store input values into array while reading them in, c++

I am pretty new to c++. I am trying to read a file in line by line and store the input into several arrays.
Because I don't know the size of input file, I have this to get the number of lines in the file
while (std::getline(inputFile, line)){
++numOfLines;
std::cout << line << std::endl;
}
Now I want to use the numOfLines as the size of arrays, but i cannot get it run by having this
std::string *firstName= new std::string[numOfLines];
std::string *lastName= new std::string[numOfLines];
for (int i = 0; i < numOfLines; ++i)
{
line >> firstName[i];
}
I guess it is because it has reached the end of the file after the while loop. But I do not know how to solve this problem. Is there a way to scan the input file in and store the value into array at the same time?
If you use std::vector you don't need to know ahead the lines count. You can use vector method push_back to insert new elements into it. Try use something like this:
#include <fstream>
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main()
{
std::vector<std::string> first_names;
std::string line;
ifstream input_file;
while (std::getline(input_file, line)){
first_names.push_back(line);
}
for (size_t i = 0; i < first_names.size(); i++) {
std::cout << first_names[i] << std::endl;
}
return 0;
}
I don't know if you have ever taken a course related to Data Structures & Algorithms,
in which you will learn to use Containers (such as:
vector,
deque,
list, etc.) instead of Primitive Data Structures.
Please notice that although the follow example chooses vector as its container, it could vary according to different contexts. Say you are handling gigantic mount of data, you might want to use list instead`1,2,3.
#include <fstream>
#include <iostream>
#include <vector>
#include <string>
// alias long type
// #see: https://en.cppreference.com/w/cpp/language/type_alias
using NameVector = std::vector<std::string>;
int handleLine(std::string line, NameVector &firstNames)
{
// TODO implement your line handler here
firstNames.push_back(line);
return 0;
}
int handleFile(std::ifstream inputFile, NameVector &firstNames)
{
std::string line;
for (int lineNum = 1;
// invoke `good` to check if there is any error
inputFile.good()
&&
std::getline(inputFile, line);
lineNum++)
{
std::cout << "Current line number : (" << lineNum << ")" << std::endl;
std::cout << "Current line content: (" << line << ")" << std::endl;
handleLine(line, &firstNames);
}
return 0;
}
int main()
{
std::string path; // = R"(HERE GOES YOUR FILE PATH)";
// Using **Raw string**
std::ifstream inputFile { path }; // Initialize `inputFile`
NameVector firstNames;
handleFile(inputFile, firstNames);
for (auto firstName : firstNames)
{
std::cout << firstName << std::endl;
}
return 0;
}

How to return a string line by line in a function?

I am reading a text file which contains integers separated by a new line. Like this.
5006179359870233335
13649319959095080120
17557656355642819359
15239379993672357891
3900144417965865322
12715826487550005702
From this file, I want to access each integer in a loop and compare it with another, in order to match those two. In function File_read() I can print the integers. But what I want is to get it integer by integer outside the function. For example in main method, if there is a integer called x, I want to check whether x equals one of the integers in my text file.
string File_read() {
std::ifstream my_file("H:\\Sanduni_projects\\testing\\test.txt",
std::ifstream::binary);
if (my_file) {
string line;
for (int i = 0; i < 25; i++){
getline(my_file, line);
//cout << line << endl;
return line;
}
if (my_file)
std::cout << "all characters read successfully."<<endl;
my_file.close();
}
return 0;
}
Never return unconditionally inside a loop.
You are returning unconditionally from inside the loop. This causes the caller to exit the loop and return from the function during the first iteration.
for (int i = 0; i < 25; i++){
getline(my_file, line);
return line; // <-- Return from function (rest of iterations unreachable). Bad.
}
No need to reinvent stuff
Use the standard library to read the numbers, e.g., into a container std::vector.
std::vector<unsigned long long> v{std::istream_iterator<unsigned long long>{my_file},
std::istream_iterator<unsigned long long>{}};
Notice the value type of unsigned long long that is needed to fit the large numbers (you're pushing ~64 bits here).
Find a match
Use, e.g., std::find to find a possible match among the parsed numbers.
auto key = 15239379993672357891ull;
if (auto it = std::find(std::begin(v), std::end(v), key); it != std::end(v)) {
std::cout << "Key found at line " << std::distance(std::begin(v), it) + 1 << std::endl;
}
Here, I'm using a C++1z if(init; condition) statement to limit the scope of the iterator it to inside the if statement. It's optional of course.
Live example
You are, currently, just returning the first number (as a std::string and not a number). If you remove the return statement in your loop you can, of course, print each of them. Here is a slightly modified version of your File_read function that will return a std::vector<unsigned long long> that contains all the numbers. Then you can use this vector in, e.g., your main function to do your processing.
std::vector<unsigned long long> File_read()
{
std::vector<unsigned long long> numbers;
std::ifstream my_file("H:\\Sanduni_projects\\testing\\test.txt"); // Text files are not 'binany', i.e., removed std::ifstream::binary
if (my_file)
{
std::string line;
for (int i = 0; i < 25; i++)
{
std::getline(my_file, line);
numbers.push_back(std::stoull(line));
}
if (my_file)
{
std::cout << "all characters read successfully." << std::endl;
}
// my_file.close(); // Do not do this manually
}
return numbers;
}
Usage example:
int main()
{
unsigned long long x = /* some number */;
// Read all the numbers
std::vector<unsigned long long> vl = File_read();
// Run through all the numbers
for (unsigned long long y : vl)
{
// Check if any of the numbers are equal to x
if (x == y)
{
// There is a match...
// Do stuff
}
}
}
Update
The numbers cannot be held by in a long, however unsigned long long is sufficient.
std::vector<long> File_read(){
vector<long> numbers;
ifstream my_file("H:\\Sanduni_projects\\testing\\test.txt",
std::ifstream::binary);
if (my_file) {
string line;
for (int i = 0; i < frames_sec; i++){
getline(my_file, line);
numbers.push_back(std::stol(line));
}
if (my_file)
std::cout << "all characters read successfully." << endl;
else
std::cout << "error: only " << my_file.gcount() << " could be read" << endl;
my_file.close();
}
else{
cout << "File can not be opened" << endl;
}
return numbers;
}
Although the someone gives the answers that works correctly, I want to share my code.
#include <memory>
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
#define MAX_SIZE 4096
class FileRead
{
public:
FileRead(string path) :_file(path)
{
Reset();
}
void Reset()
{
memset(_buff, 0, MAX_SIZE);
}
string ReadLine()
{
if (!_file.is_open())
{
cout << "error open file" << endl;
return "";
}
if (!_file.eof())
{
Reset();
_file.getline(_buff,MAX_SIZE);
return string(_buff);
}
else
{
cout << "read file finished." << endl;
return "";
}
}
private:
ifstream _file;
string _line;
char _buff[MAX_SIZE];
};
int _tmain(int argc, _TCHAR* argv[])
{
FileRead fr("H:\\Sanduni_projects\\testing\\test.txt");
string line;
while (!(line = fr.ReadLine()).empty())
{
//do some compare..
}
return 0;
}
The other answers are correct about how return works, but there is something that acts how you thought return acted.
using string_coro = boost::coroutines::asymmetric_coroutine<std::string>
void File_read(string_coro::push_type & yield) {
std::ifstream my_file("H:\\Sanduni_projects\\testing\\test.txt", std::ifstream::binary);
if (my_file) {
string line;
for (int i = 0; i < 25; i++){
getline(my_file, line);
yield (line);
}
if (my_file)
std::cout << "all characters read successfully." << std::endl;
my_file.close();
}
}
Which is used like this
string_coro::pull_type(File_read) strings;
for (const std::string & s : strings)
std::cout << s << endl;

CSV parser in C++ doesn't read the first element

I extracted this piece of code to parse a CSV file, however it doesn't read the first element for the first n-1 rows. I can't figure it out why, and when I copy the data into a new empty file and save it as CSV file, the error disappears and it works fine. Here are the links for the original (error happens) and the copied (error doesn't happen) CSV file. Could you please help me as why this is happening?
Thank you.
#include <boost/tokenizer.hpp>
#include <fstream>
#include <string>
#include <vector>
#include <iostream>
#include <cstdlib>
int main(int argc, char** argv)
{
using namespace std;
if (argc != 2)
{
cerr << "Usage: " << argv[0] << " <csv file>" << endl;
return -1;
}
vector< vector<string> > csv_values;
fstream file(argv[1], ios::in);
if (file)
{
typedef boost::tokenizer< boost::char_separator<char> > Tokenizer;
boost::char_separator<char> sep(",");
string line;
while (getline(file, line))
{
Tokenizer info(line, sep); // tokenize the line of data
vector<string> values;
for (Tokenizer::iterator it = info.begin(); it != info.end(); ++it)
{
// convert data into double value, and store
values.push_back(it->c_str());
}
// store array of values
csv_values.push_back(values);
}
}
else
{
cerr << "Error: Unable to open file " << argv[1] << endl;
return -1;
}
// display results
cout.precision(1);
cout.setf(ios::fixed,ios::floatfield);
for (vector< vector<string> >::const_iterator it = csv_values.begin(); it != csv_values.end(); ++it)
{
const vector<string>& values = *it;
for (vector<string>::const_iterator it2 = values.begin(); it2 != values.end(); ++it2)
{
cout << *it2 << " ";
}
cout << endl;
}
}
New lines in your orginal file ends with a carriage return, which is read by your code with last varible in the line, and then printed. So first line is printed like this
1 2 3 4 5\r
and then you print space, which is printed at the beginning of the line, covering "1".
You could easily see that in debugger :)

Read and print a csv file with more than 2 column in c++ using multimap

I'm a beginner in c++ and required to write a c++ program to read and print a csv file like this.
DateTime,value1,value2
12/07/16 13:00,3.60,50000
14/07/16 20:00,4.55,3000
May I know how can I proceed with the programming?
I manage to get the date only via a simple multimap code.
I spent some time to make almost (read notice at the end) exact solution for you.
I assume that your program is a console application that receives the original csv-file name as a command line argument.
So see the following code and make required changes if you like:
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <map>
#include <string>
std::vector<std::string> getLineFromCSV(std::istream& str, std::map<int, int>& widthMap)
{
std::vector<std::string> result;
std::string line;
std::getline(str, line);
std::stringstream lineStream(line);
std::string cell;
int cellCnt = 0;
while (std::getline(lineStream, cell, ','))
{
result.push_back(cell);
int width = cell.length();
if (width > widthMap[cellCnt])
widthMap[cellCnt] = width;
cellCnt++;
}
return result;
}
int main(int argc, char * argv[])
{
std::vector<std::vector<std::string>> result; // table with data
std::map<int, int> columnWidths; // map to store maximum length (value) of a string in the column (key)
std::ifstream inpfile;
// check file name in the argv[1]
if (argc > 1)
{
inpfile.open(argv[1]);
if (!inpfile.is_open())
{
std::cout << "File " << argv[1] << " cannot be read!" << std::endl;
return 1;
}
}
else
{
std::cout << "Run progran as: " << argv[0] << " input_file.csv" << std::endl;
return 2;
}
// read from file stream line by line
while (inpfile.good())
{
result.push_back(getLineFromCSV(inpfile, columnWidths));
}
// close the file
inpfile.close();
// output the results
std::cout << "Content of the file:" << std::endl;
for (std::vector<std::vector<std::string>>::iterator i = result.begin(); i != result.end(); i++)
{
int rawLen = i->size();
for (int j = 0; j < rawLen; j++)
{
std::cout.width(columnWidths[j]);
std::cout << (*i)[j] << " | ";
}
std::cout << std::endl;
}
return 0;
}
NOTE: Your task is just to replace a vector of vectors (type std::vector<std::vector<std::string>> that are used for result) to a multimap (I hope you understand what should be a key in your solution)
Of course, there are lots of possible solutions for that task (if you open this question and look through the answers you will understand this).
First of all, I propose to consider the following example and to try make your task in the simplest way:
#include <iostream>
#include <sstream>
#include <vector>
#include <string>
using namespace std;
int main()
{
string str = "12/07/16 13:00,3.60,50000";
stringstream ss(str);
vector<string> singleRow;
char ch;
string s = "";
while (ss >> ch)
{
s += ch;
if (ss.peek() == ',' || ss.peek() == EOF )
{
ss.ignore();
singleRow.push_back(s);
s.clear();
}
}
for (vector<string>::iterator i = singleRow.begin(); i != singleRow.end(); i++)
cout << *i << endl;
return 0;
}
I think it can be useful for you.

Storing data from an unknown number of files

I have used the following piece of code to read from multiple .dat files and parse them. This code uses 3D vectors to store data after the reading process. However, I would like that the data corresponding to each single file be independent from the others. The issue is that the number of files varies, and is unknown at compile time; hence, the number of vectors varies too. I would like to know if there is any solution for this.
vector<vector<vector<string>>> masterList;
for (int i = 0; i < files.size(); ++i) {
cout << "file name: " << files[i] << endl;
fin.open(files[i].c_str());
if (!fin.is_open()) {
// error occurs!!
// break or exit according to your needs
cout<<"error"<<endl;
}
std::vector<vector<string>> tokens;
int current_line = 0;
std::string line;
while (std::getline(fin, line))
{
cout<<"line number: "<<current_line<<endl;
// Create an empty vector for this line
tokens.push_back(vector<string>());
//copy line into is
std::istringstream is(line);
std::string token;
int n = 0;
//parsing
while (getline(is, token, DELIMITER))
{
tokens[current_line].push_back(token);
cout<<"token["<<current_line<<"]["<<n<<"] = " << token <<endl;
n++;
}
cout<<"\n";
current_line++;
}
fin.clear();
fin.close();
masterList.push_back(tokens);
}
So, the main issue I'm facing is: how to create a variable number of 2D vectors to store the data corresponding to each single file, when I don't know how many files there are at compile time.
Modify the list of files in the main to adapt the size of your "master data". If the length of file names is variable, then parse it first (or get it one way or another first), and then execute the parsing on the dat files. If the filenames are known at run time only, and asynchronously with that, then add a new element in the list each time you get a new filename (you can use events for that for example, take a look at https://github.com/Sheljohn/siglot).
Note that list elements are independent in memory, and that lists support deletion/insertion in constant time. That way, data corresponding to each file is independent from the other. If you want to retrieve the data specific to a file (knowing the filename), either iterate on the list to find the corresponding file (linear time) or trade the list for an unordered_map (amortized constant time).
#include <string>
#include <list>
#include <vector>
#include <iostream>
#include <sstream>
#include <fstream>
#include <iterator>
#include <algorithm>
using namespace std;
#define AVG_LINES_PER_FILE 100
/**
* [tokenize_string Tokenize input string 'words' and put elements in vector 'tokens'.]
* #param words [Space separated data-string.]
* #param tokens [Vector of strings.]
*/
void tokenize_string( string& words, vector<string>& tokens )
{
unsigned n = count( words.begin(), words.end(), ' ' );
tokens.reserve(n);
istringstream iss(words);
copy(
istream_iterator<string>(iss),
istream_iterator<string>(),
back_inserter<vector<string> >(tokens)
);
}
/**
* Contains data parsed from a single .dat file
*/
class DATFileData
{
public:
typedef vector<string> line_type;
typedef vector<line_type> data_type;
DATFileData( const char* fname = nullptr )
{
m_fdata.reserve(AVG_LINES_PER_FILE);
m_fdata.clear();
if ( fname ) parse_file(fname);
}
// Check if the object contains data
inline operator bool() const { return m_fdata.size(); }
// Parse file
bool parse_file( const char* fname )
{
string line;
m_fdata.clear();
ifstream fin( fname );
if ( fin.is_open() )
{
while ( fin.good() )
{
getline(fin,line);
m_fdata.push_back(line_type());
tokenize_string( line, m_fdata.back() );
}
fin.close();
m_fname = fname;
cout << "Parsed " << m_fdata.size() << " lines in file '" << fname << "'." << endl;
return true;
}
else
{
cerr << "Could not parse file '" << fname << "'!" << endl;
return false;
}
}
// Get data
inline unsigned size() const { return m_fdata.size(); }
inline const char* filename() const { return m_fname.empty() ? nullptr : m_fname.c_str(); }
inline const data_type& data() const { return m_fdata; }
inline const line_type& line( const unsigned& i ) const { return m_fdata.at(i); }
private:
string m_fname;
data_type m_fdata;
};
int main()
{
unsigned fcount = 0;
vector<string> files = {"some/file/path.dat","another/one.dat"};
list<DATFileData> data(files.size());
for ( DATFileData& d: data )
d.parse_file( files[fcount++].c_str() );
cout << endl << files.size() << " files parsed successfully." << endl;
}