Системы искусственного интеллекта. Тематический план
Скачать 1.4 Mb.
|
ТЕМА 2.5 ОРГАНИЗАЦИЯ ПОВТОРЕНИЙ В ЯЗЫКЕ PROLOG План лекции: 2.5.1 Метод отката после неудачи 2.5.2 Метод отсечения и отката 2.5.3 Простая рекурсия 2.5.4 Метод обобщенного правила рекурсии (ОПР) 2.5.1 Метод отката после неудачи Очень часто в программах необходимо выполнить одну и ту же задачу несколько раз. Существуют два способа реализации правил, выполняющих одну и ту же задачу многократно. Первый их них мы будем называть повторением, а второй — рекурсией. Правила Turbo-Prolog, выполняющие повторения, используют откат, а правила, выполняющие рекурсию, используют самовызов. Вид правила, выполняющего повторение, следующий: repetitive_rule :- /* правило повторения */ <предикаты и правила>, fail. /* неудача */ Конструкция <предикаты и правила> в теле этого правила обозначает предикаты, содержащие несколько утверждений, а также правила, определенные в программе. Встроенный предикат fail («неудача») вызывает откат, так что предикаты и правила выполняются еще раз. Вид правила, выполняющего рекурсию, следующий: recursive_rule :- /* правило рекурсии */ <предикаты и правила>, recursive_rule. Заметим, что последним в теле данного правила является само же правило recursive_rule. Это и есть рекурсия: тело правила содержит вызов самого себя. Правила повтора и рекурсии могут обеспечивать одинаковый результат, хотя алгоритмы их выполнения не одинаковы. Каждый из них в конкретной ситуации имеет свои преимущества. Рекурсия, например, может требовать больше системных ресурсов, поскольку всякий раз при рекурсивном вызове новые копии используемых значений помещаются в стек — особую область памяти, используемую в основном для передачи значений между правилами. Эти значения сохраняются, пока правило не завершится успешно или неуспешно. В некоторых ситуациях такое использование стека может быть оправдано, если промежуточные значения должны храниться в определенном порядке для дальнейшего использования. Метод отката после неудачи (ОПН) может быть использован для управления вычислением внутренней цели при поиске всех возможных ее решений. Метод ОПН использует предикат fail, который вызывает неуспешное завершение правила. При его срабатывании внутренние унификационные подпрограммы выполняют откат в точку возврата, и процесс повторяется до тех пор, пока последнее утверждение не будет обработано. Использование метода ОПН позволяет извлекать данные из каждого утверждения базы данных. Добавив же дополнительные условия на значения объектов для одной или более переменных предиката, можно извлекать данные только из определенных утверждений. Пример 1. Пусть в базе данных в виде набора фактов хранятся сведения о нескольких городах (название города и название реки, протекающей через город). Цель — получить на экране полный список городов. /* Демонстрация метода отката после неудачи */ predicates city (symbol, symbol) all_cities clauses city ("Самара","Волга"). city ("Саратов","Волга"). city ("Ростов","Дон"). city ("Москва","Москва"). city ("Волгоград","Волга"). /*правило для получения полного списка городов*/ all-cities:-city (C,_), write (C), nl, fail. goal write ("Полный список городов:"),nl, all_cities. Пример 2. Пусть в базе данных в виде набора фактов хранятся сведения о нескольких городах (название города и название реки, протекающей через город). Цель — получить на экране список городов, стоящих на указанной реке. predicates city (symbol, symbol) search (symbol) clauses city ("Самара","Волга"). city ("Саратов","Волга"). city ("Ростов","Дон"). city ("Москва","Москва"). city ("Волгоград","Волга"). /*правило для поиска*/ search (R):- city (C,R), write (C), nl, fail. goal write ("Укажите название реки: "), readln (River), nl, write ("Города, стоящие на реке", River,":"), nl, search (River). 2.5.2 Метод отсечения и отката В некоторых ситуациях необходимо иметь доступ только к определенной части данных. Метод отсечения и отката (ОО) может быть использован для фильтрации данных, выбираемых из утверждений базы данных. Задавая условие на окончание просмотра базы данных, можно получить только требуемую часть информации. Для этих целей Prolog имеет встроенный предикат cut («отсечение»), который обозначается символом восклицательного знака (!). Этот предикат, вычисление которого всегда завершается успешно, заставляет внутренние унификационные подпрограммы «забыть» все указатели отката, установленные во время попыток вычислить текущую подцель. Другими словами, предикат cut «устанавливает барьер», запрещающий выполнить откат ко всем альтернативным решениям текущей подцели. Однако последующие подцели могут создать новые указатели отката и тем самым создать условия для поиска новых решений, на которые область действия предиката cut уже не распространяется. Но если все более поздние цели окажутся неуспешными, то барьер, установленный предикатом cut, заставит механизм отката отсечь все решения в области действия cut путем немедленного отката к другим возможным решениям вне области действия предиката cut. Тем самым метод отсечения и отката имитирует неуспешное вычисление и выполняет последующий откат до тех пор, пока не будет обнаружено определенное условие, а предикат cut служит для устранения всех последующих откатов. Пример. Пусть имеется база данных, которая содержит несколько имен детей. Цель — выдать список этих имен до имени Diana включительно. /* Демонстрация метода отката и отсечения */ predicates name (symbol) choice clauses name ("Mary"). name ("Bob"). name ("Diana"). name ("John"). name ("Peter"). /*правило для поиска*/ choice:- name (N), write (N), nl, N="Diana",!. goal write ("Список имен до Diana: "), nl, choice. 2.5.3 Простая рекурсия Правило, содержащее само себя в качестве компонента, называется правилом рекурсии. Пример. Программа «Эхо» — ввод с клавиатуры строк и вывод их на экран; признак окончания ввода данных — пустая строка. /* Демонстрация рекурсии на примере программы "Эхо" */ predicates echo clauses echo :- readln (String), String<>"", write (String), nl, echo. goal echo. 2.5.4 Метод обобщенного правила рекурсии (ОПР) Обобщенное правило рекурсии содержит в теле правила само себя. Рекурсия будет конечной, если в правило включено условие выхода, гарантирующее окончание его работы. Тело правила состоит из утверждений и правил, определяющих задачи, которые должны быть выполнены. Общий вид правила рекурсии в символическом виде: <имя правила рекурсии> :- <список предикатов>, (1) <предикат условия выхода>, (2) <список предикатов>, (3) <имя правила рекурсии>, (4) <список предикатов>. (5) Данное правило рекурсии имеет пять компонентов. Первый — это группа предикатов, которые всегда истинны и на рекурсию не влияют. Второй компонент — это предикат условия выхода. Успех этого предиката позволяет продолжить рекурсию, а неудача вызывает ее остановку (рекурсивное правило возвращает «Ложь»). Третий компонент — список других предикатов, которые также всегда истинны и на рекурсию тоже не оказывает влияния. Четвертая группа — само рекурсивное правило, успех которого и вызывает рекурсию. Наконец, пятая группа — это список предикатов, которые тоже всегда успешны и опять-таки не влияют на рекурсию. Пятая группа также получает значения (если они имеются), помещенные в стек во время выполнения рекурсии. Напомним, что правило рекурсии обязательно должно содержать условие выхода. В противном случае рекурсия будет бесконечной, а такое правило — бесполезным. Ответственность за обеспечение завершаемости правила рекурсии возлагается на программиста. Правила, построенные указанным образом, являются обобщенными правилами рекурсиии (ОПР), а сам метод называется ОПР-методом. Пример. Правило генерации всех целых чисел, начиная с 1 и заканчивая 7. Пусть имя правила будет write_number(Number). Для этого примера первый компонент структуры общего правила рекурсии не используется. Вторым компонентом, т. е. предикатом выхода, является условие Number < 8: когда значение Number равно 8, правило будет успешным, а программа завершится. Третий компонент правила оперирует с числами: число выдается на экран, а затем увеличивается на 1. Для увеличенного числа будет использоваться новая переменная Next_Number. Четвертый компонент — вызов самого правила рекурсии write_number(Next_number). Пятый компонент, представленный в общем случае, здесь не используется. Таким образом, программа генерации ряда чисел будет использовать следующее правило рекурсии: write_number(8). write_number(Number) :Number < 8, write(Number), nl, Next_Number = Number + 1, write_number(Next_number). Рассмотрим ход выполнения этой программы. Она начинается с попытки вычислить подцель write_number(1). Сначала программа сопоставляет подцель с первым правилом write_number(8). Так как 1 не равно 8, то это сопоставление неуспешно. Тогда программа вновь пытается сопоставить подцель, но уже с головой правила write_number (Number). На этот раз сопоставление успешно, так как переменной Number присвоено значение 1. Теперь программа сравнивает это значение с 8 (условие выхода). Так как 1 меньше 8, то данное подправило успешно. Следующий предикат выдает значение, присвоенное Number. Переменная Next_Number получает значение 2, а значение Number увеличивается на 1. ТЕМА 2.6 СПИСКИ В ЯЗЫКЕ PROLOG Prolog также поддерживает связанные объекты, называемые списками. Список — это упорядоченный набор объектов, следующих друг за другом. Составляющие списка внутренне связаны между собой, поэтому с ними можно работать и как с группой (списком в целом), и как с индивидуальными объектами (элементами списка). Рассмотрим структуру, организацию и представление списков. Список является набором объектов одного и того же доменного типа. Объектами списка могут быть целые числа, действительные числа, символы, символьные строки и структуры. Порядок расположения элементов является отличительной чертой списка; те же самые элементы, упорядоченные иным способом, представляют уже совсем другой список, поскольку порядок играет важную роль в процессе сопоставления. Prolog допускает списки, элементами которых являются структуры. Если структуры принадлежат к альтернативному домену, то элементы списка могут иметь разный тип. Такие списки используются для специальных целей. Совокупность элементов списка заключается в квадратные скобки — [], а друг от друга элементы списка отделяются запятыми. Примеры: [1,2,3,6,9,3,4] [3.2,4.6,1.1,2.64,100.2] ["YESTERDAY","TODAY","TOMORROW"] Элементами первого списка являются целые числа, элементами второго — действительные числа, а третьего — символьные строки. Количество элементов в списке называется его длиной. Например, длина списка ["MADONNA","AND","CHILD"] равна 3, а длина списка [4.50, 3.50, 6.25, 2.9, 100.15] равна 5. Список может также содержать всего один элемент или даже не содержать элементов вовсе, например, ["summer"] или []. Список, не содержащий элементов, называется пустым, или нулевым списком. Непустой список можно рассматривать как состоящий из двух частей: первый элемент списка — его голова (1), а остальная часть списка — хвост (2). Голова является одним из элементов списка, а хвост — это список сам по себе. Голова списка представляет собой отдельное неделимое значение; хвост же — это список, составленный из того, что осталось от исходного списка в результате «усекновения головы». Если, например, список состоит из одного-единственного элемента, то его можно разделить на голову, которой будет этот самый единственный элемент, и хвост, являющийся пустым списком. Примеры: Список Голова Хвост [1,2,3,4,5] 1 [2,3,4,5] ['s','k','y'] 's' ['k','y'] [cat] cat [] [] не определена не определен Чтобы использовать в программе список, необходимо описать предикат списка. Примеры: num ([1,2,3,6,9,3,4]) realnum ([3.2,4.6,1.1,2.64,100.2]) time (["YESTERDAY","TODAY","TOMORROW"]) Введение списков в программу необходимо отразить в трех ее разделах. Домен списка должен быть описан в разделе domains, работающий со списком предикат — в разделе predicates и, наконец, надо задать сам список в разделе clauses или goal. Пример: /* Демонстрация работы со списками */ domains bird_list = bird_name * bird_name = symbol number_list = number * number = integer predicates birds(bird_list) score(number_list) clauses birds(["sparrow", "robin", "mockingbird", "thunderbird"]). score([56,87,63,89,91,62,85]). Отличительной особенностью описания списка является наличие звездочки (*) после имени домена элементов. Так, запись bird_name * указывает, что это домен списка, элементами которого являются bird_name, т. е. запись bird_ name * следует понимать как список, состоящий из элементов домена bird_name. Примеры внешних целей: birds(All). birds([_,_,_,B,_]). birds([B1,B2,_,_,_]). score(All). score([F,S,T,_,_,_,_]). При задании целей во втором, третьем и пятом примерах требовалось точное знание количества элементов списка, являющегося объектом предиката birds, что не всегда возможно (с точки зрения пользователя). Но вспомним, что Prolog позволяет отделять от списка первый элемент и обрабатывать его отдельно. Данный метод работает вне зависимости от длины списка до тех пор, пока список не будет исчерпан. Этот метод доступа к голове списка называется методом разделения списка на голову и хвост. Операция деления списка на голову и хвост обозначается при помощи вертикальной черты (|): [Head|Tail]. Head здесь является переменной для обозначения головы списка, переменная Tail обозначает хвост списка. Пример. Правило печати элементов списка [4,-9,5,3]: print_list ([]). print_list ([Head|Tail]) :write (Head),nl, print_list (Tail). Когда это правило пытается удовлетворить цель print_ list([4,-9,5,3]), то первый вариант правила — print_ list[] — дает неуспех, так как его объект является пустым списком. Напротив, введенный список соответствует объекту второго варианта предиката — print_list([Head|Tail]). Переменной Head, следовательно, присваивается значение первого элемента в списке: 4, в то время как переменной Tail cтавится в соответствие оставшаяся часть списка: [-9,5,3]. Теперь, когда выделен первый элемент списка, с ним можно обращаться так же, как и с любым простым объектом: write(Head),nl. Далее, так как хвост списка есть список сам по себе, то значение переменной Tail может быть использовано в качестве объекта рекурсивного вызова print_list: print_list(Tail). Когда испытывается данное подправило, Tail имеет значение [-9,5,3]. Снова первый вариант не проходит и соответствие устанавливается при помощи второго. Переменной Head присваивается значение –9, которое затем печатается на экране, а процесс повторяется со списком [5,3]. В итоге, когда переменная Head принимает значение 3, переменной Tail присваивается пустой список. Теперь при рекурсивном вызове print_list(Tail) значение Tail соответствует объекту правила print_list([]). Поскольку этот вариант не имеет рекурсивных вызовов, цель считается удовлетворенной, и вырабатывается условие окончания рекурсии print_list. Тем самым первый вариант позволяет print_list завершиться успехом, когда рекурсивные вызовы опустошат весь список. Пример: /* Демонстрация использования метода деления списка на голову и хвост при выводе списка на экран */ domains animal_list = symbol * predicates print_list(animal_list) clauses animal (["тигр","заяц","лев","волк"]). print_list([]). print_list([Head|Tail]) :write(Head),nl, print_list(Tail). goal animal (Animals_list), print_list (Animals_list). Операции над списками Поиск элемента в списке представляет собой просмотр списка для выявления соответствия между элементом данных и элементом просматриваемого списка. Если такое соответствие найдено, то поиск заканчивается успехом; в противном случае поиск заканчивается неуспехом. Для сопоставления объекта поиска с элементами просматриваемого списка необходим предикат, объектами которого являются эти объект поиска и список: find_it(3 ,[1,2,3,4,5]). Первый из объектов утверждения — 3 — есть объект поиска. Второй — это список [1,2,3,4,5]. Для выделения элемента из списка и его сравнения с объектом поиска можно применить метод разделения списка на голову и хвост. Стратегия поиска при этом будет состоять в рекурсивном выделении головы списка и ее сравнении с элементом поиска. Правило поиска может сравнивать объект поиска и голову текущего списка. Саму операцию сравнения можно записать в виде: find_it(Head,[Head|_]). Этот вариант правила предполагает наличие соответствия между объектом поиска и головой списка. В данном случае поскольку осуществляется попытка найти соответствие между объектом поиска и головой списка, то нет необходимости заботиться о том, что происходит с хвостом. Если объект поиска и голова списка действительно соответствуют друг другу, то результатом сравнения явится успех; если же нет, то неуспех. Но если эти два элемента данных различны, то попытка сопоставления считается неуспешной, происходит откат и поиск другого правила или факта, с которыми можно снова попытаться найти соответствие. Для случая несовпадения объекта поиска и головы списка необходимо предусмотреть правило, которое выделяло бы из списка следующий по порядку элемент и делало бы его доступным для сравнения: find_it(Head, [Head|_]. find_it(Head, [_,Tail]) :find_it(Head, Tail). Если правило find_it(Head,[Head|_]) неуспешно, то происходит откат и делается попытка со вторым вариантом. При этом опять-таки присвоенный переменной Tail список разделяется на голову и хвост при помощи утверждения find_it(Head,[Head|_]), и этот процесс повторяется, пока данное утверждение не даст либо успех (в случае установления соответствия на очередной рекурсии), либо неуспех (в случае исчерпания списка). Деление списков. При работе со списками достаточно часто требуется разделить список на несколько частей. Это бывает необходимо, когда для текущей обработки нужна лишь определенная часть исходного списка. Рассмотрим предикат split, аргументами которого являются элемент данных и три списка: split(Middle,L,L1,L2). Элемент Мiddle здесь является компаратором, L — это исходный список, а L1 и L2 — подсписки, получающиеся в результате деления списка L. Если элемент исходного списка меньше или равен Middle, то он помещается в список L1, а если больше, — то в список L2. Пример: split(40,[30,50,20,25,65,95],L1,L2). Правило здесь устроено следующим образом: очередной элемент извлекается из списка при помощи метода разделения списка на голову и хвост, а потом сравнивается с компаратором Middle. Если значение этого элемента меньше или равно значению компаратора, то элемент помещается в список L1, в противном случае — в список L2. В результате применения этого правила к списку [30,50,20,25,65,95] значениями списков L1 и L2 станут соответственно [30,20,25] и [50,65,95]. Само же это правило для разделения списка записывается на языке Prolog следующим образом: split(Middle,[Head|Tail],[Head|L1],L2) :Head <= Middle, split(Middle,Tail,L1,L2). split(Middle,[Head|Tail],L1,[Head|L2]) :Head > Middle, split(Middle,Tail,L1,L2), split(_,[],[],[]). Отметим, что метод деления списка на голову и хвост используется в данном правиле как для разделения исходного списка, так и для формирования выходных списков. Присоединение списка. Слияние двух списков и получение, таким образом, третьего списка принадлежит к числу наиболее полезных при работе со списками операций. В качестве примера рассмотрим две переменные L1 и L2, представляющие списки и имеющие значения [1,2,3] и [4,5] соответственно. Тогда весь процесс слияния можно представить в виде такой совокупности действий: 1) список L3 (результирующий) вначале пуст; 2) элементы списка L2 пересылаются в L3; теперь значением L3 будет [4,5]; 3) элементы списка L1 пересылаются в L3; в результате он принимает значение [1,2,3,4,5]. Структура правила для выполнения этих действий следующая: append([],L,L). append([N|L1],L2,[N|L3]) :append(L1,L2,L3). Если на его вход подать списки L1=[1,2,3] и L2=[4,5], то сначала Prolog пытается удовлетворить первый вариант правила: append([],L,L). Чтобы сделать это, первый объект предиката должен быть пустым списком. Однако это не так. Внутренний процесс унификации Prolog, пытаясь удовлетворить второе правило append, раскручивает цепочку рекурсий до тех пор, пока не обнулит первый список. Элементы списка при этом последовательно пересылаются в стек. Когда первый объект предиката append окажется пустым списком, становится возможным применение первого варианта правила. Третий список при этом инициализируется вторым: append([],[4,5],_). append([],[4,5],[4,5]). Теперь Prolog начинает сворачивать рекурсивные вызовы второго правила. Извлекаемые при этом из стека элементы помещаются один за другим в качестве головы к первому и третьему спискам. Следует особо отметить, что элементы извлекаются в обратном порядке (ведь это стек!), и что значение извлеченного из стека элемента присваивается переменной N одновременно в [N|L1] и [N|L3]. Шаги данного процесса можно представить так: append([],[4,5],[4,5]) append([3],[4,5],[3,4,5]) append([2,3],[4,5],[2,3,4,5]) append([1,2,3],[4,5],[1,2,3,4,5]) Сортировка списков представляет собой переупорядочивание элементов списка определенным образом для упрощения доступа к нужным элементам. Существует много способов сортировки списков, но обычно применяются три из них: метод перестановки, метод вставки и метод выборки (или их комбинации). Первый из этих методов заключается в попарной перестановке элементов списка до тех пор, пока они не выстроятся в нужном порядке. Второй осуществляется при помощи вставки элементов в список на требуемые места до тех пор, пока список не будет упорядочен. Третий метод включает в себя многократную выборку и перемещение элементов списка. Второй из указанных методов (метод вставки) особенно удобен для реализации на языке Prolog. Рассмотрим список [4,7,3,9], элементы которого расположены хаотично. Пусть мы хотим получить из него список [3,4,7,9], упорядоченный по возрастанию. Опишем предикат, производящий нужную сортировку списка методом вставки. Чтобы воспользоваться преимуществами мощного средства языка Prolog — внутренней унификацией, проведем сортировку хвоста. Правила, реализующие этот способ сортировки, имеют следующую структуру: insert_sort([],[]). insert_sort([X|Tail],Sorted_list) :insert_sort(Tail,Sorted_Tail), insert(X,Sorted_Tail,Sorted_list). insert(X,[Y|Sorted_list],[Y|Sorted_list1]) :asc_order(X,Y), !, insert(X,Sorted_list,Sorted_list1). insert(X,Sorted_list,[X|Sorted_list]). asc_order(X,Y) :- X>Y. Обсудим работу этих правил на примере списка [4,7,3,9]. Вначале Prolog применяет указанные выше правила к исходному списку; выходной список в этот момент еще не определен: insert_sort([4,7,3,9],_). Первая попытка удовлетворить правило insert_sort осуществляется с первым вариантом правила insert_sort ([],[]), т. е. для удовлетворения этого правила оба списка должны быть пустыми. Второй вариант правила insert_sort трактует список как комбинацию головы списка и его хвоста. Внутренние унификационные процедуры языка Prolog пытаются сделать пустым исходный список. Устранение элементов списка начинается с головы списка и осуществляется рекурсивно. По мере того как Prolog пытается удовлетворить первое из правил, происходят рекурсивные вызовы insert_sort, при этом значениями Х последовательно становятся все элементы исходного списка, которые затем помещаются в стек. В результате применения этой процедуры список становится пустым. Теперь первый вариант правила insert_sort производит обнуление выходного списка. Далее Prolog пытается удовлетворить второе правило из тела insert_sort — правило insert. Переменной Х сначала присваивается первое взятое из стека значение 9, а правило insert принимает форму insert(9,[],[9]). Затем из стека извлекается следующий элемент — 3 — и испытывается первый вариант insert, а значит, и правило упорядочивания asc_order(X,Y) :X>Y. Переменная Х здесь имеет значение 3, а Y — значение 9. Так как это правило неуспешно, то вместе с ним неуспешен и первый вариант insert. Тогда при помощи второго варианта insert 3 вставляется в выходной список слева от 9: insert(3,[9],[3,9]). Далее происходит возврат к insert_sort; теперь оно имеет форму insert_sort([3,9],[3,9]). На следующем круге рекурсии происходит вставка очередного элемента из стека, а именно 7. В начале работы на этом круге правило insert имеет вид insert(7,[3,9],_) и происходит сравнение 7 и 3: asc_order(7,3):- 7>3. Так как данное правило успешно, то элемент 3 убирается в стек, а insert вызывается рекурсивно еще раз, но уже с хвостом списка: insert(7,[9],_). Так как правило asc_order(7,9):- 7>9 неуспешно, то испытывается второй вариант insert (успешно) и происходит возврат на предыдущие круги рекурсии сначала insert, а потом insert_sort. В результате 7 помещается в выходной список между элементами 3 и 9: insert(7,[3,9],[3,7,9]). При возврате еще на один круг рекурсии insert_sort из стека извлекается элемент 4. Правило insert теперь выглядит как insert(4,[3,7,9],_). Далее мы получаем правило asc_order(4,3) :- 4>3. Оно успешно, следовательно, имеем insert(4,[7,9],_). Правило же asc_order(4,7):4>7 неуспешно. Это означает, что 4 окажется в выходном списке между 3 и 7: insert(4,[3,7,9],[3,4,7,9]). insert_sort([4,7,3,9],[3,4,7,9]). Теперь в стеке больше нет элементов, а все рекурсии insert_sort уже свернуты. Компоновка данных в список. Иногда при программировании определенных задач возникает необходимость собрать данные из базы данных в список для последующей их обработки. Prolog содержит встроенный предикат, позволяющий справиться с этой задачей: findall(Variable_name, Predicate_expression, List_name). Variable_name здесь обозначает объект входного предиката Predicate_expression, а List_name является именем переменной выходного списка. Эта переменная должна относиться к домену списков, объявленному в разделе domains. Пример. Опишем предикат, содержащий сведения об очках, набранных футбольными командами. Необходимо сложить все очки и усреднить их. /* Демонстрация предиката компоновки данных в список для вычисления среднего значения*/ domains list = real * predicates football (symbol,real) sum_list (list, real, integer). average_score сlauses /* факты (футбольная база данных) */ football("Ohio State",116.0). football("Michigan",121.0). football("Michigan State",114.0). football("Purdue",99.0). football("UCLA",122.0). average_score :findall(Points,football(_,Points),Point_list), sum_list (Point_list, Sum, Number), Average = Sum / Number, write("Среднее значение= ",Average). sum_list ([],0,0). sum_list ([H|T], Sum, Number) :- sum_list(T,Sum1,Number1), Sum = H + Sum1, Number = Number1 + 1. goal average_score. |