
2.2. Определение длины списка
Теперь посмотрим, как можно выяснить, сколько в списке элементов. И вообще, что такое длина списка? Вот простое логическое определение:
Длина [] = 0.
Длина любого другого списка = 1 + длина его хвоста.
/* Это иллюстрация , а не фрагмент программы */ длина([1,2,3],L1).
длина([2,3],L2).
длина([3],L3).
длина([],0).
L3=0+1=1
L2=L3+1=2
L1=L2+1=3
Проблема с предикатом "длина" заключается в том, что мы не сможем вычислить длину списка до тех пор, пока не будет вычислена длина хвоста. Но, оказывается, эту проблему можно решить. Для этого нужен предикат с тремя аргументами:
- один - это список, который машина будет сводить на нет, пока он не станет пустым (как и в предыдущем случае);
- второй - свободный аргумент, который будет в конечном счете содержать результат (длину);
- третий - счетчик, начинающийся с 0 и заканчивающийся при каждом вызове.
Когда список в конце становится пустым, счетчик унифицируется с не связанным (до сих пор) значением результатом.
2.3. Модификация списка
Иногда требуется взять список и сделать из него другой. Осуществляется это путем поэлементного прохождения списка с заменой каждого элемента вычисляемым значением.
На естественном языке это звучит так:
| Чтобы добавить единицу ко всем элементам пустого списка,
| нужно сгенерировать другой пустой список. Чтобы добавить
| 1 ко всем элементам любого другого списка, нужно добавить
| 1 к голове и сделать ее головой результата, а затем доба-
| вить 1 к хвосту и сделать его хвостом результата.
2.4. Принадлежность к списку
Предположим, что у нас есть список с именами "Иван", "Петр", "Евгений", "Федор" и мы хотим выяснить, есть ли некоторое имя в данном списке. Другими словами, мы должны определить отношение "принадлежность" двух аргументов: имени и списка имен. Это соответствует предикату принадлежит(имя,список_имен)
Если голова списка - не "Имя", нам нужно проверить, нет ли элемента "Имя" в хвосте списка.
На естественном языке это звучит так :
Имя принадлежит списку, если Имя - первый элемент списка,
или Имя принадлежит списку, если Имя принадлежит хвосту.
2.5. Присоединение одного списка к другому
Построим предикат для присоединения одного списка к другому. Это будет предикат с тремя аргументами :
присоединить(Список1,Список2,Список3)
Здесь Список1 и Список2 объединяются в Список3. Снова мы используем рекурсию .
Если Список1 пуст, то результатом будет Список2: присоединить([],Список2,Список2).
Если же Список1 не пуст, то первый и второй можно объединить, создав Список3, если голову первого списка сделать головой третьего. Хвост третьего списка - это C3. Он составляется из остатков первого и всего второго списка.
Предикат "присоединить" работает следующим образом: Пока Список1 не пуст, рекурсивное правило переправляет по одному элементу в Список3. Когда Список1 пуст, первое выражение гарантирует, что Список2 присоединится к концу результирующего списка.
2.6. Нахождение всех решений сразу
В отличие от поиска с возвратом, при рекурсии можно передавать информацию (через аргументы) из одного рекурсивного вызова в другой. Благодаря этому рекурсивная процедура по мере выполнения может отслеживать промежуточные результаты или вести
счетчик.
Но есть и у поиска с возвратом свойство, которым не обладает рекурсия, а именно, находить все альтернативные решения для заданной цели. Так что можно оказаться в затруднительном положении: нам нужны все решения для цели, но все они нам нужны сразу, как часть одной составной структуры данных.
В Visual-Прологе предусмотрен выход из этого положения. Встроенный предикат findall берет цель как один из своих аргументов и собирает все решения в один список. У findall три аргумента:
- первый аргумент, "ПеремИмя", указывает, какой аргумент в заданном предикате должен собираться в список;
- второй аргумент, "мой_предикат", указывает предикат, значения из которого должны собираться;
- третий аргумент, "ПарамСписка", представляет собой переменную для хранения списка, собираемого в процессе поиска с возвратом значений; заметим, что для значений "ПарамСписка" должен быть определен пользовательский домен.
Рассмотрим использование findall для вывода среднего возраста группы людей:
/* Использование findall */
domains
имя, адрес = string
возраст = integer
список = возраст*
predicates
человек(имя, адрес, возраст)
сум_список(список, возраст, integer)
goal
findall(Возраст, человек(_, _, Возраст), С),
clauses
человек("Sherlock Holmes", "22B Baker Street", 42).
человек("Pete Spiers", "Apt. 22, 21st Street", 36).
человек("Mary Darrow", "Suite 2, Omega Home", 51).
Выражение findall в этой программе создает список C, в который собираются все значения возраста, получаемые из предиката "человек". Если бы нам нужно было собрать список всех, кому 42 года, для этого можно было бы воспользоваться подцелью
findall(Кто,человек(Кто,_,42),Список).