Привет, абстрагирующимся. Прочитав эту статью, задумался, а как представлять эту задачу языком Пролог? Попробую выразить свое, затянувшееся, субботнее отношение к этой пятничной задаче, с помощью доступных декларативных формулировок.
В реализации на Скала, я увидел операцию "(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).
Автор: go-prolog