Чтение из текстовых файлов : проблемы и тонкости

Книга адресована школьникам средних и старших классов, желающим испытать себя в «олимпийских схватках». Может быть полезна студентам-первокурсникам и преподавателям информатики.

Модераторы: Oleg_D, Модераторы

Чтение из текстовых файлов : проблемы и тонкости

Сообщение artischev » 28.12.2012 21:52:53

Хочу предложить небольшие усовершенствования программы P_31_1 из 31-ой главы редакции 12.4 от 2012-11-23.

В функции ReadFam цикл чтения символов фамилии организован посредством оператора цикла с предусловием:
Код: Выделить всё
while not Eoln(InFile) and (Ord(sym)>32) do begin
  S:= S+sym;
  Read(InFile, sym);
  end;


Очевидно, программа должна корректно обрабатывать строки с нулевым количеством оценок. При обработке таких строк предложенный автором вариант "съедает" последние буквы фамилий учеников.

Предлагаю после цикла добавить строку:
Код: Выделить всё
while not Eoln(InFile) and (Ord(sym)>32) do begin
  S:= S+sym;
  Read(InFile, sym);
  end;
if EoLn(InFile) then S:=S+sym;

Или вовсе заменить этот фрагмент кода на:
Код: Выделить всё
repeat
  S:=S+sym;
  if EoLn(InFile) then break;
  read(InFile, sym);
  until Ord(sym)<=32;


Как лучше - не знаю, рассудите.

Кроме того, строку:
Код: Выделить всё
Writeln(OutFile, 'Ученик не аттестован');
заменить на:
Код: Выделить всё
Writeln(OutFile, Counter:3, Fam:18, 'Ученик не аттестован');
что позволит видеть в выходном файле фамилии неаттестованных учеников.
artischev
новенький
 
Сообщения: 12
Зарегистрирован: 27.12.2012 11:17:22

Re: Редактирование и вычитка книги

Сообщение Oleg_D » 29.12.2012 09:46:02

artischev писал(а):Хочу предложить небольшие усовершенствования программы P_31_1

Принимаю, большое спасибо! Вариант с REPEAT мне больше нравится, но придётся и блок-схему исправить.
Ошибка не замечалась, поскольку без оценок и фамилия не печаталась, -- там тоже поправлю.
Oleg_D
постоялец
 
Сообщения: 390
Зарегистрирован: 09.05.2011 11:28:36

Re: Редактирование и вычитка книги

Сообщение bormant » 29.12.2012 11:11:55

P_31_1.PAS
Код: Выделить всё
function ReadFam: string;
...
repeat Read(InFile, sym); until Ord(sym)>32;
Наличие в конце файла пустой строки (что не такая уж и редкость) "повесит" программу в цикле until 26>32.


Код: Выделить всё
while Length(Fam) < 12 do Fam:= Fam + Char(32);
Поскольку ниже всё равно обсуждается форматный вывод, можно не модифицировать сами данные (Fam), а пользоваться возможностями write(ln):
Код: Выделить всё
writeln(..., Fam, '':12-length(Fam), ...)

Writeln(OutFile, Counter:3, '':5, Fam, '':12-length(Fam), Cnt:8, Sum:14, Rating:11:1);
Последний раз редактировалось bormant 29.12.2012 11:28:07, всего редактировалось 2 раз(а).
Аватара пользователя
bormant
постоялец
 
Сообщения: 407
Зарегистрирован: 21.03.2012 11:26:01

Re: Редактирование и вычитка книги

Сообщение Oleg_D » 29.12.2012 11:26:20

bormant писал(а):Пара интересных моментов.
P_31_1.PAS
Код: Выделить всё
function ReadFam: string
...
  repeat Read(InFile, sym); until Ord(sym)>32;
Пустая строка (или несколько) в конце входного файла "повесит" программу внутри цикла в состоянии until 26>32.

Я в курсе этой слабости, тут надо бы так:
Код: Выделить всё
  repeat
    if Eof(InFile) then Break;
    Read(InFile, sym);
  until Ord(sym)>32;

Но не хотелось усложнять программу дополнительными проверками, -- это всё же обучающий пример для новичков.
Oleg_D
постоялец
 
Сообщения: 390
Зарегистрирован: 09.05.2011 11:28:36

