Wstęp do programowania/Pliki/Ćwiczenia

From Studia Informatyczne

To są zadania na pliki.

Oglądaj wskazówki i rozwiązania
Ukryj wskazówki i rozwiązania

W poniższych zadaniach zakładamy, że eof(f) implikuje eoln(f).


Zadanie 1

Napisz funkcję sprawdzającą, czy dwa pliki tekstowe o podanych nazwach mają tę samą liczbę wierszy.

Wskazówka 1

Najprostsze rozwiązanie polega na policzeniu liczby wierszy w jednym i drugim pliku, a potem porównaniu tych liczb.

Rozwiązanie 1

function LiczWiersze(f:Text):integer;
// f otwarty plik tekstowy
var c:char;
  l:integer;
begin
  l:=0;
  while not eof(f) do begin
    while not eoln(f) do
      read(f,c);
    readln(f);
    l:=l+1;
  end;
  LiczWiersze:=l;
end; // LiczWiersze

function RownoWierszowe(s1,s2:string):Boolean;
var f1,f2:Text;
begin // RownoWierszowe
  assign(f1,s1);
  assign(f2,s2);
  reset(f1);
  reset(f2);
  RownoLinijne:=LiczWiersze(f1)=LiczWiersze(f2);
  close(f1);
  close(f2);
end; // RownoWierszowe

Zaletą tego rozwiązania jest możliwość powtórnego wykorzystania funkcji LiczWiersze do innych celów, a wadą konieczność czytania obu plików, nawet gdy jeden jest bardzo mały, a drugi bardzo duży.

Wskazówka 2

Można też wczytywać po jednym wierszu z każdego pliku, aż któryś z nich się nie skończy.

Rozwiązanie 2

function RownoWierszowe2(s1,s2:string):Boolean; //s1,s2, to ścieżki dostępu do plików f1 i f2
var f1,f2:Text;
  c:char;
begin
  assign(f1,s1);
  assign(f2,s2);
  reset(f1);
  reset(f2);
  while not (eof(f1) or eof(f2)) do begin
    readln(f1); //korzystamy tu z semantyki Readln, która połyka resztę wiersza i przechodzi do nowego. 
    readln(f2);
  end;
  RownoWierszowe2:=eof(f1) and eof(f2);
  close(f1);
  close(f2);
end; // RownoWierszowe2

Czytamy po jednym wierszu z każdego pliku, aż co najmniej jeden z nich się skończy. Jeśli okazało się, że skończyły się oba naraz - mają tę samą liczbę wierszy. Jeśli skończył się tylko jeden - ten drugi musi być dłuższy.

Zadanie 2

Napisz procedurę, która skopiuje plik tekstowy f na g, modyfikując go w następujący sposób: bezpośrednio po każdym słowie należy wstawić koniec wiersza, a nowy wiersz rozpocząć od nowego słowa (spacje i puste wiersze należy pomijać).

Słowem (w tym zadaniu) nazywamy dowolny maksymalny spójny ciąg znaków niezawierający spacji i znaków końca wiersza. Nie zakładamy niczego o długości słów.

Wskazówka 1

Użyj specjalnej zmiennej do pamiętania czy przetwarzamy właśnie wnętrze słowa, czy odstęp między słowami. Co należy zrobić, jak przeczytamy odstęp, znak końca wiersza lub inny znak, w każdej z tych sytuacji?

Rozwiązanie 1

procedure Kopiuj(var f,g:Text);
type pozycja=(odstep,slowo);
var stan:pozycja;
  c:char;
begin
  reset(f);
  rewrite(g);
  while not eof(f) do begin
    stan:=odstep;
    while not eoln(f) do begin
      read(f,c);
      case stan of
        odstep: 
          if c<>' ' then begin
            write(g,c);
            stan:=slowo;
          end
        slowo:
          if c<>' ' then 
            write(g,c)
          else begin
            stan:=odstep;
            writeln(g);
          end
      end
    end
    if stan=slowo then writeln(g);
    readln(f);
  end;
  close(f);
  close(g);
end; // Kopiuj

Ta procedura ma klasyczną strukturę procedur przetwarzających pliki tekstowe: zewnętrzna pętla while wykonuje jeden obrót na każdą linię pliku f, a wewnętrzna na każdy znak wewnątrz linii.

W procedurze wykorzystana jest zmienna 'stan', która wskazuje, czy przetwarzamy w danym momencie słowo czy znaki (spacje i zmiany linii) pomiędzy słowami.

