Глава 37 Ввод и вывод множеств



Мы узнали о множествах и приспособили их к директорской задаче. Чтобы покончить с нею доделаем ещё пару пустяков: организуем ввод и вывод множеств. Для ввода-вывода строк и простых типов данных годятся процедуры Read[ln] и Write[ln]. Но сейчас все не так просто, – эти процедуры не способны работать, ни с множествами, ни с другими сложными типами данных. Однако ж «нормальные герои всегда идут в обход», – пойдем так и на этот раз.

Вывод множества в текстовый файл

Начнем с вывода числового множества на экран (или в файл, – что одно и то же). Так мы получим средство для последующей проверки вводимых множеств.

Раз уж процедура Writeln не печатает множество одним махом, выведем каждый его элемент по отдельности – ведь это обычные числа или символы. Проверяя все возможные элементы множества, будем печатать лишь те, что входят в него – в этом основная идея. Напомню, что для такой проверки подходит операция IN. Дополнив её циклом со счетчиком, соорудим несложную процедуру распечатки числового множества. Вот она вместе с программой для её проверки.


{ P_37_1 – вывод множества в файл }


type TSet = set of 1..255;     { объявление типа «множество» }


    {----- Процедура вывода множества в файл -----}

procedure WriteSet(var aFile: text; const aSet : TSet);

var k : integer;

begin

    for k:=1 to 255 do     { цикл по всем элементам множества}

    if k in aSet     { если K входит в множество }

    then Write(aFile, k:4);     { печатаем в строке }

    Writeln(aFile); { по окончании – переход на следующую строку }

end;

    {----- Программа для проверки процедуры WriteSet -----}

var S1 : TSet;     F: text;

begin

    Assign(F, ''); Rewrite(F); { связываем файл с экраном! }

    S1:= [3, 10, 25];     { значение множества }

    WriteSet(F, S1);         { печатаем }

    Readln;

    Close(F);

end.


В первой строке объявлен тип данных TSet, он может содержать целые числа от 1 до 255. Процедура распечатки WriteSet принимает по ссылке два параметра: файловую переменную и множество, которое надо распечатать. Внутри процедуры работает цикл FOR, перебирающий все возможные элементы множества. Те из них, что содержатся в нём, печатаются в текущей строке. По завершении цикла оператор Writeln переводит позицию записи на следующую строку файла.

Обратите внимание: множество передано в процедуру по ссылке CONST. Передача в процедуры множеств, строк и других сложных типов данных по ссылкам CONST и VAR — это обычная практика. Так повышается скорость работы программ и уменьшается объём памяти, занимаемый параметрами.

Теперь взгляните на оператор Assign(F,''), который назначает файловой переменной пустое имя файла. Так файловая переменная связывается с экраном дисплея (при выводе данных), либо с клавиатурой (при вводе). А когда вам потребуется вывести результаты в дисковый файл, достаточно будет задать нужное имя файла, не меняя процедуры WriteSet (этот прием – подстановка пустого имени – не работает в Pascal ABCNet).

Примечание. В современные версии Паскаля (Delphi) для обработки множеств введён вариант цикла FOR-IN-DO. С ним распечатка множества станет ещё проще:


    for k in aSet do Write(aFile, k:4);


Ввод множества из текстового файла.

Разобравшись с распечаткой множества, перейдем к вводу его из файла. Есть соображения на этот счет? Здесь пригодится опыт чтения чисел из строки текстового файла, – вспомните обработку классного журнала. Добавить число к множеству мы тоже умеем: для этого надо объединить его с множеством, состоящим из добавляемого числа. На этих идеях построена процедура ввода, показанная ниже вместе с тестирующей её программой.


{ P_37_2 – ввод и вывод числового множества }

type TSet = set of 1..255; { объявление типа «множество» }

    {----- Процедура чтения множества из файла -----}

procedure ReadSet(var aFile: text; var aSet : TSet);

var k : integer;

begin

      aSet:= [];

    While not Eoln(aFile) do begin { пока не конец строки }

    Read(aFile, K);     { читаем очередное число }

    aSet:= aSet+[K];     { и добавляем к множеству }

    end;     

    Readln (aFile);     { переход на следующую строку }

end;

    {----- Процедура распечатки множества в файл -----}

procedure WriteSet(var aFile: text; const aSet : TSet);

var k : integer;

begin

    for k:=1 to 255 do     { цикл по всем элементам множества}

    if k in aSet     { если входит в множество }

    then Write(aFile, k:4); { печатаем в строке }

    Writeln(aFile);     { по окончании переход на следующую строку }

end;


    {----- Программа для проверки процедуры ввода -----}

var S1 : TSet;     F, D: text;

begin

    Assign(F, ''); Rewrite(F); { вывод на экран }

    Assign(D, ''); Reset(D); { ввод с клавиатуры }

    S1:= [];     { перед вводом опустошаем множество }

    ReadSet(D, S1);     { вводим множество из файла }

    WriteSet(F, S1); Readln; { распечатаем для проверки }

    Close(F); Close(D);