Re: Редактирование и вычитка книги

Сообщение bormant » 29.12.2012 11:40:46

Oleg_D писал(а):Я в курсе этой слабости, тут надо бы так:
Код: Выделить всё
  repeat
    if Eof(InFile) then Break;
    Read(InFile, sym);
  until Ord(sym)>32;

Но не хотелось усложнять программу дополнительными проверками, -- это всё же обучающий пример для новичков.

Достаточно и эквивалентного
Код: Выделить всё
  repeat Read(InFile, sym) until (Ord(sym)>32) or Eof(InFile);
Но на самом-то деле нужен не break, a exit, ведь дальше-то читать смысла нет:
Код: Выделить всё
begin
  ReadFam := '';
  repeat
    if eof(InFile) then exit;
    read(InFile, sym);
  until ord(sym) > 32;
  s := sym;
  ...
  ReadFam := s;
end;
Хотя, это уже больше по-С-шному с их вечным return. С другой стороны, при eof=true и eoln тоже true, поэтому в последующий цикл входа не будет, и вариант, приведённый в качестве эквивалентного, вполне подходит.

Добавлено спустя 17 минут:
То есть, в сухом остатке,
Код: Выделить всё
function ReadFam: string;
var
  sym: char;
  s: string;
begin
  s := ''; { очистка накопителя строки }
  { пропуск символов до первой буквы }
  repeat Read(InFile, sym); until (Ord(sym)>32) or Eof(InFile);
  { чтение последующих символов }
  while not Eoln(InFile) and (Ord(sym)>32) do begin
    s := s+sym;
    Read(InFile, sym);
  end;
  if Ord(sym)>32 then s := s+sym;
  ReadFam := s;
end;
Аватара пользователя
bormant
постоялец
 
Сообщения: 407
Зарегистрирован: 21.03.2012 11:26:01

Re: Редактирование и вычитка книги

Сообщение Oleg_D » 29.12.2012 12:30:04

bormant писал(а):if eof(InFile) then exit;

Кажется, что Exit я в книжке нигде не упоминал. На практике пользуюсь часто, но в учебнике для новичков воздержался, поскольку эта штука требует некоторого опыта, привычки.
В целом ваши предложения верны, но я ещё подумаю, стоит ли эти усложнения давать в книжке. Новичку важно ущучить основную идею, а усложнения могут помешать этому, программа и так довольно накручена.
Oleg_D
постоялец
 
Сообщения: 390
Зарегистрирован: 09.05.2011 11:28:36

Re: Редактирование и вычитка книги

Сообщение bormant » 29.12.2012 13:14:14

Oleg_D писал(а):подумаю, стоит ли эти усложнения давать в книжке. Новичку важно ущучить основную идею, а усложнения могут помешать этому, программа и так довольно накручена.

Про усложнения -- это действительно очень важно. Для наглядности приведу разницу между книжным вариантом и работающим (слева: 12.4, справа -- модифицированный, "|" -- изменение в строке, ">" -- новая строка):
Код: Выделить всё
function ReadFam: string;                              function ReadFam: string;
var                                                    var
  sym: char;                                             sym: char;
  s: string;                                             s: string;
begin                                                  begin
  s := ''; { очистка накопителя строки }                 s := ''; { очистка накопителя строки }
  { чтение символа до первой буквы }                     { чтение символа до первой буквы }
  repeat Read(InFile, sym); until Ord(sym)>32;       |   repeat Read(InFile, sym); until (Ord(sym)>32) or Eof(InFile);
  { чтение последующих символов }                        { чтение последующих символов }
  while not Eoln(InFile) and (Ord(sym)>32) do begin      while not Eoln(InFile) and (Ord(sym)>32) do begin
    s := s+sym;                                            s := s+sym;
    Read(InFile, sym);                                     Read(InFile, sym);
  end;                                                   end;
                                                     >   if Ord(sym)>32 then s := s+sym;
  ReadFam := s;                                          ReadFam := s;
end;                                                   end;
Аватара пользователя
bormant
постоялец
 
Сообщения: 407
Зарегистрирован: 21.03.2012 11:26:01

Re: Редактирование и вычитка книги

Сообщение Oleg_D » 29.12.2012 13:34:11

