Решение задачи про определение программой победителя из текстового файла

Разберем задачу №2 из 18 урока про победителя:

У вас есть текстовый файл с информацией об итогах соревнований (каждая строка имеет формат: имя + произвольное число пробелов + балл 1 + произвольное число пробелов + балл 2):

Вася    16     485
Коля    17   555
Юра     18   61
..........

-- где:
слева указаны имена участников, а справа число баллов.
Первое число в каждой строке -- штрафные баллы набранные участниками, а второе число -- "положительные баллы", то есть если у нас есть строка:

Vasya    N      M

где N -- число шрафных баллов, а M - число баллов положительных, то итоговая оценка Васи Z вычисляется как разность:

Z=M−N.

Текстовый файл для компьютера -это файл из байтов, состоящий из специальных символов.

Что нужно обязательно понять, работая с файлами:

  • Файлы это бесконечный список знчений одного и того же типа
  • Все элементы файла пронумерованы и начальный элемент имеет нулевой номер
  • В любой момент доступен только один элемент на который ссылается указатель переменной (дескриптор)
  • Чтение и запись в файл происходит поэлементно
  • Ввод-вывод для текстовых файлов подчиняется тем же правилам, что и для типизированных файлов; однако имеется несколько важных особенностей:
  • Нельзя одновременно производить операции и ввода, и вывода. Это означает, что после открытия текстового файла процедурой reset возможно только чтение информации из файла, а после процедуры rewrite - только запись в файл.
  • Обмены с текстовыми файлами всегда являются строго последовательными, то есть после чтения из файла элемента с порядковым номером N следующая операция чтения даст элемент с номером N+1. Иными словами, прямой доступ к любому элементу текстового файла невозможен; для текстовых файлов не допускаются вызовы Seek, FilePos, FileSize.
  • Под чтением файла понимают ввод данных из внешнего файла, находящегося на диске, в оперативную память компьютера. Данные файла становятся доступными программе. Внешний файл, из которого читаются данные, часто называют входным файлом.
  • Базовой техникой обменов с текстовыми файлами является посимвольный ввод-вывод. При этом производится чтение или запись всех символов, как информационных, так и специальных.

Ссылка

Для того чтобы "общаться" программе с файлом, нужно ввести переменную через которое это общение будет происходит. Введем переменную 'f'.
Тип переменной 'f' - 'text'. В паскале эта переменная является служебной, также, как real,integer. Пример:

Var
  TextFile : text; 

Создадим текстовый файл на компьютере с расширением '.txt'. Определим его путь на диске для программы. И свяжем нашу переменную 'f' с данным файлом:

Var
  f: text;
Begin
  assign(f, 'd:\tp7\bin\file.txt'); {Полный путь к файлу }
  reset(f);{Открыть на чтение]
  . . .
End.

Процедура 'assign' связывает переменную 'f' с текстовым файлом 'file.text'. Процедура assign не должна использоваться для открытого файла. Если при вызове процедуры assign в качестве имени файла задается пустая строка: assign(f), то после обращения к reset(f) переменная f будет связана со стандартным файлом ввода, а после обращения к rewrite(f) – со стандартным файлом вывода.
Процедура

 reset(f)

- где f файловая переменная, открывает файл на чтение.

Покажем простую программу, выполняющую чтение некоторого текста. Эта программа выводит на экран последовательность кодов символов, составляющих файл text.txt.

Program TextFile1;
Var
  f : text; {Читаемый текст}
  S : char; {Очередной прочитанный символ}
Begin
  assign(f, 'text.txt'); {Связываем файл text.txt с переменной f}
  reset(f); {Открываем файл text.txt для чтения}
  while not Eof(f) do {Пока не достигнут конец файла, делай...}
    begin
      read(f, S); {Читаем из файла очередной символ в переменную S}
      writeln(S:2, ord(S):4); {Выводим символ и его код на экран}
    end;
  close(f); {Закрываем файл}
  readln
End.
 

Ccылка

Функция ParamStr возвращает, один из параметров командной строки используемый при вызове текущей программы. Параметр ParamIndex определяет, какой параметр будет возвращен:

0: Диск/путь/исполняемая программа 1: Возвращает 1-ый параметр
2: Возвращает 2-ой параметр...
Ccылка

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

  • C:\prg.exe
  • myfile.txt
  • file2.cmd
  • "D:\My photos\1.jpg"
  • D:\temp\1.tmp

Здесь

  • paramstr(0)='C:\prg.exe'
  • paramstr(1)='myfile.txt'
  • paramstr(2)='file2.cmd'
  • paramstr(3)='D:\My photos\1.jpg'
  • paramstr(4)='D:\temp\1.tmp'

Cсылка 1
Ccылка 2

