Назад | Содержание | Вперёд

6. 2.    Обработка файлов термов


6. 2. 1.    reаd  и write

Встроенный предикат read используется для чтения термов из текущего входного потока. Цель

        read( X)

вызывает чтение следующего терма Т и сопоставление его с X. Если Х - переменная, то в результате Х конкретизируется и становится равным Т. Если сопоставление терпит неудачу, цель read( X) тоже терпит неудачу. Предикат read - детерминированный в том смысле, что в случае неуспеха не происходит возврата для ввода следующего терма. После каждого терма во входном файле должна стоять точка или пробел, или символ возврата каретки.

Если read( X) вычисляется в тот момент, когда достигнут конец текущего входного файла, тогда Х конкретизируется атомом end_of_file (конец файла).

Встроенный предикат write выводит терм. Поэтому цель

        write( X)

выведет терм Х в текущий выходной файл. Х будет выведен в той же стандартной форме, в какой обычно пролог-система изображает на экране или распечатке значения переменных. Полезной особенностью Пролога является то, что процедура write "знает", как изображать любой терм, как бы сложен он не был.

Существуют дополнительные встроенные предикаты для форматирования вывода. Они вставляют пробелы в дополнительные строки в выходной поток. Цель

        tab( N)

выводит N пробелов. Предикат nl (без аргументов) приводит к переходу на новую строку.

Следующие примеры иллюстрируют использование этих процедур.

Предположим, у нас есть процедура для вычисления кубов чисел:

        куб( N, С) :-
             С is N * N * N.

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

        ?-  куб( 2, X).
        Х = 8

        ?-  ку6( 5, Y).
        Y = 125

        ?-  куб( 12, Z).
        Z = 1728

Для получения каждого результата нам придется набирать соответствующую цель. Давайте теперь изменим эту программу так, чтобы процедура куб сама читала соответствующие данные. Теперь программа будет сама читать данные и выводить их кубы до тех пор, пока не будет прочитан атом стоп:

        куб :-
                read( X),
                обработать( X).

        обработать( стоп) :-   !.

        обработать( N) :-
                С is N * N * N,
                write( С),
                куб.

Это был пример программы, декларативный смысл которой трудно сформулировать. В то же время ее процедурный смысл совершенно ясен: чтобы вычислить куб, сначала нужно считать X, а затем его обработать; если Х = стоп, то все сделано, иначе вывести Х3 и рекурсивно запустить процедуру куб для обработки остальных чисел.

С помощью этой новой процедуры таблица кубов чисел может быть получена таким образом:

        ?-  куб.

        2.
        8
        5.
        125
        12.
        1728
        стоп.
        yes

Числа  2,  5  и  12  были введены пользователем с терминала, остальные числа были выведены программой. Заметьте, что после каждого числа, введенного пользователем, должна стоять точка, которая сигнализирует о конце терма.

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

        куб :-
              read( стоп),   !.

        куб :-
              read( N),
              С is N * N * N,
              write( С),
              куб.

Причина, по которой эта процедура работает неправильно, станет очевидной, если проследить, какие действия она выполняет с входным аргументом, скажем с числом 5. Цель read( стоп) потерпит неудачу при чтении этого числа, и оно будет потеряно навсегда. Следующая цель read введет следующий терм. С другой стороны может случиться, что сигнал стоп будет считан целью read( N), что приведет к попытке перемножить нечисловую информацию.

Процедура куб ведет диалог между пользователем и программой. В таких случаях обычно желательно, чтобы программа перед тем, как читать с терминала новые данные, дала сигнал пользователю о том, что она готова к приему информации, а также, возможно, и о том, какого вида информация ожидается. Это делается обычно путем выдачи "приглашения" перед чтением. Нашу процедуру куб можно для этого изменить, например, так:

        куб :-
              write( 'Следующее число, пожалуйста:'),
              read( X),
              обработать( X).

        о6работать( стоп) :-   !.

        обработать( N) :-
              С is N * N * N,
              write( 'Куб'),  write( N),  write( 'равен').
              write( С), nl,
              куб.

Разговор с новой версией мог бы быть, например, таким:

        ?-  куб.

        Следующее число, пожалуйста: 5.
        Куб 5 равен 125

        Следующее число, пожалуйста: 12.
        Куб 12 равен 1728

        Следующее число, пожалуйста: стоп.
        yes

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

В последующих разделах мы увидим некоторые типичные примеры операций, в которых участвуют чтение и запись.


6. 2. 2.    Вывод списков