Сейчас я так исправил:
Код: Выделить всё
function ReadFam: string;
var sym: char;
    s : string;
begin
  s:=''; { очистка накопителя строки }
  { чтение первого символа }
  repeat Read(InFile, sym) until Ord(sym)>32;
  { чтение последующих символов }
  repeat
    s:= s+sym;
    if Eoln(InFile) then Break;
    Read(InFile, sym);
  until Ord(sym) <= 32;
  ReadFam:= s;
end;

Можно ещё одну строку здесь поправить:
Код: Выделить всё
  repeat Read(InFile, sym) until (Ord(sym)>32) or Eof(InFile);

Тут вопрос баланса между корректностью с одной стороны и понятностью для новичка с другой.
Если народ (хоть кто-то) проголосует "за", то исправлю и это.

Добавлено спустя 2 минуты 31 секунду:
Хотя можно перенести этот момент в секцию "А слабо?". Пусть сами смекнут, как тут поправить. :)
Oleg_D
постоялец
 
Сообщения: 390
Зарегистрирован: 09.05.2011 11:28:36

Re: Редактирование и вычитка книги

Сообщение bormant » 29.12.2012 13:47:17

Для наглядности пронумерую строки:
Код: Выделить всё
     1   function ReadFam: string;
     2   var sym: char;
     3       s : string;
     4   begin
     5     s:=''; { очистка накопителя строки }
     6     { чтение первого символа }
     7     repeat Read(InFile, sym) until Ord(sym)>32;
     8     { чтение последующих символов }
     9     repeat
    10       s:= s+sym;
    11       if Eoln(InFile) then Break;
    12       Read(InFile, sym);
    13     until Ord(sym) <= 32;
    14     ReadFam:= s;
    15   end;
1) Если в (7) не добавить условие or Eof(InFile), то при пустых строках в конце файла будет зависание.
2) Если в (7) добавить or Eof(InFile), то станет возможен выход из цикла по Eof(InFile), в этом случае переменная sym будет иметь значение #26.
Если теперь в (9) не используется предусловие, s получит значение #26, его же получит ReadFam в (14), что, мягко говоря, не совсем то, что ожидалось.

Мой вариант этих недостатков лишён. Где-то ошибаюсь?
Аватара пользователя
bormant
постоялец
 
Сообщения: 407
Зарегистрирован: 21.03.2012 11:26:01

Re: Редактирование и вычитка книги

Сообщение Oleg_D » 29.12.2012 19:47:42

bormant писал(а):Мой вариант этих недостатков лишён. Где-то ошибаюсь?

Всё, что вы предлагаете, понятно, но дело в другом.
Вот мой исправленный вариант:
Код: Выделить всё
    {----- Функция чтения фамилии -----}

function ReadFam: string;
var sym: char;
    s : string;
begin
  s:=''; { очистка накопителя строки }
  { пропуск возможных пустых символов }
  repeat Read(InFile, sym) until (Ord(sym)>32) or Eof(InFile);
  { чтение последующих символов }
  repeat
    if Ord(sym) > 32 then s:= s+sym;  { добавляем, если буква }
    if Eoln(InFile) then Break;       { прервать, если конец строки }
    Read(InFile, sym);                { читаем следующий символ }
  until Ord(sym) <= 32;               { вплоть до первого пробела }
  ReadFam:= s;
end;

    {----- Процедура обработки строки -----}

procedure HandleString;
var N  : integer;    { оценка, прочитанная из файла }
    Cnt: integer;    { количество оценок }
    Sum: integer;    { сумма баллов }
    Rating: Real;    { средний балл }
    Fam: string;     { фамилия }

begin
    Fam:= ReadFam; { читаем фамилию }
    if Length(Fam)>0 then begin  { если фамилия не пуста, обрабатываем }
      { для выравнивания столбцов добавляем пробелы }
      while Length(Fam) < 12 do Fam:= Fam + ' ';
      Sum:=0; Cnt:=0;   { очищаем накопитель и счетчик оценок }
      While not Eoln(InFile) do begin  { пока не конец строки }
          Read(InFile, N);      { читаем оценку в переменную N }
          Sum:= Sum+N;          { накапливаем сумму баллов }
          Cnt:= Cnt+1;          { наращиваем счетчик оценок }
      end;
      if Cnt>0 then begin       { если оценки в четверти были }
         Rating:= Sum / Cnt;    { вычисляем и печатаем ср. балл }
         Writeln(OutFile,Counter:3, Fam:18, Cnt:8, Sum:14, Rating:11:1);
      end else begin            { а если оценок не было }
         Writeln(OutFile, Counter:3, Fam:18,' : Ученик не аттестован');
      end;
    end;