Что такое функция IOResult и директивы

{$I-}

и

{$I+}

?
Пользователь может настроить сисетму вывода ошибок или другими словами настроить компилятор под решаемую задачу. Для этого нужно в комментариях cо специальным символом '$'

{$}

написать специальные символы 'I-' и 'I+' и в итоге получим

 {$I-} 

и

 {$I+}

Это позволит поменять работу компилятора и процесс вывода ошибок будет изменен.
IOResult - функция, которая контроллирует ошибки ввода и вывода в файл.С помощью директивы '{$I-}' запрещается стандартная обработка ошибок ввода-вывода, с помощью '{$I+}' - разрешается. Если стандартная обработка ошибок ввода-вывода запрещена, то прерывания программы не происходит, а результат попытки выполнения команды (в Вашем случае - открытие файла на чтение) заносится в специальную переменную IOResult. Если значение этой переменной равно 0, то ошибки при выполнении команды не было, в противном случае в эту переменную записывается условный номер ошибки. Переменную IOResult специально объявлять не нужно.
Ссылка
Если процедура ввода/вывода возвращает ненулевой результат ввода/вывода, когда переключатель /I включен, то программа завершается и выводится сообщение об ошибке времени выполнения. Если переключатель /I выключен, то вы должны использовать функцию IOResult для проверки ошибок ввода/вывода.Если IOResult не равен нулю, значит, ошибка была.

Значение IOResult	Описание ошибки
0	                         Ошибок не было
2	                         Файл не найден
3	                         Путь к файлу не найден
4	                         Слишком много открытых файлов
100	                         Ошибка чтения с диска
101	                         Ошибка записи на диск
102	                          Файл не связан с файловой переменной
103	                          Файл не открыт
104	                          Файл не открыт для ввода
105	                          Файл не открыт для вывода
106	                          Неправильный числовой формат

Ccылка 1
Ссылка 2
Ссылка 3

Что такое символы и каким кодам они соответствуют таблица кодов символов ASCII.
Rаждому символу алфавита и спецсимволу ставится в соотвествии код в шестнадцатеричной и десятичной системе исчисления - это и есть таблица ASCII. Например, char(10)+char(13) - это коды специальных символов. Это означает что мы теперь можем понимать когда каретка вернулась на начало строки новой строки. CR - это аббревиатура для "carriage return" (возврат каретки) - управляющий символ CR возвращал печатающую головку ("carriage") к нулевой колонке без движения бумаги. Например, мы можем вывести слово 'ujuykyki' с пробелом и переносом строки, так, что курсор переведется программой на следующую строку и курсор будет в самом начале строки:

writeln('ujuykyki',char(10)+char(13),'trhtrhtrhtrh'); 

Таблица кодов ASCII
Ссылка 2

Как решить данную задачу?

Cоздадим файл. Напишем четыре строки: имя участника, количество штрафных очков, количество баллов. Обратите внимание, что когда вы будете сохранять файл обязательно передвиньте курсор на следующий символ, расположенный сразу за последним символом в последней строке иначе программа выведет все пустые строки:

Sereja 2 534
Petya   200 1000
Yura 14   2000
Aleksandr 23 3000

Если сохраните с налияием пустых строк (пять строк пустых), то программа выведет все пустые строки и в консоле вы увидите, что курсор располагается на пятой строке в самом начале :

Sereja 2 534
Petya   200 1000
Yura 14   2000
Aleksandr 23 3000
"перевод на первую  строки"
"перевод на вторую строку"
"перевод на третью строку"
"перевод  на четвертую строку"
"курсор"

Как нам выделить победителя и еще посчитать разницу между баллами и штрафами?

Теперь давайте разберемся, как выводятся символы в строках.

program project1;
  var
    s,path:string; // что будем записывать из файла в переменную s, адрес расположения файла
    f:text;  // переменная которая будет связано с файлом

  begin
    path:='C:\Users\k\Desktop\ProgLazarus\input.txt';
    assign (f, path);   //  файл и переменную f обьединяем
    reset (f);    // открываем файл для чтения через переменную f
     //   WriteLn('Put k failu programmy: ');
    // WriteLn(ParamStr(0));
    readln(f,s);
    writeln(s);
    

Если написать readln(f,s) и вывести ее writeln(s) то выведется одна строка, если это сделать еще то выведется следующая строка.
То есть получается, что оператор readln(f,s) записывает всю строку в переменную s до переноса строки.

Если мы хотим вывести первую букву первой строки, то введём:

  readln(f,s);
  writeln(s[1]); // подсчет идет с 1 символа
  

