Модераторы: Poseidon
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> [Prolog] Помогите решить задачу 
V
    Опции темы
Mogaba
Дата 14.10.2007, 09:44 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 2
Регистрация: 14.10.2007

Репутация: нет
Всего: нет



Я в прологе не очень разбираюсь, поэтому сильно не бейте:)

Нужно решить на TurboProlog такую задачу:
Дано N чисел. Определить, сколько из них отлично от последнего числа.

И объясните решение, если можно.
PM MAIL   Вверх
Shaggie
Дата 18.2.2008, 13:56 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Завсегдатай
Сообщений: 570
Регистрация: 21.12.2006
Где: outer space

Репутация: нет
Всего: 72



Чует сердце, что с ответом я немножко припозднился... Но вдруг кому ещё поможет. И обещаю разъяснить! Задачка, кстати, зело интересная. Прошу расценивать это скорее как познавательную статью, нежели запоздалый ответ на вопрос. 

Итак, нам нужен предикат вроде diffLast(List, Diffs), который находил бы последний элемент списка и сравнивал все элементы списка с найденным. Итого задачу можно разбить на две части: вызов предиката last(List, Last), находящий последний элемент в списке, и diff(List, Last, Diffs), который сравнит каждый элемент списка с заранне найденным последним элементом. 

Записывается это так:
Код

diffLast( List, Diffs) :-
    last( List, Last),
    diff( List, Last, Diffs).


Теперь надо описать предикат для нахождения последнего элемента в списке. Для начала абстрагируемся от уже написанного предиката last(List, Last) и попробуем написать новый, совершенно с нуля, назовём его last_(). Что передаётся в last_() ? 

Во-первых, список, последний элемент которого мы ищем: last_(List). Как найти последний элемент? Очевидно, требуется перебрать все элементы, пока список не кончится, и последний элемент окажется искомым. Значит, голову списка надо каждый раз передавать заново: last(List, Head). Но как нам вернуть найденый последний элемент? Значит необходим третий аргумент, который окажется вычислен только в момент полной раскрутки списка и будет возвращён без имзменений: last_(List, Head, Last).

Теперь мы можем сформулировать условие, при котором предикат last_() будет возвращать истинное значение. Представим, что мы раскрутили весь список, рекурсивно передавая его головной элемент вторым аргументом, и достигли конца списка. Теперь переданный аргумент автоматически оказывается последним, и его значение должно совпадать со значением третьего аргумента (того, который теперь будет возвращён наверх. Это напоминает поплавок, который выталкивается на поверхность озера smile ). Если третьим аргументом будет передана не инициализированная переменная, в этот момент её значение станет равным значению второго аргумента (что нам и нужно).
Код

last_([], Last, Last) :- !.


Восклицательный знак даёт команду виртуальной машине отсечь все варианты, достигнутые ранее, чтобы более к ним не возвращаться. Это повышает эффективность алгоритма. Рекомендую обратиться к специализированной литературе по языку. Если не можете рассказать своими словами, что и зачем происходит в этот момент, то запись можно сократить до
Код

last_([], Last, Last).


Теперь формулируем условие, при котором список не пуст. Что нужно сделать? Хм... изъять голову списка и рекурсивно вызвать предикат ещё раз:
Код

last_([Head|Tail], _, Last) :-
    last_(Tail, Head, Last).


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

И теперь осталось только вызвать его из уже придуманного нами предиката last(List, Last), передав соответствующие параметры:
Код

last([Head|Tail], Last) :-
    last_(Tail, Head, Last).


Обобщим опыт:

Код

last([Head|Tail], Last) :-
    last_([Head|Tail], Head, Last).

last_([], Last, Last).

last_([Head|Tail], _, Last) :-
    last_(Tail, Head, Last).


Обратите внимание - предикат last_([], Last, Last) идёт первым. Pattern Matching с этим предикатом нас интересует больше, чем со следующим.

Теперь дело за предикатом diff( List, Last, Diffs). Задача: сформулировать безусловно истинный предикат, не требующий никаких лишних телодвижений. Возможно ли это? Да! При условии, что в списке List остаётся только один последний элемент, такой же как и Last, счётчик отличных элементов будет равным нулю:
Код

diff( [Last], Last, 0) :- !.


Характерное условие - сначала нам нужно раскрутить список до конца, чтобы переменная Diffs оказалась инициализирована нулём. Теперь при возвратах можно будет сравнивать голову списка с элементом Last, и при их неравенстве инкрементировать счётчик Diffs. Только при возвратах! Нельзя инкрементировать счётчик, если переменная не инициализирована, это приведёт к исключению.

