1

在 Prolog 中解决一个非常简单的练习:打印从 1 到 100 的所有数字,但如果数字是 3 的倍数,则打印“Fuzz”,如果是 5 的倍数,则打印“Buzz”,如果两者都打印,则打印“FizzBu​​zz”。

我最终做了以下事情:

fizzbuzz :- forall( between(1, 100, X), fizzbuzz(X) ).
fizzbuzz(X) :- ( write_fb(X) ; write_n(X) ), nl.

write_fb(X) :- bagof(_, fb(X), _).
fb(X) :- X rem 3 =:= 0, write('Fizz').
fb(X) :- X rem 5 =:= 0, write('Buzz').

write_n(X) :- write(X).

但是没有任何谓词或控制结构可以避免使用 bagof/3 仅仅因为它的副作用吗?(我总是有点不确定是否只为副作用使用谓词)。

4

11 回答 11

3

作为对现有答案的补充,我想展示一个更具相关性的解决方案,我希望它能够说明将声明性编程范式(例如逻辑编程)应用于这个问题的一些独特好处。

首先,让我们回顾一下任务:

打印从 1 到 100 的所有数字,但不是数字,而是打印...

  • 'Fuzz' 如果数字是 3 的倍数
  • '嗡嗡声'如果是 5 的倍数
  • 和“FizzBu​​zz”(如果两者都有)。

我认为默认的假设是数字仅限于 整数

为简单起见,让我们首先将自己限制为单个整数,然后描述该整数与所需输出之间的关系。

使用 Prolog 系统的CLP(FD) 约束进行声明性整数算术,上述三种情况可以直接转换为 Prolog :

integer_output(N, 'Fuzz') :- N #= 3*_。
integer_output(N, 'Buzz') :- N #= 5*_。
整数输出(N,'FizzBu​​zz'):- N #= 3*_,N #= 5*_。

但这还不是全部,因为这会产生例如:

?- integer_output(4, N)。
错误的。

因此,我们需要另外一种情况,例如,我们可以将其表述为:

整数输出(N,N):- N mod 3 #\= 0,N mod 5 #\= 0。

这只是说明如果其他情况都不适用,我们按原样输出数字。由此产生的关系非常普遍。例如,我们可以将它用于具体整数:

?- integer_output(1, O)。
O = 1。

?- integer_output(3, O)。
O = '绒毛' ;
错误的。

我们也可以用它来编写单元测试,例如:

?- integer_output(5, 'Buzz' )。
真的 。

在这里,预期的输出已经指定,我们可以使用相同的关系来询问输出是否符合要求。这是关系的一个非常好的属性,如果我们只在系统终端上编写输出而不是像上面那样将其显式地作为谓词参数,那么就不会那么容易了。

但还有更多!我们也可以在另一个方向使用相同的关系,例如:“哪些整数导致输出?” 这里是:Buzz

?- integer_output(I, 'Buzz')。
5*_680#=我。

这是对早期测试用例的大规模概括,可以作为我们已经涵盖所有用例的额外保证。事实上,我们甚至可以进一步概括这一点,得到最一般的查询,询问答案的一般情况

?- integer_output(I, O)。
O ='模糊',
3*_742#=我;
O = '嗡嗡声',
5*_742#=我;
O = 'FizzBu​​zz',
5*_1014#=我,
3*_1038#=我。

让我们对输出进行更多推理。显然,我们期望每个可能的整数的输出都是唯一确定的,对吧?让我们通过询问该属性的反例来询问 Prolog是否如此:

?-差异(O1,O2),
   整数输出(I,O1),
   整数输出(I,O2)。
O1 =“绒毛”,
O2 = '嗡嗡声',
5*_1046#=我,
3*_1070#=我;
O1 =“绒毛”,
O2 = 'FizzBu​​zz',
5*_1318#=我,
3*_1342#=我,
3*_1366#=我。

现在看起来不太好:从上面看,我们已经怀疑可能存在相同整数 I产生两个不同的、同样合理的输出O1和 的情况O2

事实上,这是一个出现此问题的具体整数:

?-整数输出(15,O)。
O = '绒毛' ;
O = '嗡嗡声' ;
O = 'FizzBu​​zz' ;
错误的。

