Jak wczytać 200 ostatnich linii z pliku bez użycia biblioteki string?

0

Cześć!

Mam pewien problem, muszę wczytać 200 ostatnich linii pliku csv żeby na ich podstawie zrobić wykres. Normalnie nie miał bym z tym problemu, ale niestety nie mogę do tego użyć biblioteki string z c++ (Nie mogę bo takie ograniczenia narzuca zadanie). Niestety nie mam bladego pojęcia jak się za to zabrać bez użycia getline.

Tutaj chciałbym dodać że plik wygląda tak:

2023-11-27,15.65,44.545,43.65,55.08,983164
2023-11-28,63.69,44.08,43.64,44.02,1967329
1

Najprościej to chyba wczytać wszystkie linijki z pliku iterując getline() po znakach, chunkach lub linijkach, ale zapisywać tylko 200 ostatnich. Ewentualnie możesz zapisać wszystkie, i tylko wyświetlić 200 ostatnich.

Alternatywny pomysł jaki mam to zrobić seek na koniec pliku, i czytać właśnie z końca: tutaj jest przykład: https://stackoverflow.com/questions/11876290/c-fastest-way-to-read-only-last-line-of-text-file

0
grzank 4 napisał(a):

Tutaj chciałbym dodać że plik wygląda tak:

2023-11-27,15.65,44.545,43.65,55.08,983164
2023-11-28,63.69,44.08,43.64,44.02,1967329

Czy to tylko przypadek, czy każda linijka jest tej samej długości? Jeśli to drugie, to możesz łatwo przeskoczyć pierwsze 200 * (długość linii) bajtów i czytać resztę.

0
Althorion napisał(a):
grzank 4 napisał(a):

Tutaj chciałbym dodać że plik wygląda tak:

2023-11-27,15.65,44.545,43.65,55.08,983164
2023-11-28,63.69,44.08,43.64,44.02,1967329

Czy to tylko przypadek, czy każda linijka jest tej samej długości? Jeśli to drugie, to możesz łatwo przeskoczyć pierwsze 200 * (długość linii) bajtów i czytać resztę.

True, tylko to też pewnie zależy od końcówek linii (LF vs CRLF), więc pewnie Pan sprytny wykładowca mu tego nie zaliczy.

0

@Riddle Specyfikacja mówi https://datatracker.ietf.org/doc/html/rfc4180#section-2 CRLF więc jak nie zaliczy to mu ją pokazać.

1

I weź tu odgadnij co nauczyciel uznaje za poprawne rozwiązanie :D
jeżeli jednym kryterium jest brak getline
to wplecenie w kod tail -n 200 plik tez mogło by przejść

0

to wplecenie w kod tail -n 200 plik tez mogło by przejść

Nie do końca wiem jak takie coś wpleść w kod. Z drugiej strony, zastosowanie czegoś takiego nie było by błędem. Jeśli działa i nie wykorzystuje biblioteki string to takie rozwiązanie przejdzie.

0

@grzank 4 To jest polecenie powłoki. Musisz wywołać je za pomocą np. funkcji exec- https://man7.org/linux/man-pages/man3/exec.3.html, osobiście bym jeszcze przed tym fork- https://man7.org/linux/man-pages/man2/fork.2.html zrobił, jeśli już się decydujesz na to

0

Tak najprymitywniej to przelicz wszystkie nowe linie w pliku. Jak większe od 200 to odejmujesz od ich liczby 200 i następnie czytasz jeszcze raz plik aż dojdziesz do tej szukanej nowej linii.
Na koniec wypisujesz wszystko po prostu.

1

wyciac tailem z lini polecen ostatnie 200 do innego pliku, wczytac caly i tyle. po co wymyslac kolo od nowa

3

Jak nie możesz <string> z C++, to użyj scanf z <cstdio> - czy to też zakazane?

0
char buffer[256];
fgets(buffer,256,file);

Poruszanie się po pliku w trybie do odczytu <-artykuł ze strony https://cpp0x.pl/kursy/Kurs-C++/Poziom-4/Poruszanie-sie-po-pliku-w-trybie-do-odczytu/476

0
#include <stdio.h>

#define EXTLINES 200
#define TEXTBUF 20480

char buffer[TEXTBUF];