Кроме стандартного прологовского формата для списков существуют несколько других естественных форм их внешнего представления, которые в некоторых ситуациях являются более предпочтительными. Следующая процедура

        вывспис( L)

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

        вывспис( [ ]).

        вывспис( [X | L) :-
                write( X), n1.
                вывспис( L).

Если у нас есть список списков, то одной из естественных форм его выводе является такая, при которой все элементы каждого списка записываются на отдельной строке. Для этого мы определим процедуру вывспис2. Вот пример ее использования:

        ?-  вывспис2( [ [а, b, с], [d, e, f], [g, h, i] ] ).

        а   b   с
        d   e   f
        g   h   i

Процедура, выполняющая эту работу, такова:

        вывспис2( [ ]).

        вывспис2( [L | LL] ) :-
                строка( L), n1,
                вывспис1( LL).

        строка( [ ]).

        строка( [X | L] ) :-
                write( X), tab( 1),
                строка( L).

Список целых чисел иногда удобно представить в виде диаграммы. Следующая процедура диагр выводит список в такой форме (предполагается, что числа списка заключены между 0 и 80). Пример ее использования:

        ?- диагр( [3, 4, 6, 5] ).

        ***
        ****
        ******
        *****

        Процедуру диагр можно определить так:

                диагр( [N | L]) :-
                    звездочки( N), n1,
                    диагр( L).

                звеэдочки( N) :-
                    N > 0,
                    write( *),
                    Nl is N - 1,
                    звездочки( Nl).

                звездочки( N) :-
                    N =< 80.


6. 2. 3.    Формирование термов

Предположим, наша программа имеет дело с семьями, которые представлены в виде термов так, как это сделано в гл. 4 (рис. 4.1). Тогда, если, перемен-

        родители
                том фокс, датарожд 7 май 1950, работает bbс,
                                                                        оклад 15200
                энн фокс, датарожд 9 май 1951, неработает
        дети
                пат фокс, датарожд 5 май 1973, неработает
                джим фокс, датарожд 5 май 1973, неработает

Рис. 6. 2.  Улучшенный формат вывода термов, представляющих семью.

ная F конкретизирована термом, изображенный на рис. 4.1, то цель

        write( F)

вызовет вывод этого терма в стандартной форме примерно так:

        семья( членсемьи( том, фокс, дата( 7, май,1950),
                         работает( bbс, 15200)),
                членсемьи( энн, фокс, дата( 9, май, 1951),
                         неработает),
                [членсемьи( пат, фокс, дата( 5, май, 1973),
                         неработает),
                членсемьи( джим, фокс, дата( 5, май, 1973),
                         неработает)])

Здесь содержится полная информация, однако форма представления легко может запутать, поскольку трудно проследить, какие ее части образуют самостоятельные семантические единицы. Поэтому обычно предпочитают выводить такую информацию в каком-либо формате, например так, как показано на рис. 6.2. Процедура

        вывсемью( F)

с помощью которой это достигается, приведена на рис. 6.3.

        вывсемью( семья ( Муж, Жена, Дети) :-
                nl, write( родители), nl, nl,
                вывчленсемьи( Муж), nl,
                вывчленсемьи( Жена), nl, nl,
                write( дети), nl, nl,
                вывчленсемьи( Дети).

        вывчленсемьи( членсемьи( Имя, Фамилия, дата( Д, М, Г), Работа) ) :-
                tab(4), write( Имя),
                tab(1), write( Фамилия),
                write( ', дата рождения'),
                write( Д), tab( 1),
                write( M), tab( 1),
                write( Г), write( ','),
                вывработу( Работа).

        вывсписчлсемьи( [ ]).

        вывсписчлсемьи( [Р | L]) :-
                вывчленсемьи( Р), nl,
                вывсписчлсемьи( L),
        вывработу( неработает) :-
                write( неработает).

        вывработу( работает Место, Оклад) ) :-
                write(' работает '), write( Место),
                write( ', оклад '), write( Оклад).

Рис. 6. 3.  Программа, обеспечивающая вывод в формате, представленном на рис. 6.2.


6. 2. 4.    Обработка произвольного файла термов

Типичная последовательность целей для обработки файла F от начала до конца будет выглядеть примерно так:

        . . . , see( F), обработкафайла, sеe( user), . . .

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

        обработкафайла:
                обработкафайла :-
                        read( Терм),
                        обработка( Терм).

        обработка( end_of_file) :-   !.
                                    % Все сделано

        обработка( Терм) :-
                обраб( Терм),

                                    % Обработать текущий элемент
                обработкафайла.
                                    % Обработать оставшуюся часть файла

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

        показфайла( N) :-
                read( Терм),
                показ( Терм, N).

        показ( Терм, N) :-  !
                write( N), tab( 2), write( Терм),
                Nl is N + 1,
                показфайла( Nl).

Вот другой пример программы обработки файлов, построенной по подобной схеме. Пусть есть файл с именем файл1, термы которого имеют форму

        изделие( НомерИзд, Описание, Цена, ИмяПоставщика)

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

        создатьфайл( Поставщик)

Например, если исходный каталог хранится в файле файл1, а мы хотим создать специальный каталог в файле файл2, содержащий всю информацию о том, что поставляет Гаррисон, тогда мы применим процедуру создатьфайл следующим образом:

        ?-  seе( файл1), tеll( файл2), создатьфайл( гаррисон),
                  see( user), tell( user).

Процедуру создатьфайл можно определить так:

        создатьфайл( Поставщик) :-
                write( Поставщик), write( '.'), nl,
                создатьостальное( Поставщик).

        создатьостальное( Поставщик) :-
                read( Изделие),
                обработать( Изделие, Поставщик).

        обработать( end_ot_file) :-  !.

        обработать( Изделие( Ном, Опис, Цена, Поставщик),
                                Поставщик) :-  !,
                write( Изделие( Ном, Опис, Цена) ),
                write( '.'), nl,
                создатьостальное( Поставщик).

            обработать ( _, Поставщик) :-
                создатьостальное( Поставщик).

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

Упражнения

6. 1.    Пусть f  -   файл термов. Определите процедуру

        найтитерм( Терм)

которая выводит на терминал новый терм из f, сопоставимый с Терм'ом.

Посмотреть ответ

6. 2.    Пусть f  -   файл термов. Напишите процедуру

        найтивсетермы( Терм)

которая выводит на экран все термы из f, сопоставимые с Tepм'ом. Обеспечьте при этом, чтобы во время поиска Терм не конкретизировался (это могло бы помешать ему сопоставиться с другими термами дальше по файлу).

Посмотреть ответ


Назад | Содержание | Вперёд