所以,事实证明,输出不是唯一确定的!让我们跟随我们的本能,马上问:

这是谁的错?

CLP(FD) 限制责任?

事实上,事实证明,使用声明式表述只是暴露了任务表述中的模糊性。过早地采用其中一种解决方案不会暴露这个问题。

可能的意思是一个任务描述,它在整数和输出之间引入以下关系:

integer_output(N, 'Fuzz') :- N #= 3*_, N mod 5 #\= 0。
integer_output(N, 'Buzz') :- N #= 5*_, N mod 3 #\= 0。
整数输出(N,'FizzBu​​zz'):- N #= 3*_,N #= 5*_。
整数输出(N,N):- N mod 3 #\= 0,N mod 5 #\= 0。

这产生:

?- integer_output(15, O)。
O = 'FizzBu​​zz' ;
错误的。

其他测试用例仍然按预期工作。

现在,使用此关系作为构建块,使用元谓词很容易将其提升到整数列表maplist/3

嘶嘶声(Ls):-
        numlist(1, 100, Ls0),
        地图列表(整数输出, Ls0,Ls)。

示例查询和回答:

?- fizz_buzz(Ls)。
Ls = [1, 2, 'Fuzz', 4, 'Buzz', 'Fuzz', 7, 8, 'Fuzz'|...] ;
错误的。

请注意,我们自己并没有写任何东西:我们使用 Prolog 顶层来为我们写东西,并推理arguments

优点很明显:我们可以再次为这样的谓词编写测试用例。例如,我们期望以下内容成立,并且确实如此:

?- Ls = [1,2|_] , fizz_buzz(Ls)。
Ls = [1, 2, 'Fuzz', 4, 'Buzz', 'Fuzz', 7, 8, 'Fuzz'|...] 。

到目前为止,一切都是完全纯净的,可以在各个方向使用。我将根据您的需要格式化此类解决方案作为一个简单的练习。

如果您的 Prolog 系统不提供numlist/3,您可以使用bagof/3来获取从 1 到 100 的整数列表,如下所示:

?- bagof (L, (L in 1..100,indomain(L)), Ls)。
Ls = [1, 2, 3, 4, 5, 6, 7, 8, 9|...]。

因此,bagof/3对于这项任务可能很有用,但我不建议将其用于副作用。

于 2017-04-02T23:05:25.300 回答
2

而不是bagof/3你可以使用if/3,像这样:

:- 使用模块(库(聚合),[forall/2])。
:- 使用模块(库(之间),[之间/3])。

嘶嘶声:-
   forall(介于(1,100,Z),fizzbuzz(Z))。

嘶嘶声(Z):-
   forall( if(integer_fb(Z,X), true, Z=X) , write(X)),
   写(' ')。

integer_fb(Z, 'Fizz') :- Z rem 3 =:= 0。
integer_fb(Z, 'Buzz') :- Z rem 5 =:= 0。

使用 SICStus Prolog 4.4.0 的示例输出:

| ?- fizzbuzz.
1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz Fizz 22 23 Fizz Buzz 26 Fizz 28 29 FizzBuzz 31 32 Fizz 34 Buzz Fizz 37 38 Fizz Buzz 41 Fizz 43 44 FizzBuzz 46 47 Fizz 49 Buzz Fizz 52 53 Fizz Buzz 56 Fizz 58 59 FizzBuzz 61 62 Fizz 64 Buzz Fizz 67 68 Fizz Buzz 71 Fizz 73 74 FizzBuzz 76 77 Fizz 79 Buzz Fizz 82 83 Fizz Buzz 86 Fizz 88 89 FizzBuzz 91 92 Fizz 94 Buzz Fizz 97 98 Fizz Buzz 
于 2018-03-03T16:30:09.777 回答
1

aggregate(count, fb(X), C) 允许计算解决方案,但基于 bagof,因此构建列表只是为了计算元素。然后我写了一个可重用的“积木”,早于 call_nth/2,来自这个@false 答案

:- meta_predicate count_solutions(0, ?).

count_solutions(Goal, C) :-
    State = count(0, _), % note the extra argument which remains a variable
    (   Goal,
        arg(1, State, C1),
        C2 is C1 + 1,
        nb_setarg(1, State, C2),
        fail
    ;   arg(1, State, C)
    ).