int main (int argc, char **argv) {
        FILE *fp;
        char c;
        unsigned int lines;
        char *bufpos;
        unsigned long int buflevel;
        char *shsrc, *shdest;
        char *pr;

        if (argc < 2) {
                puts ("No filename specified.");
                return 1;
                }
        if ((fp = fopen (argv[1], "r")) == NULL) {
                puts ("Input file error.");
                return 2;
                }

        bufpos = buffer;
        lines = 0;
        while ((c = fgetc (fp)) != EOF) {
                if (c == '\n') {
                        if (lines >= EXTLINES) {
                                shsrc = buffer;
                                while (*shsrc++ != '\n');
                                buflevel -= shsrc - buffer;

                                shdest = buffer;
                                while (shsrc < bufpos)
                                        *shdest++ = *shsrc++;
                                bufpos = shdest;
                                }
                        else
                                ++lines;
                        }

                if (buflevel < TEXTBUF) {
                        *bufpos++ = c;
                        ++buflevel;
                        }
                }

        fclose (fp);

        pr = buffer;
        while (pr < bufpos)
                putchar (*pr++);

        return 0;
        }
0
// Zakladam, ze plik CSV jako znak koñca linii uzywa CRLF (carriage return + line feed)
// Windows 10 Pro 22H2 x64 & Debian GNU/Linux 11 (bullseye)
// Code::Blocks 20.03! gcc (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 8.1.0 & 9.3.0

#include <iostream>
#include <cstdio>

using namespace std;

int main()
{
    FILE *file=fopen("wyniki.csv","r");
    char buffer[256];
    int count=0,last_records=200;
    fseek(file,0,SEEK_SET);
    for(;fgets(buffer,256,file);)++count;
    fseek(file,0,SEEK_SET);
    for(int i=0;i<count-last_records;fgets(buffer,256,file),++i);
    for(;fgets(buffer,256,file);)cout<<buffer;
    fclose(file);
    return 0;
}
0

Jeżeli chcesz to zrobić dobrze, to trzeba było by na początku programu ustawić się na koniec pliku, jeżeli masz uzyskać ostatnie 200 linii to nie ma potrzeby wczytywać całego pliku np 2GB.

Czyli na początku:

fseek(file,0,SEEK_END);

potem lecisz od końca wczytując do bufora i sprawdzasz czy znalazło się tam 200 razy znak nowej linii.

0

@sherman Nawet jeśli w testach działa, to twój kod jest wadliwy.

  1. Nie sprawdasz, czy udało się otworzyć plik. Wystarczy, że zwyczajnie nie będzie istniał i program może wywalić system.
  2. Zmienna last_records powinna być makrem, a nie zmienną. Chyba że ilość linii miała by być dynamiczna w przyszłości, ale to jest jednorazowe zadanie.
  3. Co do makr, liczba 256 stanowiąca długość bufora powtarza się 4 razy...
  4. W liniach 16 i 19 wystarczyłoby while zamiast i tak równoważnego for.
  5. fseek(file,0,SEEK_SET) to krócej rewind (file).
  6. Odejmowanie w warunku pętli w linii 18 wykonywane będzie za każdym jej przebiegiem, a dane się nie zmieniają. Lepiej odjąć raz na początku i przechować wynik w zmiennej. Jakby to było wyrażenie stałe to co innego, ale w tym przypadku nie jest i nie może być, bo count jest ustalane w czasie działania programu.
  7. Pamiętaj, że funkcja fgets przerwie odczyt linii również w przypadku zapełnienia bufora, co policzysz jako koniec linii. Możesz sprawdzać, czy ostatni znak (przed terminatorem lub końcem tablicy) jest znakiem nowego wiersza, i jak nie to nie liczyć linii.
  8. Ale będzie to bardzo wolne. Najlepiej po prostu czytaj pojedyncze znaki zwracając uwagę na znak nowej linii zamiast wczytywać całe linie do tablicy. Tak uzyskasz implementację bez żadnych ograniczeń (poza tym, że algorytm jest wieloprzebiegowy).
0

Nie sprawdasz, czy udało się otworzyć plik. Wystarczy, że zwyczajnie nie będzie istniał i program może wywalić system.

# ifdef _UNISTD_H
#  include "sys/unistd.h"
# else
#  include "unistd.h"
# endif

