仅使用bagof / 3作为副作用

时间:2013-01-11 06:30:01

标签: prolog

在Prolog中解决一个非常简单的练习:打印1到100之间的所有数字,但不是数字,如果数字是3的倍数,则打印'Fuzz',如果是5的倍数,则打印'Buzz',如果是5,则打印'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作为副作用吗? (我总是有点不确定只使用谓词来表示副作用)。

10 个答案:

答案 0 :(得分:3)

补充现有答案,我想展示一个更强大的关系解决方案,我希望能够说明将声明性编程范例(如逻辑编程)应用于此的一些独特优势问题

首先,让我们概括一下这个任务:

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

  • 'Fuzz'如果数字是3的倍数
  • 'Buzz'如果是5的多个
  • 如果两者都是
  • 和'FizzBu​​zz'。

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

为简单起见,我们首先将自己局限于单个整数,让我们描述这样的整数和所需输出之间的关系

上面提到的三种情况可以直接翻译成Prolog,使用Prolog系统的 CLP(FD)约束进行声明性整数运算:

integer_output(N, 'Fuzz')     :- N #= 3*_.
integer_output(N, 'Buzz')     :- N #= 5*_.
integer_output(N, 'FizzBuzz') :- N #= 3*_, N #= 5*_.

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

?- integer_output(4, N).
false.

因此,我们还需要一个案例,我们可以将其表述为:

integer_output(N, N)          :- N mod 3 #\= 0, N mod 5 #\= 0.

这简单地说明,如果没有其他情况适用,我们按输出数字。由此产生的关系非常普遍。例如,我们可以将它用于具体整数:

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

?- integer_output(3, O).
O = 'Fuzz' ;
false.

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

?- integer_output(5, 'Buzz').
true .

这里,预期的输出是已经指定,我们可以使用相同的关系来询问是否输出将是必需的。这是关系的一个相当不错的属性,如果我们只在系统终端上编写输出而不是像上面那样将它显式化为谓词参数,那就不会那么容易了。

但还有更多!我们也可以在其他方向使用相同的关系,我们要求例如:“哪个整数会导致输出{{1} }?”这是:

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

这是早期测试案例的大量概括,可以作为我们已涵盖所有案例的额外保证。事实上,我们甚至可以进一步概括这一点,从而导致最常见的查询,它会询问答案如何看起来像一般

?- integer_output(I, O).
O = 'Fuzz',
3*_742#=I ;
O = 'Buzz',
5*_742#=I ;
O = 'FizzBuzz',
5*_1014#=I,
3*_1038#=I.

让我们更多地了解输出。显然,我们希望每个可能的整数的输出唯一确定,对吧?让我们询问Prolog 是否是这样,请求此属性的反例

?- dif(O1, O2),
   integer_output(I, O1),
   integer_output(I, O2).
O1 = 'Fuzz',
O2 = 'Buzz',
5*_1046#=I,
3*_1070#=I ;
O1 = 'Fuzz',
O2 = 'FizzBuzz',
5*_1318#=I,
3*_1342#=I,
3*_1366#=I .

现在看起来并不好:从上面开始,我们已经怀疑可能相同整数Buzz的情况,产生两个不同的,同样合理,输出IO1

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

?- integer_output(15, O).
O = 'Fuzz' ;
O = 'Buzz' ;
O = 'FizzBuzz' ;
false.

事实证明,输出是唯一确定的!让我们按照自然的直觉立即提出要求:

这是什么原因?

CLP(FD)约束阻止?

事实上,事实证明,使用声明性公式只是暴露了任务公式中的含糊不清。过早地提交其中一个解决方案并不会暴露这个问题。

可能意味着是一个任务描述,它会在整数和输出之间产生以下关系:

integer_output(N, 'Fuzz')     :- N #= 3*_, N mod 5 #\= 0.
integer_output(N, 'Buzz')     :- N #= 5*_, N mod 3 #\= 0.
integer_output(N, 'FizzBuzz') :- N #= 3*_, N #= 5*_.
integer_output(N, N)          :- N mod 3 #\= 0, N mod 5 #\= 0.

这会产生:

?- integer_output(15, O).
O = 'FizzBuzz' ;
false.

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

现在,使用这种关系作为构建块,使用元谓词O2 <<很容易提升列表整数/ p>

fizz_buzz(Ls) :-
        numlist(1, 100, Ls0),
        maplist(integer_output, Ls0, Ls).

示例查询和回答:

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

请注意,我们自己写任何东西:我们正在使用Prolog toplevel 为我们写作,并推理参数

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

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

到目前为止,一切都是完全纯净的,可用于各个方向。我将格式化这样的解决方案留作您想要的简单练习。

如果您的Prolog系统未提供maplist/3,您可以使用 numlist/3 获取1到100之间的整数列表,如下所示:

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

因此,bagof/3可用于此任务,但我不建议将其用于副作用。

答案 1 :(得分: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.

答案 2 :(得分:1)

aggregate(count,fb(X),C)允许计算解决方案,但是基于bagof,因此构建列表只是为了计算元素。然后我从this @false回答

写了一个可重用的'building block',比call_nth / 2更早。
:- 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)
    ).

'applicative'代码变为

:- 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').

答案 3 :(得分: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的内容。我缩进的方式看起来可能还可以,但也许根本不行。 betweenfizzbuzz以及format现已对齐,但只有fizzbuzzformat应该已对齐,但between意外对齐,所以这是不是故意的,也许是令人困惑的,但我不喜欢这样的东西

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

因为forall(看起来如此孤独,一个人独自在一条线上,没有任何东西帮助它感到不那么孤独与它悲伤的小开口paren。

答案 4 :(得分: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/3和朋友很好,因为生成的列表非常小。

答案 5 :(得分: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).

答案 6 :(得分: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        ) :- ! .

答案 7 :(得分: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).

答案 8 :(得分:0)

问题:

  

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

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

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

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

  1. 产生
  2. 打印
  3. 变换
  4. 问题是你在“变换”之前试图“打印”。 您希望在“转换”之后“打印”,如下所示:

    1. 产生
    2. 变换
    3. 打印
    4. 考虑到这一点,我们可以重写问题陈述:

      新问题:

      解决所有数字:

        

      生成从1到100的每个数字,但将每个数字转换为每条消息,如果每个数字是3的倍数,则将每个数字转换为每个消息“Fuzz”,如果每个数字是“Buzz”,则每个数字是5,进入每条消息'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进行测试。

答案 9 :(得分:-1)

/ *

问题陈述

solve for all each number   :

generate each number from 1 to 100 , but transform each number into an each message , into each message 'Fuzz' if the each number is a multiple of 3 , into each message 'Buzz' if the each number is a multiple of 5 , into each message 'FizzBuzz' if the each number is both , then print each message .

* /

/ *

程序

* /

    :- 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.

    ?- 

* /