ПОЗНАВАТЕЛЬНОЕ Сила воли ведет к действию, а позитивные действия формируют позитивное отношение Как определить диапазон голоса - ваш вокал
Игровые автоматы с быстрым выводом Как цель узнает о ваших желаниях прежде, чем вы начнете действовать. Как компании прогнозируют привычки и манипулируют ими Целительная привычка Как самому избавиться от обидчивости Противоречивые взгляды на качества, присущие мужчинам Тренинг уверенности в себе Вкуснейший "Салат из свеклы с чесноком" Натюрморт и его изобразительные возможности Применение, как принимать мумие? Мумие для волос, лица, при переломах, при кровотечении и т.д. Как научиться брать на себя ответственность Зачем нужны границы в отношениях с детьми? Световозвращающие элементы на детской одежде Как победить свой возраст? Восемь уникальных способов, которые помогут достичь долголетия Как слышать голос Бога Классификация ожирения по ИМТ (ВОЗ) Глава 3. Завет мужчины с женщиной 
Оси и плоскости тела человека - Тело человека состоит из определенных топографических частей и участков, в которых расположены органы, мышцы, сосуды, нервы и т.д. Отёска стен и прирубка косяков - Когда на доме не достаёт окон и дверей, красивое высокое крыльцо ещё только в воображении, приходится подниматься с улицы в дом по трапу. Дифференциальные уравнения второго порядка (модель рынка с прогнозируемыми ценами) - В простых моделях рынка спрос и предложение обычно полагают зависящими только от текущей цены на товар. | Управление выполнением программы на прологе Лекция: Управление выполнением программы на прологе Арифметические выражения Арифметическое выражение является числом или структурой. В структуру может входить одна или более компонент, таких, как числа, арифметические операторы, арифметические списковые выражения, переменная, конкретизированная арифметическим выражением, унарные функторы, функторы преобразования и арифметические функторы. Арифметические операторы. Язык Пролог не предназначен для программирования задач с большим количеством арифметических операций. Для этого используются процедурные языки программирования. Однако в любую Пролог-систему включаются все обычные арифметические операторы: + - * / mod div В некоторых реализациях языка Пролог присутствует более широкий набор встроенных арифметических операторов. Арифметические списковые выражения. Если Х - арифметическое выражение, то список [X] также является арифметическим выражением, например [1,2,3]. Первый элемент списка используется как операнд в выражении. Скажем, Х is ([1,2,3]+5) имеет значение 6. Арифметические списковые выражения полезны и при обработке символов, поскольку последние могут рассматриваться как небольшие целые числа. Например, символ «а» эквивалентен [97] и, будучи использован в выражении, вычисляется как 97. Поэтому значение выражения «р»+"А"-"а" равно 80, что соответствует коду ASCII для «Р». Переменная, конкретизированная арифметическим выражением. Примеры: Х = 5+2 и Y = 3 * (2+А) Унарные функторы. Примеры: +(Х) и -(Y) Функторы преобразования. В некоторых реализациях Пролога имеется арифметика с плавающей точкой, а следовательно, и функторы преобразования. Например: float(X) преобразует целое число Х в число с плавающей точкой. Математические функторы. Пример: квадрат(Х)эквивалентен арифметическому выражению (Х*Х). Арифметические операторы. Атомы +, -, *, /, mod и div — обычные атомы Пролога и могут использоваться почти в любом контексте. Указанные атомы — не встроенные предикаты, а функторы, имеющие силу только в пределах арифметических выражений. Они определены как инфиксные операторы. Эти атомы являются главными функторами в структуре, а сама структура может принимать только описанные выше формы. Арифметический оператор выполняется следующим образом. Во-первых, вычисляются арифметические выражения по обе стороны оператора. Во-вторых, над результатом вычислений выполняется нужная операция. Арифметические операторы определяются Пролог-системой. Если мы напишем предикат среднее (X,Y,Z) :- Z is (X+Y)/2. то, хотя можно определить среднее как оператор, ?- ор(250^х, среднее). но Пролог выдаст сообщение об ошибке, если встретит выражение Z is X среднее Y. Это произойдет потому, что Х среднее Y не образует арифметического выражения, а среднее не является арифметическим оператором, определенным в системе. Вычисление арифметических выражений В Прологе не допускаются присваивания вида Сумма=2+4. Выражение такого типа вычисляется только с помощью системного предиката is, например: Сумма is 2 + 4. Предикат is определен как инфиксный оператор. Его левый аргумент — или число, или неконкретизированная переменная, а правый аргумент — арифметическое выражение. Попытка доказательства целевого утверждения Х is Y заканчивается успехом в одном из следующих случаев: Х — неконкретизированная переменная, а результат вычисления выражения Y есть число; Х — число, которое равно результату вычисления выражения Y. Цель Х is Y не имеет побочных эффектов и не может быть согласована вновь. Если Х не является неконкретизированной переменной или числом либо если Y — не арифметическое выражение, возникает ошибка. Примеры: D is 10- 5 заканчивается успехом и D становится равным 5 4 is 2 * 4 - 4 заканчивается успехом 2 * 4 - 4 is 4 заканчивается неудачей a is 3 + 3 заканчивается неудачей X is 4 + а заканчивается неудачей 2 is 4 - X заканчивается неудачей Обратите внимание, что предикат is требует, чтобы его первый аргумент был числом или неконкретизированной переменной. Поэтому М - 2 is 3 записано неверно. Предикат is не является встроенным решателем уравнений. Предикаты ввода-вывода Предикат readln считывает строку с текущего устройства ввода и связывает ее со своим единственным выходным параметром. Предикат readint читает с текущего устройства целое число и связывает его со своим единственным выходным параметром. Предикат readreal отличается от предиката readint тем, что он считывает не целое, а вещественное число. Для чтения символа с текущего устройства ввода используется предикат readchar. Есть еще предикат inkey, который так же, как и readchar, читает символ со стандартного устройства ввода. Разница между ними в том, что предикат readchar приостанавливает работу программы до тех пор, пока не будет введен символ, а предикат inkey не прерывает выполнение программы. Если нужно просто проверить, нажата ли клавиша, можно воспользоваться предикатом keypressed, не имеющим аргументов. Предикат readterm предназначен для чтения сложных термов. У него два параметра: первый входной указывает имя домена, второй параметр конкретизируется термом домена, записанного в первом параметре. Если считанная этим предикатом строка не соответствует домену, указанному в его первом параметре, предикат выдаст сообщение об ошибке. Для записи данных в текущее устройство записи служит предикат write. Он может иметь произвольное количество параметров. Кроме того, в Турбо Прологе есть еще и предикат writef, который служит для форматного вывода данных. Для осуществления перехода на следующую строку (возврат каретки и перевод строки) применяется предикат nl, не имеющий параметров. Описанная ниже группа предикатов служит для преобразования типов. Предикатupper_lower имеет два аргумента и три варианта использования. Если в качестве первого аргумента указана строка (или символ), а второй аргумент свободен, то второй аргумент будет означен строкой (символом), полученной из первого аргумента преобразованием к нижнему регистру. Если в исходной строке были прописные английские буквы, то они будут заменены строчными. Если же, наоборот, первый аргумент свободен, а второй аргумент - это строка (или символ), то первый аргумент получит значение, равное строке (символу), полученной из второго аргумента преобразованием к верхнему регистру. Если в строке, находящейся во втором аргументе, были строчные английские буквы, то они будут заменены прописными. И, наконец, имеется третий вариант использования. Если и первый, и второй аргументы связаны, то предикат будет истинным только в том случае, если во втором аргументе находится строка (символ), которая получается из строки, находящейся в первом аргументе, путем замены всех прописных английских букв на строчные. В противном случае предикат будет ложным. Также имеют два параметра и три варианта использования предикаты str_int, str_real. Первый преобразует строку в целое число и наоборот. Второй служит для превращения строки в вещественное число или вещественного числа в строку. Предикат str_char имеет те же параметры использования и применяется для преобразования односимвольной строки в один символ и наоборот. Немного по-другому работает предикат char_int. Он позволяет переходить от символа к его ASCII-коду и обратно. Кроме того, можно использовать обычные математические функции, такие как: логарифмы натуральный (ln) и десятичный (log), квадратный корень (sqrt), модуль (abs), экспонента (exp). Тригонометрические функции: синус (sin), косинус (cos), тангенс (tan), арктангенс (arctan). Величины углов указываются в радианах. Функция trunc отбрасывает дробную часть своего параметра, а функция round округляет вещественное число до ближайшего целого. Для вычисления псевдослучайных чисел имеется два варианта предиката random. Первый вариант имеет один выходной параметр, в который помещается сгенерированное вещественное число, лежащее в промежутке межу нулем и единицей. Второй вариант этого предиката - двухаргументный. В качестве первого входного аргумента указывается целое число. Второй аргумент означивается целым числом, лежащим между нулем и первым аргументом. Одноместный предикат free истинен, если его аргументом является свободная переменная, и ложен в противном случае. Предикат bound, наоборот, истинен, если его аргумент - это связанная переменная, и ложен, если его аргумент свободен. Управление выполнением программы на прологе Откат уже упоминался в предыдущих лекциях. Суть этого механизма такова: в том месте программы, где возможен выбор нескольких вариантов, Пролог сохраняет в специальный стек точку возврата для последующего возвращения в эту позицию. Точка возврата содержит информацию, необходимую для возобновления процедуры при откате. Выбирается один из возможных вариантов, после чего продолжается выполнение программы. Во всех точках программы, где существуют альтернативы, в стек заносятся указатели. Если впоследствии окажется, что выбранный вариант не приводит к успеху, то осуществляется откат к последней из имеющихся в стеке точек программы, где был выбран один из альтернативных вариантов. Выбирается очередной вариант, программа продолжает свою работу. Если все варианты в точке уже были использованы, то регистрируется неудачное завершение и осуществляется переход на предыдущую точку возврата, если такая есть. При откате все связанные переменные, которые были означены после этой точки, опять освобождаются. При объяснении сущности бэктрекинга часто приводят пример с поиском пути в лабиринте. Один из возможных способов найти выход из лабиринта — это на каждой развилке поворачивать в одну и ту же сторону, до тех пор, пока не попадешь в тупик. После попадания в тупик нужно вернуться до ближайшей развилки. На ней нужно выбрать другое направление. После этого нужно опять на всех развилках выбирать поворот в том же направлении, что и в самом начале. Продолжаем этот алгоритм до тех пор, пока не выберемся из лабиринта. Методы управления откатом: Методотката после неудачи. Этот метод используется в ситуации, когда нужно получить не один ответ, а все возможные в данной ситуации ответы. Например, если вопрос является внутренней целью, то Турбо Пролог останавливает поиск после первого же успешного вычисления цели. При этом выявляется только первое решение. В методе отката после неудачи обычно используется всегда ложный предикат fail. Хотя, по большому счету, вместо этого предиката можно воспользоваться каким-нибудь заведомо ложным выражением. Например, 1=2 или чем-то в этом роде. Пример. Теперь давайте напишем предикат, который будет выводить на экран с помощью стандартного предиката write имена всех дочек. show_names:– mother(_,Name), /* означивает переменную Name именем дочки */ write(" ", Name), nl, /* выводит значение переменной Name на экран */ fail. /* вызывает откат на место, сохраненное в стеке точек возврата */ GOAL write("Имена дочек:"),nl,show_names. Предикат fail вызывает неуспешное завершение правила, затем осуществляется откат в точку, помещенную в стек последней. Процесс повторяется до тех пор, пока не будут исчерпаны все варианты достижения подцели mother(_,Name). В окне диалога будут выведены имена всех дочек в том порядке, в котором они упоминались в программе. пример с городами Метод отсечения и отката. В основе этого метода лежит использование комбинации предикатов fail (для имитации неудачи и искусственной организации отката) и «!» (отсечение или cut), который позволяет прервать этот процесс в случае выполнения какого-то условия. Предикат «!» всегда истинен и запрещает откат для подцелей, расположенных левее. Пример. Модифицируем еще раз предикат, выводящий имена всех дочек, так чтобы он выводил их не все, а только до определенного имени. У предиката будет один входной аргумент, где указывается имя дочери, на котором должен оборваться вывод на экран имен дочек. show_names3(Daughter):–mother(_,Name), /* означивает переменную Name именем дочки */write(" ", Name), nl, /* выводит значение переменной Name */Name=Daughter, /* проверяет совпадение имен дочек Name и Daughter. В случае несовпадения вызывает откат на место, указатель на которое хранится в стеке точек возврата. В случае совпадения, за счет наличия отсечения, завершает поиск и вывод имен дочек */write("Искомый человек найден!"),!. Метод повтора, определяемый пользователем. Он также использует откат, однако, в отличие от метода отката после неудачи, в котором откат осуществляется только после специально созданной неудачи, в этом методе откат возможен всегда за счет использования специального предиката, обычно кодируемого в виде следующего предложения: repeat.repeat:– repeat. Этот предикат всегда успешен. После выполнения первого предложения процедуры в стек точек возврата запоминается указатель, поскольку имеется еще один альтернативный вариант для него. В теле второго предложения опять вызывается предикат repeat. Предикат repeat не является встроенным предикатом, а имя "repeat" — зарезервированным словом. Соответственно, вместо этого имени можно использовать какое-нибудь другое. С помощью этого предиката можно организовывать циклы, подобные циклам в императивных языках программирования. Нужно не забыть добавить в правило, организующее цикл, кроме предиката repeat, условие завершения цикла и отсечение, чтобы не получилось зацикливания. Пример. Создадим предикат, который будет дублировать символ, введенный пользователем с клавиатуры. Завершиться этот процесс должен, когда пользователь введет некий ключевой символ, о котором мы заранее договоримся, что его появление означает окончание процесса дублирования символов. Для определенности, возьмем в качестве такого символа точку (.). double_char:– repeat, readchar(C), /* читаем символ с клавиатуры в переменную C */ write(C,C), nl,/* выводим на экран значение переменной C */ C=’.’,!, /* сравниваем значение переменной C с символом ‘.’*/ nl, write("Была введена точка. Закончили."). Первой подцелью нашего правила записан вызов предиката repeat. Он обеспечивает нам повторное выполнение следующих за ним подцелей. Можно эксперимента ради закомментировать предикат repeat, дабы убедиться, что в этом случае цикла не получится. Последующие подцели выполнятся всего один раз. Далее, используя стандартный предикат readchar, осуществляем чтение символа с клавиатуры в переменную C. Посредством встроенного предиката write выводим два экземпляра введенного пользователем символа на экран; стандартным предикатом nl переводим курсор на следующую строку. Затем значение переменной C сравнивается с символом точка (‘.’). Это условие, которое, с одной стороны, обеспечивает откат на первую подцель (предикат repeat), а с другой обеспечивает выход из цикла. Если поступивший с клавиатуры символ отличен от точки, подцель C=’.’ терпит неуспех. Пролог-система осуществляет откат на последнее место, указатель на которое записан в стеке точек возврата. Из подцелей, расположенных левее, альтернативы имелись только у первой подцели (предиката repeat). Все повторяется еще раз. Пользователь вводит символ, он дважды отображается на экране, сравнивается с точкой. Процесс повторяется до тех пор, пока введенный символ не окажется точкой. В этом случае подцель C=’.’ окажется успешной, управление перейдет на подцели, расположенные правее. Предикат nl сдвинет курсор на начало следующей строки, стандартный предикат write выводит на экран сообщение: "Была введена точка. Закончили." — и процесс дублирования символов на этом завершается. Напишем внутреннюю цель, которая проинформирует пользователя о правилах работы нашей программы, после чего запустит предикат double_char. GOAL write("Вводите символы, которые нужно повторить (точка — завершение)"),nl, double_char. |