仅使用bagof / 3作为副作用

问题描述 投票:2回答:11

在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作为其副作用吗? (我总是有点不确定只使用谓词的副作用)。

prolog
11个回答
3
投票

作为对现有答案的补充,我想展示一个更多的关系解决方案,我希望能够说明将逻辑编程这样的声明性编程范例应用于这个问题的一些独特优势。

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

print all numbers from 1 to 100, but instead of the number, print...

  • '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 .

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

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

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

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

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

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

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

WHOSE FAULT IS THIS?

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.

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

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

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系统不提供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可用于此任务,但我不建议将其用于副作用。


0
投票

问题:

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

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

更多关于“气味”的信息:*/ /* -- 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. ?-

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

  1. 生成
  2. 打印
  3. 转变

问题是你在“变换”之前试图“打印”。您希望在“转换”之后“打印”,如下所示:

  1. 生成
  2. 转变
  3. 打印

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

新问题:

解决所有每个数字:

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

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

程序列表之后是​​示例查询会话。

  % this contingency is undefined in the problem statement %

但是,目前提出的程序并没有完全产生如上所述的预期输出。

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

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

在该计划100%满意之前,需要解决问题。

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


-1
投票

/*

problem statement

solve for all each number   :

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

*/

/*

program

*/

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

    ?- 

/*

testing

测试swi prolog。 * /

qazxswpoi

*/


2
投票

而不是bagof/3你可以使用if/3,如下所示:

:- use_module(library(aggregate), [forall/2]).
:- use_module(library(between),  [between/3]).

fizzbuzz :-
   forall(between(1,100,Z),  fizzbuzz(Z)).

fizzbuzz(Z) :-
   forall(if(integer_fb(Z,X), true, Z=X),  write(X)),
   write(' ').

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 

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.

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

'适用'代码成为

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

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来收集解决方案的意图,或者如果没有解决方案则做其他事情。当我阅读你的问题和答案以及对答案的评论时,这是讨论的一部分吗?

我不知道如何缩进between的内容。我缩进的方式看起来可能还可以,但也许根本不行。 fizzbuzzformatfizzbuzz现在已经对齐,但只有formatbetween应该已经对齐,但forall( between(1, 100, X), ( fizzbuzz(X, Output), format("~w~n", [Output]) )) 意外排列,所以这不是故意的,也许是令人困惑,但我不喜欢这样的东西

forall(

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


0
投票

好吧,你已经在使用它; 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

当然,在这种情况下,使用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). 和朋友很好,因为生成的列表非常小。


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( 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        ) :- ! .

0
投票

我......我会这样做:

bagof/3

0
投票

为什么滥用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). 的副作用,只是停在那里?我们还可以滥用循环列表:

https://en.wikipedia.org/wiki/Code_smell
© www.soinside.com 2019 - 2024. All rights reserved.