Как стать автором
Обновить

Logical FizzBuzz

Время на прочтение 4 мин
Количество просмотров 2.7K

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


В реализации на Скала я увидел операцию "(value % n)" и пояснение, что значения value,n -это: type class "Integral" требующий от типа "T" возможности вычислять остаток от деления и иметь значение "zero".


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


Это представляли еще в Древней Греции, натуральное число N — это 0 или следующее за ним натуральное число, результат функции p(N).


Записывать можно так N0= 0,
потом N1=p(0), и далее N2=p(p(0)) — вот такой ряд натуральных чисел, от 0 и далее следующее и следующее значение:


Изображаю это на Прологе:


natural(0).
natural(N+1):-natural(N).

если выполнить такую цель, получим бесконечность ответов:


?-natural(X).
X=0;
X=0+1;
X=0+1+1: .... ну и тд.

От этого, пользы большой не извлечь, а как часть объяснения годится, значения переменной X являются структурами, в Прологе есть инфиксные операции, которые могут соединить значения между собой, вместо громоздкой записи p(p(p..(p(0)..))), просто соединим символы знаком "+": 0+1+1+1+1… это просто такое символьное выражение, почти строка, а можно бы записать и как: zero plus one plus one.


Вот так можно объявлять свой знак операции:


:-op(600, yfx, plus). %op(+Precedence, +Type, :Name) 
natural(zero).
natural(N plus one):-natural(N).

Будут такие представления чисел:


?- natural(X).
X = zero ;
X = zero plus one ;
X = zero plus one plus one ; ...

Далее, полезно будет сформулировать следующую проблему, как выражать сложение или вычитание над двумя такими натуральными числами?,
вот оно:


add(0, X, X).
add(X+1, Y, Z+1):- add(X, Y, Z).

Теперь можно решить вот такие цели для сложения:


?- add(0+1,  0+1+1, X).
X=0+1+1+1

И вот такой вопрос, превратится в вычитание натуральных чисел:


?- add(X,  0+1+1+1,  0+1+1+1+1).
X=0+1

Можно получать все возможные натуральные слагаемые, составившие определенное значение:


?- add(X, Y, 0+1+1+1+1).
X=0, Y=0+1+1+1+1;
X=0+1, Y=0+1+1+1;
X=0+1+1, Y=0+1+1;.. и тд.

Подбираемся к сути этой записки, умение абстрагироваться, разделять понятия на более примитивные, до состояния их точного и краткого описания — это и есть важная часть программирования, это, думаю, радость любой инженерии…


В задаче, на вход поступят числа, записанные как целые, арабские, и если не отменять это условие и не просить пользователя передавать на вход, только что показанные структуры, реализую такое правило:


int_natural(0,0).
int_natural(X, Y+1):- X0 is X-1,  int_natural(X0, Y).

оно сможет конвертировать целое число в его "натуральное" представление:


?- int_natural(6,X).
X=0+1+1+1+1+1+1 

Операция is — это встроенное вычисление арифметических действий.
Теперь ближе к задаче, там было, про определить остаток от деления. Сначала продемонстрирую умножение:


mul(0, X, 0).
mul(X+1, Y, Z):- mul(X, Y, Z1), add(Z1, Y, Z).

это можно использовать и для обратной операции — для деления:


?- mul(0+1+1, 0+1+1, X).
X=0+1+1+1+1
?- mul(0+1+1, X, 0+1+1+1+1).
X=0+1+1

Вот так, сформулирую остаток от деления:


rem(X, X, 0).
rem(X, Y, R) :- add(X1,Y,X), rem(X1,Y,R),!.
rem(X, _, X):-!.

тут и отсечение (!) заранее установленно, что бы не шокировать читателей функцией обратной к "остатку от деления", это, наверное, операция "следующее число, не кратное на одну величину остатка", типа так:


?- rem(X, 0+1+1+1, 0+1).
X = 0+1+1+1+1 ;
X = 0+1+1+1+1+1+1+1 ;
X = ... + ... + 1+1+1+1+1+1+1+1+1 ... и тд.

"Прямое" использование этого правила вот:


?- rem(0+1+1+1+1, 0+1+1, X).
X = 0.
?- rem(0+1+1+1+1, 0+1+1+1, X).
X = 0+1. 

FizzBuzz


Отлично, всю задачу сформулировать теперь можно, таким видом:


fizz_buzz(N, fizzbuzz) :- rem(N, 0+1+1+1, 0), rem(N, 0+1+1+1+1+1, 0),!.
fizz_buzz(N, fizz) :- rem(N, 0+1+1+1, 0),!.
fizz_buzz(N, buzz) :- rem(N, 0+1+1+1+1+1, 0),!.
fizz_buzz(N, N).

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


?- int_natural(5, N),!, fizz_buzz(N, X).
N = 0+1+1+1+1+1,
X = buzz.
?- int_natural(15, N),!, fizz_buzz(N, X).
N = ... + ... + 1+1+1+1+1+1+1+1+1,
X = fizzbuzz.

или по условию, на выходе получаем тоже число:


?- int_natural(4, N),!, fizz_buzz(N, X).
N = X, X = 0+1+1+1+1.

Исключив предыдущие недостатки и немного подумав про производительность, упрощаю до такого "алгоритма":


fizz_buzz2(IN, R) :-once(int_natural(IN,N)),
                    once(rem(N, 0+1+1+1, 0) -> F=fizz; F=''),
                    once(rem(N, 0+1+1+1+1+1, 0) -> B=buzz; (F\='')->B=''),
                    atom_concat(F,B,R),!.
fizz_buzz2(N, N).

В этом варианте сразу переводим входное целое число и компонуем выходное значение из частей, если это потребовалось. Встроенное правило once(Goal), выполняет цель только один раз, для исключения возвратов и зацикливаний.
Вот такие примеры:


?- fizz_buzz2(2, X).
X = 2.
?- fizz_buzz2(15, X).
X = fizzbuzz.

Обработку ряда чисел, можно получить отобразив собрав все решения, указанной цели в один список, для этого используем встроенный мета-предикат findall():


?- findall(X, (between(1, 10, N), fizz_buzz2(N, X)), Result).
Result = [1, 2, fizz, 4, buzz, fizz, 7, 8, fizz|...].

Вот тут, можно попробовать.


Для заключения


Пролог, это любопытная возможность переносить в "символы" условия задач, это система опирающаяся на логику предикатов и встроенные процедуры автоматического доказательства теорем. Формулировка проблемы, при удачном выражении, позволяет находить решение для самой задачи, а также сразу иметь решение "обратной" к ней проблеме. А может вот таким вопросом, увидим, какие числа, приводят к fizz: ?- fizz_buzz(N, fizz).

Теги:
Хабы:
+6
Комментарии 12
Комментарии Комментарии 12

Публикации

Истории

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн