Чтение последних n строк из файла в c/С++

Я видел много сообщений, но не нашел ничего подобного. Я получаю неправильный вывод:

ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ......  // may be this is EOF character

Переход в бесконечный цикл.

Мой алгоритм:

  • Перейдите к концу файла.
  • уменьшить позицию указателя на 1 и прочитать символ персонаж.
  • выйти, если мы найдем наши 10 строк или дойдем до начала файла.
  • теперь я буду сканировать полный файл до EOF и печатать их//не реализован в коде.

код:

#include<iostream>
#include<stdio.h>
#include<conio.h>
#include<stdlib.h>
#include<string.h>

using namespace std;
int main()
{
    FILE *f1=fopen("input.txt","r");
    FILE *f2=fopen("output.txt","w");
    int i,j,pos;
        int count=0;
        char ch;
        int begin=ftell(f1);
        // GO TO END OF FILE
        fseek(f1,0,SEEK_END);
        int end = ftell(f1);
        pos=ftell(f1);

        while(count<10)
        {
            pos=ftell(f1);
            // FILE IS LESS THAN 10 LINES
            if(pos<begin)
                break;
            ch=fgetc(f1);
            if(ch=='\n')
                count++;
            fputc(ch,f2);
            fseek(f1,pos-1,end);
        }
    return 0;
}

UPD 1:

измененный код: теперь он имеет только 1 ошибку - если на входе есть строки типа

3enil
2enil
1enil

it prints 10 lines only

line1
line2
line3ÿine1
line2
line3ÿine1
line2
line3ÿine1
line2
line3ÿine1
line2

PS:
1. работа над окнами в блокноте ++

  • Это не домашнее задание

  • Также я хочу сделать это, не используя больше памяти или использования STL.

  • Я тренируюсь, чтобы улучшить свои базовые знания, поэтому, пожалуйста, не сообщайте о каких-либо функциях (например, tail -5 tc.)

пожалуйста, помогите улучшить мой код.

Ответ 1

С вашим кодом возникает ряд проблем. Большинство важно то, что вы никогда не проверяете, что любая из функций удалось. И сохранение результатов ftell в int не является тоже очень хорошая идея. Тогда существует тест pos < begin; это может произойти только в случае ошибки. И тот факт, что вы помещаете результаты fgetc в char (что приводит к в случае потери информации). И тот факт, что первый читал вас do находится в конце файла, поэтому произойдет сбой (и как только поток войдет в состояние ошибки, оно остается там). И тот факт, что вы не можете надежно выполнить арифметику по значениям, возвращаемым ftell (за исключением под Unix), если файл был открыт в текстовом режиме.

О, и нет "символа EOF"; 'ÿ' - совершенно character (0xFF в латинском-1). Как только вы присвоите возвращаемое значение от fgetc до char, вы потеряли возможность проверить конец файла.

Я мог бы добавить, что чтение назад по одному символу за раз крайне неэффективен. Обычным решением было бы выделить достаточно большой буфер, затем подсчитайте '\n' в нем.

EDIT:

Просто немного кода, чтобы дать идею:

std::string
getLastLines( std::string const& filename, int lineCount )
{
    size_t const granularity = 100 * lineCount;
    std::ifstream source( filename.c_str(), std::ios_base::binary );
    source.seekg( 0, std::ios_base::end );
    size_t size = static_cast<size_t>( source.tellg() );
    std::vector<char> buffer;
    int newlineCount = 0;
    while ( source 
            && buffer.size() != size
            && newlineCount < lineCount ) {
        buffer.resize( std::min( buffer.size() + granularity, size ) );
        source.seekg( -static_cast<std::streamoff>( buffer.size() ),
                      std::ios_base::end );
        source.read( buffer.data(), buffer.size() );
        newlineCount = std::count( buffer.begin(), buffer.end(), '\n');
    }
    std::vector<char>::iterator start = buffer.begin();
    while ( newlineCount > lineCount ) {
        start = std::find( start, buffer.end(), '\n' ) + 1;
        -- newlineCount;
    }
    std::vector<char>::iterator end = remove( start, buffer.end(), '\r' );
    return std::string( start, end );
}

Это немного слабое в обработке ошибок; в частности, вы вероятно, хотят отличить между невозможностью открыть файл и любые другие ошибки. (Никаких других ошибок не должно быть, но вы никогда не знаете.)

Кроме того, это чисто Windows, и он предполагает, что фактическое файл содержит чистый текст и не содержит '\r', который не являются частью CRLF. (Для Unix просто снимите последняя строка.)

Ответ 2

Комментарии в коде

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    FILE *in, *out;
    int count = 0;
    long int pos;
    char s[100];

    in = fopen("input.txt", "r");
    /* always check return of fopen */
    if (in == NULL) {
        perror("fopen");
        exit(EXIT_FAILURE);
    }
    out = fopen("output.txt", "w");
    if (out == NULL) {
        perror("fopen");
        exit(EXIT_FAILURE);
    }
    fseek(in, 0, SEEK_END);
    pos = ftell(in);
    /* Don't write each char on output.txt, just search for '\n' */
    while (pos) {
        fseek(in, --pos, SEEK_SET); /* seek from begin */
        if (fgetc(in) == '\n') {
            if (count++ == 10) break;
        }
    }
    /* Write line by line, is faster than fputc for each char */
    while (fgets(s, sizeof(s), in) != NULL) {
        fprintf(out, "%s", s);
    }
    fclose(in);
    fclose(out);
    return 0;
}

Ответ 3

Это может быть сделано с использованием кругового массива очень эффективно. Дополнительный буфер не требуется.

void printlast_n_lines(char* fileName, int n){

    const int k = n;
    ifstream file(fileName);
    string l[k];
    int size = 0 ;

    while(file.good()){
        getline(file, l[size%k]); //this is just circular array
        cout << l[size%k] << '\n';
        size++;
    }

    //start of circular array & size of it 
    int start = size > k ? (size%k) : 0 ; //this get the start of last k lines 
    int count = min(k, size); // no of lines to print

    for(int i = 0; i< count ; i++){
        cout << l[(start+i)%k] << '\n' ; // start from in between and print from start due to remainder till all counts are covered
    }
}

Пожалуйста, оставьте отзыв.

Ответ 4

Я считаю, вы используете fseek неправильно. Проверьте man fseek на Google.

Попробуйте следующее:

fseek(f1, -2, SEEK_CUR);
//1 to neutrialize change from fgect
//and 1 to move backward

Также вы должны установить положение в начале последнего элемента:

fseek(f1, -1, SEEK_END).

Вам не нужна переменная end.

Вы должны проверить возвращаемые значения всех функций (fgetc, fseek и ftell). Это хорошая практика. Я не знаю, будет ли этот код работать с пустыми файлами или аналогичным.

Ответ 5

int end = ftell(f1);
pos=ftell(f1);

это указывает вам последнюю точку в файле, поэтому EOF. Когда вы читаете, вы получаете ошибку EOF, и ppointer хочет переместить 1 пробел вперед...

Итак, я рекомендую уменьшить текущую позицию на единицу. Или поставьте fseek (f1, -2, SEEK_CUR) в начале цикла while, чтобы исправить предмет на 1 пункт и перейти на 1 пункт назад...

Ответ 6

Использование: fseek(f1,-2,SEEK_CUR); назад

Я пишу этот код, он может работать, вы можете попробовать:

#include "stdio.h"

int main()
{
        int count = 0;
        char * fileName = "count.c";
        char * outFileName = "out11.txt";
        FILE * fpIn;
        FILE * fpOut;
        if((fpIn = fopen(fileName,"r")) == NULL )
                printf(" file %s open error\n",fileName);
        if((fpOut = fopen(outFileName,"w")) == NULL )
                printf(" file %s open error\n",outFileName);
        fseek(fpIn,0,SEEK_END);
        while(count < 10)
        {
                fseek(fpIn,-2,SEEK_CUR);
                if(ftell(fpIn)<0L)
                        break;
                char now = fgetc(fpIn);
                printf("%c",now);
                fputc(now,fpOut);
                if(now == '\n')
                        ++count;
        }
        fclose(fpIn);
        fclose(fpOut);
}

Ответ 7

Я бы использовал два потока для печати последних n строк файла: Это выполняется в O (строки) времени выполнения и O (строки).

#include<bits/stdc++.h>
using namespace std;

int main(){
  // read last n lines of a file
  ifstream f("file.in");
  ifstream g("file.in");

  // move f stream n lines down.
  int n;
  cin >> n;
  string line;
  for(int i=0; i<k; ++i) getline(f,line);

  // move f and g stream at the same pace.
  for(; getline(f,line); ){
    getline(g, line);
  }

  // g now has to go the last n lines.
  for(; getline(g,line); )
    cout << line << endl;
}

Решение с O (строками) runtime и O (N) пространство использует очередь:

ifstream fin("file.in");
int k;
cin >> k;
queue<string> Q;
string line;
for(; getline(fin, line); ){
  if(Q.size() == k){
    Q.pop();
  }
  Q.push(line);
}
while(!Q.empty()){
  cout << Q.front() << endl;
  Q.pop();
}

Ответ 8

Вот решение в C++.

#include <iostream>                                                             
#include <string>                                                               
#include <exception>                                                            
#include <cstdlib>                                                              

int main(int argc, char *argv[])                                                
{                                                                               
    auto& file = std::cin;                                                      

    int n = 5;                                                                  
    if (argc > 1) {                                                             
        try {                                                                   
            n = std::stoi(argv[1]);                                             
        } catch (std::exception& e) {                                           
            std::cout << "Error: argument must be an int" << std::endl;         
            std::exit(EXIT_FAILURE);                                            
        }                                                                       
    }                                                                           

    file.seekg(0, file.end);                                                    

    n = n + 1; // Add one so the loop stops at the newline above                
    while (file.tellg() != 0 && n) {                                            
        file.seekg(-1, file.cur);                                               
        if (file.peek() == '\n')                                                
            n--;                                                                
    }                                                                           

    if (file.peek() == '\n') // If we stop in the middle we will be at a newline
        file.seekg(1, file.cur);                                                

    std::string line;                                                           
    while (std::getline(file, line))                                            
        std::cout << line << std::endl;                                         

    std::exit(EXIT_SUCCESS);                                                    
} 

Телосложение:

$ g++ <SOURCE_NAME> -o last_n_lines

Бежать:

$ ./last_n_lines 10 < <SOME_FILE>