char arg[] = "wyniki.csv";
if(access(arg,F_OK)==-1) {
printf("file not exist\n");
} else {
FILE *file=fopen(arg,"r");

or

    FILE* file = fopen("wyniki.csv", "r");
    if (file == nullptr)
    {
        perror("fopen failed");
        return 1;
    }
    constexpr int BUFFER_SIZE = 10;
    char buffer[BUFFER_SIZE];

  1. Zmienna last_records powinna być makrem, a nie zmienną...
  2. Co do makr,...

program jest w C++ nie w Ansi C zamiast "makr" lepiej użyć constexpr

W liniach 16 i 19 wystarczyłoby while zamiast i tak równoważnego for

oba rodzaje pętli mogą być stosowane zamiennie, tak mogłem, tak chciałem, tak napisałem,
ale owszem pętla "while" częściej się wybiera, gdy liczba iteracji nie jest znana z góry

Pamiętaj, że funkcja fgets przerwie odczyt linii również w przypadku zapełnienia bufora

można temu zapobiec sprawdzając strlen(buffer) -1 != '\n', ustawić wskaźnik SEEK_CUR i wskrzesić odczyt linii lub jeszcze lepiej użyć funkcji fread()
w przypadku dużych plików, program, który będzie dane przetwarzał w blokach, będzie działał szybciej niż ten, który będzie zliczał każdy znak i szukał końca wiersza.

co do innych punktów, mogę się zgodzić

\

0
#include <iostream>
#include <cstdio>
#include <cassert>
using namespace std;

const size_t ROW_COUNT=3;

typedef struct Data
{
	unsigned year,month,day;
	double a,b,c,d;
	unsigned value;
} Data;

int readline(FILE *fd,Data *d)
{
	return fscanf(fd," %u-%u-%u, %lf, %lf, %lf, %lf, %u",&d->year,&d->month,&d->day,&d->a,&d->b,&d->c,&d->d,&d->value);
}

size_t readlines(FILE *fd,Data data[])
{
	Data cicle[ROW_COUNT];
	size_t rowcount=0;
	for(;;++rowcount)
	{
		Data *d=cicle+rowcount%ROW_COUNT;
		int ret=readline(fd,d);
		if(feof(fd)) break;
		assert(ret==8);
	}
	size_t count=min(ROW_COUNT,rowcount),source=rowcount-count;
	for(size_t i=0;i<count;++i) data[i]=cicle[(source+i)%ROW_COUNT];
	return count;
}

int main()
{
	Data data[ROW_COUNT];
	FILE *fd=fopen("error_0214.data","r");
	assert(fd!=NULL);
	readlines(fd,data);
	fclose(fd);
	for(size_t i=0;i<ROW_COUNT;++i) printf("%u-%u-%u, %lf, %lf, %lf, %lf, %u\n",data[i].year,data[i].month,data[i].day,data[i].a,data[i].b,data[i].c,data[i].d,data[i].value);
	return 0;
}
0

Ktoś, kto daje specyfikację na poziomie negatywnym "bez czegoś" to z profesjonalizmem niewiele ma wspólnego. Dało by się specyfikować "jedynie z użyciem ... i ... " (chyba że masz kolega po swojemu spłycił zadanie)

Jest ogólną zasadą w życiu, niczego nie da się skutecznie określić na zasadzie negatywnej, chcę jabłko a mówię "nie gruszka"

0

Co prawda nie piszę w C++, ale jakbym miał to zrobić najprościej, to pewnie coś w stylu:
std::system("tail -n 200 my_file > temp_file"); potem otworzyć ten plik i wypisać czy co tam ewentualnie się chce sobie z nim zrobić std::cout << std::ifstream("temp_file").rdbuf(); a na końcu usunąć plik tymczasowy i std::system("rm temp_file");

0
PaulGilbert napisał(a):

Co prawda nie piszę w C++, ale jakbym miał to zrobić najprościej, to pewnie coś w stylu:
std::system("tail -n 200 my_file > temp_file"); potem otworzyć ten plik i wypisać czy co tam ewentualnie się chce sobie z nim zrobić std::cout << std::ifstream("temp_file").rdbuf(); a na końcu usunąć plik tymczasowy i std::system("rm temp_file");

Sądzę, że jak ktoś dawał zadanie algorytmiczne w C++ to nie chodziło mu o czytanie ze zrozumieniem man tail (choć i to chwalebna umiejętność, z akcentem na "ze zrozumieniem") i funkcja system() gdzie umiejętności C++ nie trzeba żadnych

Ale to może infa na KUL albo w Toruniu, i to w nadgodzinach prowadzi jakiś organista (co nie znaczy, że przekreślam dobrego organistę) ...

1

mmap i masz cały plik w tablicy znakow. Szukasz w niej od końca 200*newline.

0

@AnyKtokolwiek Jak już to lepiej popen zamiast pliku tymczasowego, tylko że to funkcja niestandardowa.

0

Szkoda, że nie podajesz czego możesz użyć. Jeśli cstdio, to fgets wczytuje linię. Jeśli masz gwarancję poprawności i braku spacji to może nawet scanf. Najprościej byłoby statycznie zaalokować miejsce na 200 rekordów i zrobić sobie bufor cykliczny i czytać jak leci. Czyli masz tablicę i offset pierwszego rekordu. Żeby pobrać/zapisać nty elemet, po prostu dodajesz i robisz modulo rozmiar. Kiedy dodajesz nowy element, po prostu zapisujesz w miejscu pierwszego elementu i przesuwasz offset pierwszego elementu. Jeśli to nie jest zadowalające to chyba trzeba byłoby napisac funkcję, która wczytuje linie od końca pliku. Pewnie najwygodniej byłoby to zrobić jakoś na nmapie, bo robić seek co kilka bajtów trochę bez sensu.

Tu masz o buforach cyklicznych
https://en.wikipedia.org/wiki/Circular_buffer

0
#include <iostream>
#include <fstream>
#include <vector>
#include <cerrno>

using namespace std;

int main()
{
    std::ifstream file("wyniki.csv", std::ifstream::in);
    if (!file.good()) {
        std::cerr << "file not exist" << std::endl;
        return -1;
    }
    std::vector<char> vec_char;
    char c = file.get();
    int line_count = 0;
    constexpr int last_lines = 200;

    while(file.good())
    {
        if(c == '\n'){line_count += 1;}
        vec_char.emplace_back(c);
        c = file.get();
    }
    file.close();

    int read_lines = line_count - last_lines;
    line_count = 0;
    for(auto const &el: vec_char) {
        if(el == '\n')line_count += 1;
        if(line_count > read_lines)cout << el;
    }
    cout << '\n';
    return 0;
}

jeszcze jeden, żeby nie iterować znak po znaku, tylko zapisać cały blok danych

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

using namespace std;

int main()
{
    std::ifstream file("wyniki.csv", std::ifstream::in);
    if (!file.good()) {
        std::cerr << "file not exist" << std::endl;
        return -1;
    }
    file.seekg(0, std::ios::end);
    size_t size_file = file.tellg();
    file.seekg(0, std::ios::beg);
    std::vector<char> vec_char(size_file);
    file.read(vec_char.data(),size_file);
    file.close();

    size_t line_count = 0;
    constexpr size_t last_lines = 200;

    for(auto const &el: vec_char) {
        if(el == '\n')line_count += 1;
    }
    size_t read_lines = line_count - last_lines;
    line_count = 0;
    for(auto const &el: vec_char) {
        if(el == '\n')line_count += 1;
        if(line_count > read_lines)cout << el;
    }
    cout << '\n';
    return 0;
}

ANSI C with malloc & free, fread

// Windows 10 Pro 22H2 x64 & Debian GNU/Linux 11 (bullseye)
// Code::Blocks 20.03! gcc (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 8.1.0 & 9.3.0
// gcc.exe -Wall -g -std=c99 -c main.c -o main.o -lm

# ifdef _UNISTD_H
#  include "sys/unistd.h"
# else
#  include "unistd.h"
# endif
#define S (char)
#define LF sf
#define NL 0x0A
#define I int
#define say(x) printf("%c",x)
#define __ I main()
#define ll 0xC8
#define loop() for(int x=L;++x<LF;)
#define L log10(0.1)
#define A (S(NL))
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

__ {
    char arg[]="wyniki.csv";
    if(access(arg,F_OK)==-1){printf("file not exist\n");return -1;}
    FILE *f=fopen(arg,"r");
    fseek(f,0,SEEK_END);
    long sf=ftell(f);
    rewind(f);
    char *tch=(char*)malloc(sf*sizeof(char));
    fread(tch,sizeof(char),sf,f);
    fclose(f);
    size_t lc=0;
    loop()if(tch[x]==A)++lc;
    size_t rl=lc-ll;
    lc = 0;
    loop(){if(tch[x]==A)++lc;if(lc>rl)say(tch[x]);}
    say(A);
    free(tch);
    return 0;
}


1 użytkowników online, w tym zalogowanych: 0, gości: 1