Jednym z najczęściej zadawanych pytań przez maturzystów z informatyki jest to, czy można zmieniać zawartość plików tekstowych z danymi podczas egzaminu. W tym artykule wyjaśnimy wszystkie wątpliwości związane z tym tematem i pokażemy, jak poprawnie radzić sobie z problematycznymi przypadkami.
Istnieją dwa główne scenariusze, w których uczniowie zastanawiają się nad modyfikacją plików:
1. Błędne dane w plikuPierwszym przypadkiem są błędne dane na maturze. Warto jednak podkreślić, że zdarzają się one bardzo rzadko. Jeśli program nie działa poprawnie, problem zazwyczaj leży w kodzie, a nie w pliku z danymi.
Co jednak w sytuacji, gdy mamy stuprocentową pewność, że plik jest błędny? Wyobraźmy sobie, że w poleceniu napisano, iż plik zawiera wyłącznie liczby, a w rzeczywistości znajduje się tam również tekst w kilku miejscach.
W takiej sytuacji należy napisać program, który działa poprawnie dla poprawnych danych. Centralna Komisja Egzaminacyjna (CKE) ocenia zadania w sposób uwzględniający usunięcie błędnych linii. Teoretycznie można usunąć te błędne fragmenty do testów, ale takie sytuacje są rzadkie. Przed podjęciem takiej decyzji warto dokładnie przeanalizować program i polecenie.
Drugi przypadek jest znacznie częstszy i dotyczy obecności pustej linii w plikach tekstowych z danymi. To właśnie z tym problemem wiąże się większość pytań od uczniów.
Dlaczego ta pusta linia jest tak problematyczna? Problem ten dotyka głównie programy napisane w C++, dlatego skupimy się na tym języku programowania.
W zadaniu 4.1 z matury podstawowej 2017 mieliśmy plik "liczby.txt" z tysiącem trójek liczb. Zadanie polegało na policzeniu, w ilu wierszach pliku liczby są uporządkowane rosnąco.
Gdy zajrzymy do pliku tekstowego, widzimy rzeczywiście tysiąc wierszy z liczbami. Jednak poza tym znajduje się tam również tysiąc pierwsza linijka, która jest pusta.
Program napisany w C++ uruchamia się poprawnie i zwraca wynik 140. Na pierwszy rzut oka wszystko wygląda w porządku. Jednak gdy sprawdzamy klucz odpowiedzi, okazuje się, że poprawna odpowiedź to 139, czyli jest o 1 większa od naszego wyniku.
#include <iostream>
#include <fstream>
using namespace std;
int main() {
ifstream plik("liczby.txt");
ofstream zapis("wyniki4.txt");
int a, b, c;
int licznik = 0;
while (!plik.eof()) {
plik >> a >> b >> c;
if (a < b && b < c) {
licznik++;
}
}
zapis << "a)" << endl;
zapis << licznik << endl;
plik.close();
zapis.close();
return 0;
}
Program wczytuje kolejne trójki liczb z pliku i sprawdza, czy druga liczba jest większa od pierwszej, a trzecia większa od drugiej. Jeśli tak, zwiększa licznik o jeden. Logika programu jest poprawna - zadanie wydaje się proste i trudno tu o błąd.
Eksperyment: Po usunięciu ostatniej pustej linijki z pliku tekstowego program zwraca poprawny wynik!
W zadaniu 4.1 z czerwcowej matury 2021 mieliśmy plik z tysiącem napisów składających się z pięćdziesięciu znaków. Niektóre znaki to duże litery alfabetu, inne to cyfry. Zadanie polegało na podaniu łącznej liczby cyfr we wszystkich napisach.
Tak samo jak w poprzednim przykładzie - w pliku tekstowym widzimy tysiąc wierszy z danymi i ostatnią pustą linijkę numer tysiąc jeden.
Program w C++ korzysta z prostej funkcji sprawdzającej, czy znak jest cyfrą (sprawdza, czy kod ASCII mieści się w odpowiednim przedziale). Dla każdego znaku każdego napisu wywołuje funkcję i zwiększa zmienną liczącą cyfry. Po uruchomieniu otrzymujemy wynik 11 854.
#include <iostream>
#include <fstream>
using namespace std;
bool czyJestCyfra(char znak) {
return znak >= '0' && znak <= '9';
}
int main() {
ifstream plik("napisy.txt");
ofstream zapis("wyniki4.txt");
string linia;
int liczbaCyfr = 0;
while (!plik.eof()) {
plik >> linia;
for (int i = 0; i < linia.length(); i++) {
if (czyJestCyfra(linia[i])) {
liczbaCyfr++;
}
}
}
zapis << "a)" << endl << liczbaCyfr << endl;
plik.close();
zapis.close();
return 0;
}
I znowu niespodzianka! Poprawny wynik to 11 844 - o 10 mniej od naszego wyniku.
Analogicznie jak w poprzednim zadaniu - po usunięciu pustej linijki z pliku tekstowego wynik staje się poprawny.
Aby zrozumieć problem, przeanalizujmy jak działa proces odczytywania pliku przez C++:
Metoda eof() (end of file) nie zostanie ustawiona na true dopóki nie nastąpi próba odczytu poza końcem pliku. Gdy ostatnia linijka jest pusta, po wczytaniu przedostatniej linijki eof() nadal zwraca false. Pętla wykonuje się ponownie i próbuje wczytać pustą ostatnią linijkę, ale operacja kończy się niepowodzeniem. Dane z przedostatniej linijki pozostają w zmiennej i są przetwarzane po raz drugi, zanim eof() zostanie ustawione na true.
Odpowiedź brzmi: NIE.
Dowody z oficjalnych źródeł: 1. Informacje w najnowszych maturach:
Pod koniec poleceń czytamy: "Pamiętaj, że Twój program musi ostatecznie zadziałać na pliku liczby.txt z 3000 liczb w pierwszym wierszu." To oznacza, że na maturze program musi zadziałać dla oryginalnego pliku.
2. Informacja ze strony 7. informatora maturalnego o egzaminie maturalnym z informatyki:
Z tego punktu wprost wynika, że programy będą testowane przez egzaminatorów. Na maturze do oceny oddajemy wyłącznie program - plików tekstowych z danymi już nie.
Konsekwencje: Jeśli usuniemy pustą linię z pliku, program będzie działał poprawnie podczas naszych testów. Jednak egzaminator do testów użyje oryginalnego pliku z problematyczną pustą linią. Program zwróci błędny wynik i zgodnie z informatorem możemy nie otrzymać punktów za zadanie.
Zostawiamy warunek !plik.eof(), wczytujemy kolejne liczby za pomocą operatorów ekstrakcji plik >> a >> b >> c i przed wykonywaniem kodu dotyczącego warunku umieszczamy instrukcję if z plik.fail() oraz break; w środku:
#include <iostream>
#include <fstream>
using namespace std;
int main() {
ifstream plik("liczby.txt");
ofstream zapis("wyniki4.txt");
int a, b, c;
int licznik = 0;
while (!plik.eof()) {
plik >> a >> b >> c;
if (plik.fail()) break;
if (a < b && b < c) {
licznik++;
}
}
zapis << "a)" << endl;
zapis << licznik << endl;
plik.close();
zapis.close();
return 0;
}
Podczas próby odczytu pustej linii plik.fail() zwróci true i pętla zostanie przerwana.
Opcja 2: Przeniesienie odczytu do warunku pętliW ten sposób program będzie wczytywał dane dopóki jest coś do wczytania:
#include <iostream>
#include <fstream>
using namespace std;
bool czyJestCyfra(char znak) {
return znak >= '0' && znak <= '9';
}
int main() {
ifstream plik("napisy.txt");
ofstream zapis("wyniki4.txt");
string linia;
int liczbaCyfr = 0;
while (plik >> linia) {
for (int i = 0; i < linia.length(); i++) {
if (czyJestCyfra(linia[i])) {
liczbaCyfr++;
}
}
}
zapis << "a)" << endl << liczbaCyfr << endl;
plik.close();
zapis.close();
return 0;
}
Uwaga: Zapis plik >> linia powinien znajdować się TYLKO w warunku pętli. Umieszczenie go także wewnątrz pętli spowoduje, że w każdej iteracji będą odczytywane dwie linijki - jedna przez warunek, druga przez wywołanie wewnątrz pętli. Przez to połowa linii z pliku zostanie pominięta.
Opcja 3: Użycie funkcji getline()Ta metoda jest szczególnie przydatna, gdy pracujemy z całymi liniami tekstu zawierającymi spacje:
#include <iostream>
#include <fstream>
using namespace std;
bool czyJestCyfra(char znak) {
return znak >= '0' && znak <= '9';
}
int main() {
ifstream plik("napisy.txt");
ofstream zapis("wyniki4.txt");
string linia;
int liczbaCyfr = 0;
while (getline(plik, linia)) {
for (int i = 0; i < linia.length(); i++) {
if (czyJestCyfra(linia[i])) {
liczbaCyfr++;
}
}
}
zapis << "a)" << endl << liczbaCyfr << endl;
plik.close();
zapis.close();
return 0;
}
Pamiętajmy, żeby nie dodawać getline() także wewnątrz pętli - ma być tylko w warunku.
Na maturze z informatyki nie można modyfikować plików z danymi. Zgodnie z wymaganiami CKE program powinien działać dla oryginalnego pliku tekstowego z danymi, ponieważ egzaminatorzy będą testować rozwiązania na oryginalnych plikach, a być może także na dodatkowych zestawach testowych.
Kluczem do sukcesu jest napisanie kodu, który poprawnie radzi sobie z problematycznymi przypadkami, takimi jak puste linie na końcu pliku. Przedstawione powyżej metody pozwalają na stworzenie programów odpornych na tego typu problemy, co zapewnia poprawne działanie zarówno podczas własnych testów, jak i podczas oficjalnej oceny przez egzaminatorów.