“应用”代码变为

:- use_module(uty, [count_solutions/2]).

fizzbuzz :- forall( between(1, 100, X), fizzbuzz(X) ).
fizzbuzz(X) :-
    ( count_solutions(fb(X), 0) -> write(X) ; true ), nl.

fb(X) :- X rem 3 =:= 0, write('Fizz').
fb(X) :- X rem 5 =:= 0, write('Buzz').
于 2013-01-11T14:29:29.253 回答
1

您可以使用某种模式匹配:

fizzbuzz :-
    forall( between(1, 100, X), fizzbuzz(X) ).
fizzbuzz(X) :-
    0 is X rem 15,
    format('~w FizzBuzz~n', [X]).

fizzbuzz(X) :-
    0 is X rem 5,
    format('~w Buzz~n', [X]).

fizzbuzz(X) :-
    0 is X mod 3,
    format('~w Fizz~n', [X]).

fizzbuzz(X) :-
    write(X), nl.
于 2013-01-11T08:15:11.160 回答
1

我采用@Kintalken 的想法,只需对您的原始代码进行最小的更改即可达到此解决方案:

fizzbuzz :-
    forall( between(1, 100, X),
        (   fizzbuzz(X, Output),
            format("~w~n", [Output])
        )).

fizzbuzz(X, Output) :-
    (   bagof(Y, fb(X, Y), Ys)
    ->  atomic_list_concat(Ys, Output)
    ;   X = Output
    ).

fb(X, 'Fizz') :- X rem 3 =:= 0.
fb(X, 'Buzz') :- X rem 5 =:= 0.

您问题中代码的唯一变化是,首先我收集解决方案,然后仅在一个地方打印,而不是作为收集的谓词的副作用,bagof因此您不再需要在内部产生副作用bagof

此外,正如您现在看到的,打印在第二个参数中,forall以便清楚地知道副作用发生在哪里,而不是隐藏在其他地方,并且该程序的所有副作用恰好有一个位置,而不是分散在不同谓词的子句之间。

另一件事是,我没有使用连词,不是因为它有任何不同,而是因为它传达了使用来收集解决方案或在没有解决方案时做其他事情的->意图。bagof当我阅读您的问题和答案以及对答案的评论时,这是讨论的一部分吗?

我不知道如何准确缩进forall. 我缩进的方式看起来可能还可以,但可能根本不行。betweenfizzbuzzformat现在对齐但只是fizzbuzzformat应该对齐,但between偶然对齐所以这不是故意的,也许它令人困惑,但我不喜欢这样的东西

forall(
    between(1, 100, X),
    (   fizzbuzz(X, Output),
        format("~w~n", [Output])
    ))

