Шаг 46.
Основы логического программирования.
Принадлежность к списку

    На этом шаге мы рассмотрим принадлежность к списку.

    Предположим, что у вас есть список имен John, Leonard, Eric и Frank, и вы хотите, используя Пролог, проверить, имеется ли заданное имя в этом списке. Другими словами, вам нужно выяснить отношение "принадлежность" между двумя аргументами - именем и списком имен. Это выражается предикатом

   member(name,namelist). % "name"  принадлежит  "namelist"

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

   member(john,[john,leonard,eric,frank])
будет выполнено.
   domains
      namelist=name*
      name=symbol
   predicates
     member(name,namelist) 
   clauses
      member(Name,[Name |_]).
      member(Name,[_ |Tail]):-
         member(Name,Tail).
    Текст этой программы можно взять здесь.

    Если голова списка не совпадает с Name, то нужно проверить, можно ли Name найти в хвосте списка.

    На обычном языке:

    Name принадлежит списку, если Name есть первый элемент списка, или
   
Name принадлежит списку, если Name принадлежит хвосту.

    Второе предложение member, выражающее отношение принадлежности, в Прологе выглядит так:

   member(Name,[_ |Tail]):- 
      member(Name,Tail).

Объединение списков

    В том виде, как он дан в предыдущей программе, предикат member работает двумя способами. Рассмотрим его предложения еще раз:

   member(Name,[Name |_ ]) .
   member(Name,[_ |Tail]):-
      member(Name,Tail).
    На эти предложения можно смотреть с двух различных точек зрения: декларативной и процедурной.     Эти две точки зрения соотносятся с целевым утверждением:
   member(2, [1, 2, 3, 4]). 
и
   member[X, [1, 2, 3, 4]).

    В результате, первое целевое утверждение "просит" Пролог выяснить, верно ли утверждение, второе, - найти всех членов списка [1,2,3,4]. Предикат member одинаков в обоих случаях, но его поведение может быть рассмотрено с разных точек зрения.

Рекурсия с процедурной точки зрения

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

   append(List1,List2,List3)

    Он объединяет List1 и List2 и создает List3. Еще раз воспользуемся рекурсией (на этот раз с процедурной точки зрения). Если List1 пустой, то результатом объединения List1 и List2 останется List2. На Прологе это будет выглядеть так:

   append([],List2,List2).

    Если List1 не пустой, то можно объединить List1 и List2 для формирования List3, сделав голову List1 головой List3. (В следующем утверждении переменная H используется как голова для List1 и для List3.) Хвост List3 - это L3, он состоит из объединения остатка List1 (т. е. L1) и всего List2. To есть:

   append([H |L1],List2,[H |L3]):-
      append(L1,List2,L3).

    Предикат append выполняется следующим образом: пока List1 не пустой, рекурсивное предложение передает по одному элементу в List3. Когда List1 станет пустым, первое предложение унифицирует List2 с полученным List3.

    Предикат append определен в программе, приведенной ниже. Загрузите эту программу.

   domains
      integerlist=integer* 
   predicates
      append(integerlist,integerlist,integerlist)
   clauses
      append([],List,List).
      append([H |L1],List2,[H |L3]):-
        append(L1,List2,L3).
    Текст этой программы можно взять здесь.

    Задайте следующее целевое утверждение:

   append([1,2,3],[5,6],L).
    А теперь попробуйте это:
   append([1,2],[3],L),
   append(L,L,LL).

Предикат может иметь несколько вариантов использования

    Рассматривая append с декларативной точки зрения, вы определили отношение между тремя списками. Однако это отношение сохранится, даже если List1 и List3 известны, a List2 - нет. Оно также справедливо, если известен только List3. Например, чтобы определить, какие из двух списков можно объединить для получения известного списка, надо составить целевое утверждение такого вида:

   append(L1,L2,[1,2,4]).
    По этому целевому утверждению Пролог найдет следующие решения:
   L1=[], L2=[1,2,4]
   L1=[1], L2=[2,4]
   L1=[1,2],L2=[4]
   L1=[1,2,4],L2=[]
   4 решения

    Можно также применить append, чтобы определить, какой список можно подсоединить к [3,4] для получения списка [1,2,3,4].

    Запустите целевое утверждение

   append(L1,[3,4],[1,2,3,4]).
    Пролог найдет решение:
   L1=[1,2]

    Предикат append определил отношение между входным набором и выходным набором таким образом, что отношение применимо в обоих направлениях. Задавая такое отношение, вы спрашиваете:

    Который выход соответствует данному входу?
или
    Который вход соответствует данному выходу?

    Состояние аргументов при вызове предиката называется потоком параметров. Аргумент, который присваивается или назначается в момент вызова, называется входным аргументом и обозначается буквой i; а свободный аргумент - это выходной аргумент, обозначается буквой о.

    У предиката append есть возможность работать с разными потоками параметров, в зависимости от того, какие исходные данные вы ему дадите. Однако не для всех предикатов имеется возможность быть вызванными с различными потоками параметров. Если предложение Пролога может быть использовано с различными потоками параметров, оно называется обратимым предложением. Когда вы записываете собственные предложения в Прологе, помните, что обратимые предложения имеют дополнительные преимущества, и их создание добавляет "мощности" предикатам.

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




Предыдущий шаг Содержание Следующий шаг