end;

Всё это работает в Дельфаке, но не работает в FP и BP, когда после фамилии нет оценок.
Причина в этом фрагменте внутри HandleString:
Код: Выделить всё
      While not Eoln(InFile) do begin  { пока не конец строки ***** здесь функция не видит конца строки после пробелов }
          Read(InFile, N);      { читаем оценку в переменную N ***** тут run-time error }
          Sum:= Sum+N;          { накапливаем сумму баллов }
          Cnt:= Cnt+1;          { наращиваем счетчик оценок }
      end;

Какие будут предложения?
Oleg_D
постоялец
 
Сообщения: 390
Зарегистрирован: 09.05.2011 11:28:36

Ответ на задание 31-А

Сообщение artischev » 29.12.2012 21:53:23

Я ещё попридираюсь к мелочам. :)
Предложенное автором в ответах решение задачи 31-А добавляет в конец выходного файла лишний ноль, делая ненужный проход цикла. Предлагаю свой вариант, свободный от этого недостатка:
Код: Выделить всё
var
  N, K: integer;
  F1, F2: Text;
begin
  Assign(F1,'Police.in'); Reset(F1);
  Assign(F2,'Police.out'); Rewrite(F2);
  K:=0;
  Read(F1, N);                                         //Добавлено
  while not Eof(F1) do begin
    Write(F2, N, ' ');                                 //Чтение и запись
    Read(F1, N);                                       //поменяны местами
    K:= (K+1) mod 3;
    if K=0 then Writeln(F2);
    end;
  Close(F1); Close(F2);
end.
artischev
новенький
 
Сообщения: 12
Зарегистрирован: 27.12.2012 11:17:22

Re: Редактирование и вычитка книги

Сообщение bormant » 30.12.2012 11:15:17

Oleg_D писал(а):Какие будут предложения?

Проверьте приведённый ниже пример в Delphi/BP/TP, если не сложно... В Free Pascal работает.
Код: Выделить всё
const
  Space = char(32);

var
  Counter: integer;
  InFile: Text;

function ReadFam: string;
var
  s: string;
  sym: char;
begin
  repeat Кead(InFile, sym) until (sym > Space) or Eof(InFile);
  if sym > Space then begin
    s := sym;
    while not Eoln(InFile) do begin
      Read(InFile, sym);
      if sym > Space then s := s + sym else break;
    end;
    ReadFam := s;
  end else
    ReadFam := '';
end;

procedure HandleLine;
var
  Fam: string;
  N, Cnt, Sum: integer;
begin
  Fam := ReadFam;
  Sum := 0; Cnt := 0;
  while not SeekEOLn(InFile) do begin
    Read(InFile, N);
    Sum := Sum + N; Cnt := Cnt + 1;
  end;
  Write(Counter: 3, '':4, Fam,'':20-length(Fam));
  if Cnt > 0 then WriteLn(Cnt:6, Sum:14, Sum/Cnt:11:1)
  else WriteLn('  Ученик не аттестован');
  ReadLn(InFile);
  Counter := Counter + 1;
end;

begin
  Assign(InFile, 'journal.in'); Reset(InFile);
  WriteLn('Номер  Фамилия               Количество    Сумма     Средний');
  WriteLn('                             оценок        баллов    балл');
  Counter := 1;
  while not SeekEOF(InFile) do HandleLine;
  Close(InFile);
end.
journal.n
Код: Выделить всё
Akulova     3 5 4     
   Bykov       5 5 5 5
Voronov     4 5 5 4
Galkina     3 4 3
Krokodilkin 4 3
Dumb             
   

   
Тестовый прогон:
Код: Выделить всё
Номер  Фамилия               Количество    Сумма     Средний
                             оценок        баллов    балл
  1    Akulova                  3            12        4.0
  2    Bykov                    4            20        5.0
  3    Voronov                  4            18        4.5
  4    Galkina                  3            10        3.3
  5    Krokodilkin              2             7        3.5
  6    Dumb                  Ученик не аттестован
