0

I want to make a program that reads lines from .txt as follows:

Input: name of file / line to start printing from / number of last line to print

In console it will look like this: text 10 30 (It will print from line 10 to line 30 from .txt file)

I managed to print all lines, but do not know how to display them in range. I am sharing my code, but if you know an easier way - will be appreciated!

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

using namespace std;

bool create_file_with_n_lines(string outfile, int n)
{
    ofstream of;
    int i;

    of.open(outfile.c_str());

    for(i = 0; i < n; i++){
        of << "line " << i << endl;
    }

    of.close();

    return true;
}

bool get_last_n_lines(string infile, int n)
{
    ifstream instream;
    instream.open(infile.c_str());

    vector<string> v;
    char line[256];
    int i = 0;

    if(n < 1){
        return false;
    }

    while(!instream.eof()){
        instream.getline(line, 256);
        cout << line << endl;
        if(i < n){
            v.insert(v.end(), line);
        }
        else{
            string str = line;
            if(str.size() != 0)
                v.at(i%n) = line;
        }

        i ++;
    }

    cout << "after processing" << endl;

    int j;
    i --;

    if(i > n){
        i = i % n;
        for(j = i; j < n; j ++){
            cout << v.at(j).c_str() << endl;
        }
    }

    for(j = 0; j < i; j ++){
        cout << v.at(j).c_str() << endl;
    }

    return true;
}

int main(int argc, char* argv[])
{
    string strfile = "out.txt";
    create_file_with_n_lines(strfile,12);
    get_last_n_lines(strfile,  5);

    return 0;
}
  • [Why !.eof() inside a loop condition is always wrong.](https://stackoverflow.com/q/5605125/9254539) Then in `main()`, `size_t beg = 0, end = std::numeric_limits::max();` then just check `if (argc > 2)` convert the begin line and set `beg`, then check `if (argc > 3)` do the same for `end`. Then read and output `if (beg <= n && n <= end) .... else if (end < n) break;` where `n` is your line counter. – David C. Rankin Apr 27 '20 at 14:39

2 Answers2

1

I believe you're overthinking this.

To print lines from a to b (zero-based), you skip a lines, then print the next b-a+1 lines.

void print_lines(string infile, int from, int to)
{
    ifstream instream(infile);
    int count = to - from + 1;
    std::string line;
    while (from-- && std::getline(instream, line))
    {
        // Intentionally left blank.
    }
    while (count-- && std::getline(instream, line))
    {
        std::cout << line << std::endl;
    }
}
molbdnilo
  • 55,783
  • 3
  • 31
  • 71
  • Is there a technical reason for putting the `getline` into the condition instead of the body of the loop, or is it "just" a matter of taste? – dedObed Apr 27 '20 at 14:54
  • 1
    The purpose is to stop if the file doesn't have that many lines. – molbdnilo Apr 27 '20 at 15:03
0

There are a number of ways to approach what you are trying to do. Rather than prompting for input of the filename and range of lines, C++ provides parameters to int main (int argc, char **argv) that allow you to pass the filename to read and your limits on the command line, e.g. ./line_range file 3 30 to open file and output lines 3 - 30. This also allows you to print the entire file by default and handle printing from a beginning line number if only the first argument of the range is given. (you can do this in your function as well)

A reasonable way is to set proper defaults for the begin and end lines in the range, e.g.

#include <limits>
...
    size_t  n = 1, beg = n, /* current, begin, end counts */
            end = std::numeric_limits<std::streamsize>::max();

Open your file and validate your file is open for reading, e.g.

    if (argc < 2) { /* validate at least one argument given for filename */
        std::cerr << "error: insufficient arguments\nusage: program file [begin end]\n";
        return 1;
    }
    std::fstream f (argv[1]);   /* open file */

    if (!f.is_open()) { /* validate file is open for reading */
        std::cerr << "error: file open failed '" << argv[1] << "'.\n";
        return 1;
    }

Now check your range argument and convert them to values:

    if (argc > 2)   /* if argument given for begin */
        beg = std::stol (std::string { argv[2] });  /* convert to long */
    if (argc > 3)   /* if argument given for end */
        end = std::stol (std::string { argv[3] });  /* convert to long */

(note: for each std::stol you should implement a try .. catch exception handler to catch any error in the conversion -- that is left to you)

Now simply loop reading line and outputting those that fall in the specified range, stopping your read of the file when your upper limit is reached (no need to continue reading lines that won't be used)

    for (; n <= end && getline (f, line); n++)      /* read each line <= end */
        if (beg <= n)                               /* if count >= beg */
            std::cout << line << '\n';              /* output line */

Putting it altogether in a short example, you could do:

#include <iostream>
#include <fstream>
#include <limits>
#include <string>

int main (int argc, char **argv) {

    size_t  n = 1, beg = n, /* current, begin, end counts */
            end = std::numeric_limits<std::streamsize>::max();
    std::string line {};

    if (argc < 2) { /* validate at least one argument given for filename */
        std::cerr << "error: insufficient arguments\nusage: program file [begin end]\n";
        return 1;
    }
    std::fstream f (argv[1]);   /* open file */

    if (!f.is_open()) { /* validate file is open for reading */
        std::cerr << "error: file open failed '" << argv[1] << "'.\n";
        return 1;
    }

    if (argc > 2)   /* if argument given for begin */
        beg = std::stol (std::string { argv[2] });  /* convert to long */
    if (argc > 3)   /* if argument given for end */
        end = std::stol (std::string { argv[3] });  /* convert to long */

    for (; n <= end && getline (f, line); n++)      /* read each line <= end */
        if (beg <= n)                               /* if count >= beg */
            std::cout << line << '\n';              /* output line */
}

Example Input File

$ cat dat/20lines.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

Example Use/Output

$ ./bin/line_range dat/20lines.txt 4 8
4
5
6
7
8

or

$ ./bin/line_range dat/20lines.txt 15
15
16
17
18
19
20

You can put it together in your function in the same way. Just gather the values in main() either by input or as parameters and pass the parameters to your function to implement the read. Look things over and let me know if you have further questions.

Additional considerations: Why is “using namespace std;” considered bad practice?

David C. Rankin
  • 69,681
  • 6
  • 44
  • 72