Пролог программирование - путь к решению - PullRequest
1 голос
/ 28 сентября 2011

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

Может кто-нибудь дать мне совет в этой области. Буду очень признателен за вашу помощь.

Я привожу пример, с которым я справляюсь, а также нашел здесь решение для stackoverflow, но я ищу, как он это делает, как он находит ответ:)

Напишите предикат сглаживания (List, Flat), который сгладит список, например, flatten ([a, b, [c, d], [[1,2]], foo], X) даст X = [a, b, c, d, 1,2, foo].

Это ответ, который я нашел в stackoverflow:

flatten(List, Flattened):-
   flatten(List, [], Flattened).

flatten([], Flattened, Flattened).
flatten([Item|Tail], L, Flattened):-
  flatten(Item, L1, Flattened),
  flatten(Tail, L, L1).
flatten(Item, Flattened, [Item|Flattened]):-
  \+ is_list(Item).

этот ответ принадлежит пользователю gusbro и задан пользователем Parhs. Я пытаюсь найти способ связаться с пользователем gusbro, чтобы спросить его, как он может получить такой ответ, но я не могу.

Большое спасибо.

Ответы [ 2 ]

2 голосов
/ 29 сентября 2011

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

Вот мое решение проблемы "свести список списков (списков ...)". Я прокомментировал это, чтобы показать, как я туда попал:

  • Сначала давайте определим открытый интерфейс нашего решения. Мы определяем flatten/2. Его тело состоит из вызова внутренней реализации flatten / 3, которая принимает аккумулятор, который отображается как пустой список.

    flatten ( X , R ) :-
      flatten ( X , [] , R ) ,
      .
    

    Это было легко.

  • Внутренний предикат flatten/3 немного сложнее, но не очень.

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

      flatten( [] , X , X ).
      
    • Следующий (и единственный) другой случай - непустой список. Для этого мы рассмотрим заголовок списка. Наше правило заключается в том, что его нужно сгладить и добавить к результату. Хорошим правилом программирования является написание описательного кода, а Пролог сам по себе является описательным , а не процедурным языком: один описывает решение проблемы и позволяет механизму логического вывода сортировать вещи вне.

      Итак ... давайте опишем, что должно произойти сейчас, и расскажем о механике выравнивания заголовка списка:

      flatten( [X|Xs] , T , Y ) :-
        flatten_head(X,X1)     ,
        append( T,X1,T1)       ,
        flatten( Xs , T1 , Y )
        .
      

      Это тоже было легко.

Это суть всего решения, прямо здесь. Мы разбили нашу проблему на 3 части:

  • особый случай (пустой список)
  • нормальный регистр (непустой список)
  • что делать с каждым элементом в списке (еще не определен).

Давайте перейдем к реализации способа выравнивания одного элемента списка. Это тоже легко. У нас есть два случая: элемент списка может быть списком или чем-то другим.

  • Во-первых, элемент списка может быть несвязанной переменной. Мы не хотим нежелательного поведения, такого как неограниченная рекурсия, поэтому давайте позаботимся об этом сразу, запретив несвязанные термины (пока). Если элемент связан, мы пытаемся сгладить его, снова вызывая наш открытый интерфейс, flatten\2 (оооооооо ... больше рекурсии!)

    Это выполняет две вещи

    • Во-первых, он сообщает нам, есть ли у нас список или нет: flatten/2 терпит неудачу, если вручается что-то, кроме списка.
    • Во-вторых, когда это удается, задание flatten_head/2 выполнено.


    Вот код:

    flatten-head( X , Y   ) :-     
      nonvar(X) ,
      flatten( X , Y )
      .
    
  • Наконец, последний случай, который мы должны рассмотреть, это случай элементов списка, которые не являются списками (несвязанные переменные, атомы или какой-либо другой термин пролога). Они уже "плоские" ... все, что нам нужно сделать, это обернуть их в один список элементов, чтобы вызывающая сторона (flatten\3) получила согласованную семантику для своего "возвращаемого значения":

    flatten-head( X , [X] ).
    