Аватара пользователя
bormant
постоялец
 
Сообщения: 407
Зарегистрирован: 21.03.2012 11:26:01

Re: Редактирование и вычитка книги

Сообщение Oleg_D » 30.12.2012 13:08:55

bormant писал(а):Проверьте приведённый ниже пример в Delphi/BP/TP, если не сложно...

Проверил, работает. Для себя я тоже применил бы SeekEoln и SeekEof, но в книжке я не касаюсь этих функций, поэтому оставил следующий вариант. Он работает, когда в конце строк с фамилиями нет пробелов, а в конце файла пробелы и пустые строки допускаются.

Код: Выделить всё
{$A+,B-,D+,E+,F-,G+,I+,L+,N+,O-,P+,Q+,R-,S-,T-,V-,X+,Y+}

{ P_31_1.pas }

    {--- Глобальные переменные ---}

var InFile, OutFile : text;  { входной и выходной файлы }
    Counter: integer;        { счетчик строк в файле }

    {----- Функция чтения фамилии -----}

function ReadFam: string;
var sym: char;
    s : string;
begin
  s:=''; { очистка накопителя строки }
  { пропуск возможных пустых символов }
  repeat
    Read(InFile, sym)
  until (Ord(sym)>32) or Eof(InFile);
  { чтение последующих символов }
  repeat
    if Ord(sym) > 32 then s:= s+sym;  { добавляем неуправляющий символ }
    if Eoln(InFile) then Break;       { прервать, если конец строки }
    Read(InFile, sym);                { читаем следующий символ }
  until Ord(sym) <= 32;               { вплоть до первого пробела }
  ReadFam:= s;
end;

    {----- Процедура обработки строки -----}

procedure HandleString;
var N  : integer;    { оценка, прочитанная из файла }
    Cnt: integer;    { количество оценок }
    Sum: integer;    { сумма баллов }
    Rating: Real;    { средний балл }
    Fam: string;     { фамилия }

begin
    Fam:= ReadFam; { читаем фамилию }
    if Length(Fam)>0 then begin  { если фамилия не пуста, обрабатываем }
      { для выравнивания столбцов добавляем пробелы }
      while Length(Fam) < 12 do Fam:= Fam + ' ';
      Sum:=0; Cnt:=0;   { очищаем накопитель и счетчик оценок }
      While not Eoln(InFile) do begin  { пока не конец строки }
          Read(InFile, N);      { читаем оценку в переменную N }
          Sum:= Sum+N;          { накапливаем сумму баллов }
          Cnt:= Cnt+1;          { наращиваем счетчик оценок }
      end;
      if Cnt>0 then begin         { если оценки в четверти были }
         Rating:= Sum / Cnt;      { вычисляем и печатаем ср. балл }
         Writeln(OutFile,Counter:3, Fam:18, Cnt:8, Sum:14, Rating:11:1);
      end else begin            { а если оценок не было }
         Writeln(OutFile, Counter:3, Fam:18,' : Ученик не аттестован');
      end;
    end;
end;

begin
    Counter:= 0;    { обнуляем счетчик строк }
    { открываем входной файл }
    Assign(InFile,'P_31_1.in');     Reset(InFile);
    { создаем выходной файл }
    Assign(OutFile,'P_31_1.out');   Rewrite(OutFile);
    { записывем "шапку" таблицы }
    Writeln(OutFile, 'Номер    Фамилия       Количество      Сумма      Средний');
    Writeln(OutFile, 'ученика                  оценок        баллов      балл');
    { пока не конец входного файла... }
    while not Eof(InFile) do begin
          Counter:= Counter+1; { наращиваем счетчик строк }
          HandleString;        { обрабатываем строку }
          Readln(InFile);      { сброс признака конца строки }
    end;
    { закрываем оба файла }
    Close(InFile);  Close(OutFile);
    Write('OK'); Readln;
end.


P_31_1.in (с пустыми строками и пробелами в конце)
Код: Выделить всё
  A   3 5 4
  BB
CCC     5 5 5 5
 



P_31_1.out
Код: Выделить всё
Номер    Фамилия       Количество      Сумма      Средний
ученика                  оценок        баллов      балл
  1      A                  3            12        4.0
  2      BB           : Ученик не аттестован
  3      CCC                4            20        5.0