Przetwarzanie każdej nowej linii pliku f rozpoczyna się w stanie 'odstęp'. Zmiana tego stanu na 'słowo' nastąpi po przeczytaniu pierwszego znaku, który nie jest odstępem. Powtórna zmiana na 'odstęp' nastąpi po przeczytaniu znaku odstępu wewnątrz linii lub kiedy przetwarzana linia zakończy się.

Rozwiązanie 2

procedure Kopiuj(var f,g:Text);
type pozycja=(odstep,slowo);
var stan:pozycja;
  c:char;
begin
  reset(f);
  rewrite(g);
  stan:=odstep;
  while not eof(f) do begin
    if eoln(f) then begin
      readln(f);       
      if stan=slowo then begin
        writeln(g);
        stan:=odstep;
      end;
    end
    else begin // not eoln(f)
      read(f,c);
      if c=' ' then begin
        if stan=slowo then begin
          writeln(g);
          stan:=odstep
        end
      end
      else begin // c<>' '
        write(g,c);
        if stan=odstep then 
          stan:=slowo
      end
    end
  end;
  close(f);
  close(g);
end; // Kopiuj

W tym rozwiązaniu jest tylko jedna pętla while przetwarzająca poszczególne zdarzenia w pliku f. Zdarzeniem może być albo koniec linii, albo wczytanie znaku. Za każdym razem wykonywane są odpowiednie operacje na plikach i zmieniany jest stan.

Zadanie 3 (Usuwanie komentarzy)

Napisz procedurę usuwającą komentarze z pliku tekstowego zawierającego program w Pascalu.

Komentarze oznaczone są przez { ... } lub (* ... *). Należy przy tym uważać, by nie usuwać komentarzy znajdujących się wewnątrz napisów (oznaczonych przez ' ... ')

Wskazówka 1

Tak jak w zadaniu 2, procedura powinna być sterowana stanem oznaczającym tę część wiedzy zdobytej podczas przetwarzania dotychczasowego fragmentu pliku, od której zależy reakcja na wczytanie poszczególnych znaków: czy jesteśmy wewnątrz komentarza, napisu, po przeczytaniu nawiasu oswierającego itp.

Rozwiązanie 1

procedure Komentarze(wej,wyj:string);
type stany=(nic,kom1,naw_otw,kom2,kom2_gw,napis);
var f,g:Text;
  stan:stany;
  c:char;
begin
  assign(f,wej);
  assign(g,wyj);
  reset(f);
  rewrite(g);
  stan:=nic;
  while not eof(f) do begin
    if not eoln(f) then
      read(f,c);     
      case stan of
        nic: 
          case c of
            '(': stan:=naw_otw;
            '''': begin stan:=napis; write(g,c) end;
            else write(g,c);
          end;
        naw_otw:  
          case c of
            '*': stan:=kom2;
            '''': begin stan:=napis; write(g,'('''); end;
            '{': begin stan:=kom1; write(g,'('); end
            '(': write(g,'(');
            else begin stan:=nic; write(g,'(',c); end
          end;
        kom2:
          case c of
            '*': stan:=kom2_gw;
          end;    
        kom2_gw:
          case c of
            ')': stan:=nic;
            '*': stan:=kom2_gw;
            else stan:=kom2
          end;    
        kom1:
          case c of
            '}': stan:=nic
          end;    
        napis:
          case c of
            '''': begin stan:=nic; write(g,c); end
            else write(g,c);
          end
      end
    end
    else begin // eoln(f)
      readln(f);
      case stan of
        nic: writeln(g);
        naw_otw: begin stan:=nic; writeln(g,'('); end;
        kom1,kom2: // nic
        kom2_gw: stan:=kom2;
        napis: writeln(g)
      end 
    end
  end;
  close(f);
  close(g);
end

Każdy obrót pętli while w powyższej procedurze przetwarza jedno zdarzenie (znak lub koniec wiersza) z pliku wejściowego. W zależności od stanu i znaku (zdarzenia) zmieniany jest odpowiednio stan i zapisywany odpowiedni napis do pliku wyjściowego.

Można sobie wyobrazić zawartość pętli while w powyższej procedurze jako dwuwymiarową tabelkę, której kolumny indeksowane są stanami, wiersze - rodzajem zdarzenia wejściowego, a w każdej komórce jest informacja, jaki ma być nowy stan i co wypisać do pliku g.