end.


Полагаю, что комментарии поясняют все. Обязательно проверьте работу этой программы. Учтите, что вводить данные вы будете с клавиатуры: напечатайте в одной строке несколько чисел, разделяя их пробелами, а затем нажмите клавишу Enter.

Директорская задача, первый вариант

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


    R:= [1..250] – (S1 + S2 + S3);


Теперь добавим ввод и вывод множеств. Чтобы не занимать место повторами показанных ранее процедур, я представлю решение в целом.


{ P_37_3 – решение директорской задачи, вариант 1 }


const CMax = 20;     { мощность множества, реально 250 }

type TSet = set of 1..CMax; { объявление типа «множество» }


procedure WriteSet(var aFile: text; const aSet : TSet);

{ взять из P_37_2 }


procedure ReadSet(var aFile: text; var aSet : TSet);

{ взять из P_37_2 }


var R, S1, S2, S3 : TSet;

    FileIn, FileOut: text;


begin {----- Главная программа -----}

    { Открытие входного файла }

    Assign(FileIn, 'P_37_3.in'); Reset(FileIn);

    { Создание выходного файла }

    Assign(FileOut, 'P_37_3.out'); Rewrite(FileOut);

    { Ввод множеств из входного файла }

    S1:=[]; ReadSet(FileIn, S1);

    S2:=[]; ReadSet(FileIn, S2);

    S3:=[]; ReadSet(FileIn, S3);

    R:= [1..CMax] – (S1+S2+S3); { Решение }

    WriteSet(FileOut, R);     { Вывод решения в выходной файл }

    Close(FileIn); Close(FileOut);

end.


Для ввода и вывода множеств используем дисковые файлы, поэтому оператор Readln в конце программы не нужен. Для облегчения проверки я уменьшил число учеников – константу CMax – с 250 до 20. При тестировании программы входной файл содержал следующие строки.


2 11 4 13

9 17 12 11 3 5 18

14 2 13 15 20


А в выходной файл попали следующие числа.


1 6 7 8 10 16 19


Легко убедиться в том, что никто из этих учеников не состоит в кружках.

Директорская задача, второй вариант

Итак, задача решена, но директор не вполне доволен. Сейчас возможности программы ограничены тремя кружками и двадцатью учениками. При изменении этих данных надо менять и программу, – мы избавимся от этого недостатка.

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


20

2 11 4 13

9 17 12 11 3 5 18

14 2 13 15 20


Во-вторых, отведем для участников кружков не три, а лишь одну переменную типа множество. Затем, по мере чтения строк файла, будем накапливать в этой переменной всех, кто состоит в кружках. Цикл чтения завершится по достижении конца входного файла. Вот и все изменения, посмотрите на второй вариант (процедуры ввода и вывода множеств только обозначены).


{ P_37_4 – решение директорской задачи, вариант 2 }


type TSet = set of byte; { объявление типа «множество» }


{ Здесь надо поместить процедуры ввода и вывода множеств }

procedure WriteSet(var aFile: text; const aSet : TSet);

{ взять из P_37_2 }


procedure ReadSet(var aFile: text; var aSet : TSet);

{ взять из P_37_2 }

var R, S : TSet;

    FileIn, FileOut: text;

    N: integer; { общее число учеников }

begin

    Assign(FileIn, ' P_37_4.in'); Reset(FileIn);

    Assign(FileOut, ' P_37_4,out'); Rewrite(FileOut);

    Readln(FileIn, N);     { читаем общее число учеников }

    S:= []; { очищаем перед вводом }

    { пока не конец файла, объединяем участников всех кружков }

    while not Eof (FileIn) do ReadSet(FileIn, S);

    R:= [1..N] – S;     { Решение }

    WriteSet(FileOut, R);

    Close(FileIn); Close(FileOut);

end.


Согласитесь, программа стала и гибче, и проще. Однако к первому её варианту мы ещё вернемся.

Итоги

• Стандартные процедуры ввода и вывода не способны вводить и выводить множества, для этого создают специальные процедуры.

• Вывод (распечатка) множества выполняется циклом со счетчиком, внутри которого проверяется вхождение каждого элемента в множество.

• Ввод множества из текстового файла основан на операции объединения по отдельности прочитанных элементов.

А слабо?

А) Напишите процедуры для ввода и вывода множества символов. Можно ли здесь для счетчика цикла применить символьную переменную?

Б) Напишите функцию, принимающую числовое множество и возвращающую количество содержащихся в нём элементов.

В) На основе первого варианта директорской программы придумайте способ поиска учеников, записавшихся более чем в один кружок. Или слабо?

Г) Напишите две функции, принимающие строку и возвращающие:

• строку, в которой символы исходной строки встречаются лишь по разу и следуют в алфавитном порядке, например «PASCAL» –> «ACLPS»;

• то же, но порядок следования символов такой же, как в исходной строке, например «PASCAL» –> «PASCL».

Загрузка...