Добавлено спустя 56 минут 5 секунд:
Re: Ответ на задание 31-А
artischev писал(а):Я ещё попридираюсь к мелочам. :)

Да пожалуйста, буду рад :)
Но с поправкой вашей не соглашусь.
Во-первых, там у вас ошибка: первый вызов Read(F1, N) без проверки на конец файла (а он может быть и пустым).
Во-вторых, лишний ноль появляется из-за пустых строк в конце файла, он не появляется, когда их нет (в конце последней строки находится конец файла).
Вот один из правильных вариантов:
Код: Выделить всё
program a_31_a;
var N, K: integer;
    F1, F2: Text;
begin
  Assign(F1,'Police.in'); Reset(F1);
  Assign(F2,'Police.out'); Rewrite(F2);
  K:=0;
  while not Eof(F1) do begin
    if {Seek}Eoln(F1)
      then Readln(F1)
      else begin
        Read(F1, N);
        Write(F2, N, ' ');
        K:= (K+1) mod 3;  { K= 0, 1, 2, 0, 1, 2 ...}
        if K=0 then Writeln(F2);
      end
  end;
  Close(F1); Close(F2);
  Write('OK'); Readln
end.
Oleg_D
постоялец
 
Сообщения: 390
Зарегистрирован: 09.05.2011 11:28:36

Re: Редактирование и вычитка книги

Сообщение bormant » 30.12.2012 16:45:06

И еще один вариант для p_31_1, на этот раз построчное чтение. Ограничения: строки длиной до 255 символов.
Код: Выделить всё
const
  Space = char(32);

var
  Counter: integer;
  InFile: Text;

function GetWord(const s: string; var r: string; var p: integer): string;
var
  i: integer;
begin
  while (p <= Length(s)) and (s[p] <= Space) do p := p + 1;
  i := p;
  while (p <= Length(s)) and (s[p] > Space) do p := p + 1;
  r := Сopy(s, i, p - i);
  GetWord := r;
end;

procedure HandleLine;
var
  Fam, s, w: string;
  N, Cnt, Sum, p: integer;
begin
  ReadLn(InFile, s);
  p := 1;
  GetWord(s, Fam, p);
  if Fam > '' then begin
    Sum := 0; Cnt := 0;
    while GetWord(s, w, p) > '' do begin
      Val(w, N); Sum := Sum + N; Cnt := Cnt + 1;
    end;
    Write(Counter: 5, '':2, Fam, '': 20-Length(Fam));
    if Cnt > 0 then WriteLn(Cnt:6, Sum:14, Sum/Cnt:11:1)
    else WriteLn('  Ученик не аттестован');
    Counter := Counter + 1;
  end;
end;

begin
  Assign(InFile, 'journal.in'); Reset(InFile);
  WriteLn('Номер  Фамилия               Количество    Сумма     Средний');
  WriteLn('                             оценок        баллов    балл');
  Counter := 1;
  while not Eof(InFile) do HandleLine;
  Close(InFile);
end.
Аватара пользователя
bormant
постоялец
 
Сообщения: 407
Зарегистрирован: 21.03.2012 11:26:01

Re: Редактирование и вычитка книги

Сообщение Oleg_D » 30.12.2012 18:04:22

bormant писал(а):И еще один вариант для p_31_1, на этот раз построчное чтение.

Спасибо, всё это мне понятно, но такое решение не отвечает той цели, что поставлена в главе 31.
Обработке строк посвящена глава 44.
Ваши решения, bormant, с головой выдают профессионала, но мне надо учитывать текущий уровень подготовки читателя, здесь задачи подчинены целям соответствующих глав, а не сами по себе. А за активность вашу -- огромное спасибо.
Думаю, что будет полезно чуть-чуть сказать в 31-й главе о функциях SeekEoln и SeekEof.
Последний раз редактировалось Oleg_D 30.12.2012 18:06:15, всего редактировалось 1 раз.
Причина: Опечатка
Oleg_D
постоялец
 
Сообщения: 390
Зарегистрирован: 09.05.2011 11:28:36

След.

Вернуться в Книга "Песни о Паскале"

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 3

Рейтинг@Mail.ru