Вот полный код:

flatten ( X , R ) :-
  flatten ( X , [] , R )
  .

flatten( [] , X , X ) .
flatten( [X|Xs] , T , Y ) :-
  flatten_head(X,X1)      ,
  append( T,X1,T1)        ,
  flatten( Xs , T1 , Y )
  .

flatten-head( X , Y   ) :-
  nonvar(X)             ,
  flatten( X , Y )
  .
flatten-head( X , [X] ) .

Каждый отдельный шаг прост. Трудно определить кусочки и сплести их вместе (хотя иногда выяснить, как остановить рекурсию, может быть не так очевидно).

Некоторые учебные материалы

Чтобы понять рекурсию, вы должны сначала понять рекурсию - аноним

Эрик Робертс Рекурсивное мышление (1986), пожалуй, лучшая (только?) Книга, специально посвященная разработке рекурсивного программного обеспечения для разработки WRT с точки зрения. Есть обновленная версия Рекурсивное мышление с Java, 20th Anniversary Edition (2006), хотя я ее не видел.

Обе книги, конечно, можно приобрести в обычных местах: Пауэлл, Амазонка и т. Д.

Thinking Recursively Thinking Recursively (with Java!)

Возможно, вы также захотите прочитать классику Дугласа Хофштадтлера Гёдель, Эшер, Бах: Вечная золотая коса Некоторые считают, что это лучшая книга, когда-либо написанная.YMMV.

Gödel, Escher, Bach: An Eternal Golden Braid

Также доступно от обычных подозреваемых:

Новая книга, хотя и не относящаяся непосредственно к теории рекурсии, которая могла бы быть полезной, хотя я не видела ее (она получила хорошие отзывы), написана Майклом Корбаллисом Рекурсивный разум: истокиЧеловеческий язык, мысль и цивилизация

enter image description here

2 голосов
/ 28 сентября 2011

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

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

В одном классе у нас есть «базовые случаи», где мы предоставляем решение проблемы, когда входные данные не могут быть далее разделены на более мелкие случаи.

Другой класс - это «рекурсивные случаи», где мы разбиваем входные данные на части, решаем их отдельно, а затем «объединяем» результаты, чтобы найти решение для этого более крупного ввода.

В примере для flatten / 2 мы хотим взять в качестве входных данных список элементов, где каждый элемент также может быть списком, а результатом должен быть список, содержащий все элементы из входных данных. Поэтому мы разбиваем проблему на ее случаи. Мы будем использовать вспомогательный аргумент для хранения промежуточного уплощенного списка, и именно поэтому мы реализуем flatten / 3.

Поэтому наш предикат flatten / 2 просто вызовет flatten / 3, используя пустой список в качестве начального промежуточного уплощенного списка:

flatten(List, Flattened):-
   flatten(List, [], Flattened).

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

flatten([], Flattened, Flattened).

Теперь мы делаем рекурсивный шаг. Это включает взятие списка ввода и разделение проблемы в два шага. Первым шагом является выравнивание первого элемента этого списка ввода. Вторым шагом будет рекурсивное выравнивание остального:

flatten([Item|Tail], L, Flattened):-
  flatten(Item, L1, Flattened),
  flatten(Tail, L, L1).

Хорошо, поэтому вызов flatten (Item, L1, Flattered) выравнивает первый элемент, но передает в качестве промежуточного списка несвязанную переменную L1. Это всего лишь хитрость, так что при возврате предиката переменная L1 все еще остается неограниченной, а Flatlined будет иметь вид [... | L1], где ... - сплющенные элементы Item.

Следующий шаг, который вызывает flatten (Tail, L, L1) , выравнивает остальную часть списка ввода, и результат ограничен L1.

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

flatten(Item, Flattened, [Item|Flattened]):-
  \+ is_list(Item).

, который проверяет, является ли элемент списком, и когда он не является списком, он связывает результат как список с заголовком head = Item и как промежуточный плоский список.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...