Если мы вручную хотим вывести, например три строки, и на каждой строго определенное количество символов, то можем сделать следующее:

   readln(f,s);    // запись всей первой строки
     writeln(s[1]); // вывод 1 символа первой строки
      readln(f,s);// запись всей второй строки
       write(s[1]); //вывод 1,2,3 
       write(s[2]);     //  символа  
       write(s[3]);    //       второй строки  
       readln(f,s);// запись всей первой строки
       write(s[1]) // вывод 1 символа третьей строки
 
 

При этом дескриптор вывести просто так на экран нельзя! Если вы напишите writeln (f) или writeln (f[1]) это приведет к ошибке.
Теперь мы понимаем механизм записи в дескриптор f строк из файла в переменную s.
Попробуем вручную реализовать вывод всех строк через цикл.

 program project1;
  var
    i:integer;
    s,path:string; // что будем записвать из файла в переменную s, адрес расположения файла
    f:text;  // переменная которая будет связано с файлом
  begin
    path:='C:\Users\k\Desktop\ProgLazarus\input.txt';
    assign (f, path);   //  файл и переменную f обьединяем
    reset (f);    // открываем файл для чтения через переменную f
    for i:=1 to 4 do begin
    readln(f,s);
     writeln(s);
      end;
    readln();
  end.
  

Выведем полностью все наши строки.

Теперь давайте попробуем выделить из каждой строки строку, штраф и число и решить задачу:

В данной задаче можно обратить внимание на условие переключения между выполняемыми частями кода. Пользователь ввёл произвольное количество пробелов, значит мы должны это учесть и поставить условие: если предыдущий символ пробел ' ' а последующий буква разного регистра или число то наш счетчик увеличивает значение. А именно это значение и является уникальным для определенного условия.

При использовании большого количества переменных, нужно понимать, что паскаль не распознает регистр! Если вы пишите в процедуре I а в функции i лазарус это воспримет как одну переменную, которая дублируется.

Данную задачу можно оптимизировать. А именно применить для определения типов переменных запись record. Некоторые блоки кода можно представить в виде функций или процедур. Можно также, использовать другие условия проверки наличия пробелов и перехода указателя на символ.

Решение представлено с использованием процедуры::

program project1;
 var
   i,k,j,C,E,max,o,Code:integer;
   Imya,Kolichestvoballov,shtraf : array [1..4] of string;
   w,path:string; // что будем записвать из файла в переменную s, адрес расположения файла
   f:text;  // переменная которая будет связано с файлом
  Procedure ISKarray (var s:string);
    begin
      for k:=1 to length(s) do // пробегаем наш указатель по всем символам первой строки
        begin
          if  ( ( (s[k]>='a') and (s[k]<='z') ) or ( (s[k]>='A') and (s[k]<='Z') )) then
            Imya[i]:=Imya[i]+s[k];  // заполняем массив имен, если набор символов состоит из букв разного регистра записываем в массив строк Imya[i]
          if (((s[k]=' ') and   ((s[k+1]>='0') and (s[k+1] <='9')) ))   then
            j:=j+1;
          if j=2 then
            if ((s[k]>='0') and (s[k] <='9')) then
              Shtraf[i]:=Shtraf[i]+s[k];// заполняем массив штрафов, если набор символов состоит из цифр записываем в массив цифр Shtraf[i]
          if j=3 then
            if ((s[k]>='0') and (s[k] <='9')) then
              Kolichestvoballov[i]:=Kolichestvoballov[i]+s[k]; { заполняем массив "количество баллов", если набор символов состоит из цифр записываем в массив цифр Kolichestvoballov[i]  }
        end;
    end;
  begin
    max:=1;
    path:='C:\Users\k\Desktop\ProgLazarus\input.txt'; // файл программа получает через абсолютный или относительный адрес
    assign (f, path); // связываем дескриптор f с текстовым файлом input.txt
    reset (f); // открываем файл для чтения
    for i:=1 to 4 do
      begin
        readln(f,w); // записываем первую строку из файла в дескриптор f
        j:=1;
        ISKarray (w);
        writeln( Imya[i],' ',Shtraf[i],' ',Kolichestvoballov[i]); // для демонстрации написанного в файлы выводим в консоль построчно данные из массивов
        Val(Kolichestvoballov[i], C, Code); // переводим в число числовой литерал  из массива Kolichestvoballov[i] в переменную C
        Val(shtraf[i], E, Code); // переводим в число числовой литерал  из массива shtraf[i] в переменную E
        if ((C-E)>=max) then
          begin
            max:=C-E; // записываем наибольшую разницу в переменную max
            o:=i;
            C:=0;
            E:=0;
          end;
      end;
    writeln('Imya pobeditelya: ',Imya[o]);
    writeln('Kolichtstvo ballov pobeditelya : ', max );
    readln();
  end.