因为 thenforall(一个人在整条线上看起来很孤独,没有任何东西可以帮助它在悲伤的小开口时感到不那么孤独。

于 2017-03-30T11:56:19.660 回答
0

我......我会做类似的事情:

fizzbuzz( X , Y ) :-
  X =< Y ,
  R3 is X % 3 ,
  R5 is X % 5 ,
  map( R3 , R5 , X , V ) ,
  write(V) ,
  nl ,
  X1 is X+1 ,
  fizzbuzz( X1 , Y )
  .

map( 0 , 0 , _ , fizzbuzz ) :- ! .
map( 0 , _ , _ , fizz     ) :- ! .
map( _ , 0 , _ , buzz     ) :- ! .
map( _ , _ , X , X        ) :- ! .
于 2013-01-15T18:46:34.207 回答
0

好吧,您已经在使用它了;forall/2

write_fb(X) :-
    forall(fb(X), true).

或者,您可以更改问题的表示:

write_fb(X) :-
  (X rem 3 =:= 0 -> write('Fizz') ; true),
  (X rem 5 =:= 0 -> write('Buzz') ; true).

当然,在这种情况下,使用bagof/3and friends 就可以了,因为生成的列表非常小。

于 2013-01-11T11:09:18.337 回答
0

我尊敬的同事已经回答了,但你想要“之间”。

您不需要收集解决方案。因为你的直觉是正确的。我怀疑你从类似的东西开始

fizzbuzz :-  between(1, 100, N),
             fb(N).


fb(N) :-  N rem 5 =:= 0,  
          N rem 3 =:= 0,
          write(fizzbuzz).

fb(N) :-  N rem 5 =:= 0,    
          write(buzz).

fb(N) :-  N rem 3 =:= 0,
          write(fizz).

fb(N) :-  write(N).

问题在于 fb 不是“坚定的”——你不希望它为你提供多种解决方案,但确实如此——例如,fb(15) 与每个 fb 规则相结合。

解决方案是强制它坚定不移,使用剪切:

fizzbuzz :-  between(1, 100, N),
             fb(N).


fb(N) :-  N rem 5 =:= 0,  
          N rem 3 =:= 0,
          !,
          write(fizzbuzz).

fb(N) :-  N rem 5 =:= 0,
          !,    
          write(buzz).

fb(N) :-  N rem 3 =:= 0,
          !,
          write(fizz).

fb(N) :-  write(N).
于 2013-01-12T18:37:39.170 回答
0

为什么要滥用bagof/3副作用并就此止步?我们也可以滥用循环列表:

fizzbuzz :-
        Fizz = [fail,fail,write('Fizz')|Fizz],
        Buzz = [fail,fail,fail,fail,write('Buzz')|Buzz],
        fb(1, 100, Fizz, Buzz).

fb(N, N, _, _) :- !.
fb(N, Last, [F|Fs], [B|Bs]) :-
        (       bagof(_, ( F ; B ), _)
        ->      true
        ;       write(N)
        ),
        nl,
        succ(N, N1),
        fb(N1, Last, Fs, Bs).
于 2016-12-01T13:09:44.330 回答
0

问题:

打印从 1 到 100 的所有数字,但如果数字是 3 的倍数,则打印“Fuzz”,如果是 5 的倍数,则打印“Buzz”,如果两者都打印,则打印“FizzBu​​zz”。

我认为您遇到有关 bagof 的这种特殊性这一事实表明您的程序中有“气味”。我发现 Prolog 经常发生这种情况。Prolog 实际上是一个非常小的套件,提供的东西不多。随着时间的推移,我了解到,如果我需要一些不在那个最小套件中的东西,或者我的使用似乎背叛了内置功能的预期用途,那么几乎总是因为我当前的“气味”方法。

更多关于“气味”的信息:https ://en.wikipedia.org/wiki/Code_smell

我认为当我们概述您的程序当前需要的程序流程的一般草图时,您当前方法中的“气味”变得很明显:

  1. 产生
  2. 打印
  3. 转换

问题是您在“转换”之前尝试“打印”。您想在“转换”之后“打印”,如下所示:

  1. 产生
  2. 转换
  3. 打印

考虑到这一点,我们可以重写问题陈述:

新问题:

求解所有每个数字:

生成从 1 到 100 的每个数字,但将每个数字转换为每个消息,如果每个数字是 3 的倍数,则转换为每个消息“Fuzz”,如果每个数字是 5 的倍数,则转换为每个消息“Buzz”,转换为每条消息“FizzBu​​zz”,如果每个数字都是两者,则打印每条消息。

以下是旨在解决上述问题的程序。

程序列表后面是一个示例查询会话。

*/

/* -- prolog setup -- */

:-  ( op(10'1150,'yfx','forall')    )   .

:-  ( op(10'1150,'fy','if') )   .
:-  ( op(10'1140,'yfx','then') )    .
:-  ( op(10'1140,'yfx','else') )    .

(if IF then THEN else ELSE) :- (IF *-> THEN ; ELSE) .
(if IF then THEN) :- (IF *-> THEN ; (throw(false(IF)))) .

term_expansion((if IF then THEN else ELSE),((IF :- THEN *-> (true) ; ELSE)))    .
term_expansion((if IF then THEN),((IF :- THEN *-> (true) ; (throw(false(IF))))))    .

/* -- program -- */

if
(
  program(_)
)
then
(
  (
    if
    (
      generate(NUMBER)
    )
    then
    (
      true
    )
  )
  forall
  (
    if
    (
      transform(NUMBER,MESSAGE)
    )
    then
    (
      if
      (
        accept(NUMBER,MESSAGE)
      )
      then
      (
        if
        (
          echo(NUMBER)
        )
        then
        (
          echo(MESSAGE)
        )
      )
      else
      (
        true
      )
    )
  )
)
.

if
(
  generate(NUMBER)
)
then
(
  between(1,100,NUMBER)
)
.

if
(
  transform(NUMBER,MESSAGE)
)
then
(
  if
  (
    multiple_of_3(NUMBER)
  )
  then
  (
    if
    (
      multiple_of_5(NUMBER)
    )
    then
    (
      MESSAGE='FizzBuzz'
    )
    else
    (
      MESSAGE='Fuzz'
    )
  )
  else
  (
    if
    (
      multiple_of_5(NUMBER)
    )
    then
    (
      MESSAGE='Buzz'
    )
    else
    (
      % this contingency is undefined in the problem statement %
      true
    )
  )
)
.

if
(
  multiple_of_3(NUMBER)
)
then
(
  NUMBER rem 10'3 =:= 10'0
)
else
(
  false
)
.

if
(
  multiple_of_5(NUMBER)
)
then
(
  NUMBER rem 10'5 =:= 10'0
)
else
(
  false
)
.

if
(
  accept(NUMBER,MESSAGE)
)
then
(
  if
  (
    true
  )
  then
  (
    true
  )
  else
  (
    false
  )
)
else
(
  false
)
.

if
(
  echo(MESSAGE)
)
then
(
  if
  (
    writeq(MESSAGE)
  )
  then
  (
    nl
  )
)
.

/*

example query
=============

?-
program(_)
.

3
'Fuzz'
5
'Buzz'
6
'Fuzz'
9
'Fuzz'
12
'Fuzz'
15
'FizzBuzz'
18
'Fuzz'

[ ... and so on as expected ... ]

90
'FizzBuzz'
93
'Fuzz'
95
'Buzz'
96
'Fuzz'
99
'Fuzz'
100
'Buzz'
true.

?- 

然而,目前呈现的程序并不能完全产生上述预期的输出。

在上面的程序中有一个标记:

  % this contingency is undefined in the problem statement %

在程序 100% 令人满意之前需要解决问题。

使用 swi-prolog 和 yap 进行了测试。

于 2017-03-30T00:00:45.663 回答
-1

/*

问题陈述

解决所有每个数字:

生成从 1 到 100 的每个数字, 但是将每个数字转换为每个消息, 如果每个数字是 3 的倍数,则进入每个消息“Fuzz”, 如果每个数字是 5 的倍数,则进入每个消息“Buzz”, 如果每个数字都是,则进入每个消息“FizzBu​​zz”, 然后打印每条消息。

*/

/*

程序

*/

    :- use_module(library(clpfd)) .

    :- op(10'1,'yfx','forall') .
    :- op(10'1,'fy','once') .

    (
        program
    )
    :-
    (
        (
            between(1,100,NUMBER)
        )
        forall
        (
            once
            (
                (
                    MESSAGE='FizzBuzz'
                    ,
                    NUMBER rem 10'3 #= 10'0
                    ,
                    NUMBER rem 10'5 #= 10'0
                )
                |
                (
                    MESSAGE='Buzz'
                    ,
                    NUMBER rem 10'5 #= 10'0
                )
                |
                (
                    MESSAGE='Fuzz'
                    ,
                    NUMBER rem 10'3 #= 10'0
                )
                |
                (
                    MESSAGE=_
                )
            )
            ,
            once
            (
                (
                    nonvar(MESSAGE)
                    ,
                    writeq(NUMBER)
                    ,
                    nl
                    ,
                    writeq(MESSAGE)
                    ,
                    nl
                )
                |
                (
                    true
                )
            )
        )
    )
    .

/*

测试

在 swi prolog 中测试。*/

    ?- program .

    3
    'Fuzz'
    5
    'Buzz'
    6
    'Fuzz'
    9
    'Fuzz'
    10
    'Buzz'
    12
    'Fuzz'
    15
    'FizzBuzz'

    { .e.t.c. | ... as expected ... |  .e.t.c. }

    99
    'Fuzz'
    100
    'Buzz'
    true.

    ?- 

*/

于 2017-04-02T18:12:13.000 回答