Итак, какие есть два возможных варианта паттерн матчинга? Первый - это если голова списка эквивалентна искомому элементу. Тогда инкремента не происходит. Второй - это если голова списка не эквивалентна искомому элементу. В таком случае перемнная Diffs увеличивается на единицу.
Код

diff( [Last|Tail], Last, Diffs) :- !,
    diff( Tail, Last, Diffs).

diff( [_|Tail], Last, Diffs) :-
    diff( Tail, Last, Buf),
    Diffs is Buf + 1.


Порядок матчинга снова значим: голова списка нас не волнует (и выполняется второе условие) только в том случае, если первое условие не совпало. Ещё один важный момент - для раскручивания списка в этом случае задаётся новая переменная, и она используется в дальнейшей раскрутке стека, а при возврате её значение увеличивается на единицу и присваивается искомой переменной.

Обобщим:
Код

diff( [Last], Last, 0) :- !.

diff( [Last|Tail], Last, Diffs) :- !,
    diff( Tail, Last, Diffs).

diff( [_|Tail], Last, Diffs) :-
    diff( Tail, Last, Buf),
    Diffs is Buf + 1.


И всё! теперь можно записывать готовое решение:
Код

diffLast( List, Diffs) :-
    last( List, Last),
    diff( List, Last, Diffs).


last([Head|Tail], Last) :-
    last_([Head|Tail], Head, Last).

last_([], Last, Last).

last_([Head|Tail], _, Last) :-
    last_(Tail, Head, Last).


diff_( [Last], Last, 0) :- !.

diff( [Last|Tail], Last, Diffs) :- !,
    diff( Tail, Last, Diffs).

diff( [_|Tail], Last, Diffs) :-
    diff( Tail, Last, Buf),
    Diffs is Buf + 1.


Тест:
Цитата

1 ?- diffLast([1,2,3,1,4,5,1], N).

N = 4


Рекомендую просмотреть действие программы под отладчиком - это гораздо нагляднее. А потом отладить
diffLast([1,2,3,1,4,5,1], 4).
для постижения дао.


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

diffLast( List, Diffs) :-
    lastAndCount_( List, _, _, Diffs).


lastAndCount_([], Last, Last, 0) :- !.

lastAndCount_([Head|Tail], _, Last, Diffs) :-
    lastAndCount_(Tail, Head, Last, Buf),
    equals(Head, Last, Result),
    Diffs is Buf + Result.


equals(X, X, 0) :- !.

equals(_, _, 1).


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


--------------------
Цитата(alina3000 @  6.3.2014,  10:47 Найти цитируемый пост)
Сорри что не по теме 
PM MAIL ICQ GTalk Jabber   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "Центр помощи"

ВНИМАНИЕ! Прежде чем создавать темы, или писать сообщения в данный раздел, ознакомьтесь, пожалуйста, с Правилами форума и конкретно этого раздела.
Несоблюдение правил может повлечь за собой самые строгие меры от закрытия/удаления темы до бана пользователя!


  • Название темы должно отражать её суть! (Не следует добавлять туда слова "помогите", "срочно" и т.п.)
  • При создании темы, первым делом в квадратных скобках укажите область, из которой исходит вопрос (язык, дисциплина, диплом). Пример: [C++].
  • В названии темы не нужно указывать происхождение задачи (например "школьная задача", "задача из учебника" и т.п.), не нужно указывать ее сложность ("простая задача", "легкий вопрос" и т.п.). Все это можно писать в тексте самой задачи.
  • Если Вы ошиблись при вводе названия темы, отправьте письмо любому из модераторов раздела (через личные сообщения или report).
  • Для подсветки кода пользуйтесь тегами [code][/code] (выделяйте код и нажимаете на кнопку "Код"). Не забывайте выбирать при этом соответствующий язык.
  • Помните: один топик - один вопрос!
  • В данном разделе запрещено поднимать темы, т.е. при отсутствии ответов на Ваш вопрос добавлять новые ответы к теме, тем самым поднимая тему на верх списка.
  • Если вы хотите, чтобы вашу проблему решили при помощи определенного алгоритма, то не забудьте описать его!
  • Если вопрос решён, то воспользуйтесь ссылкой "Пометить как решённый", которая находится под кнопками создания темы или специальным флажком при ответе.

Более подробно с правилами данного раздела Вы можете ознакомится в этой теме.

Если Вам помогли и атмосфера форума Вам понравилась, то заходите к нам чаще! С уважением, Poseidon, Rodman

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Центр помощи | Следующая тема »


 




[ Время генерации скрипта: 0.0449 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.