在Prolog中改进范围内列表生成

10

我对Prolog相当新,并且越来越爱它。我想知道这个实现是否可以进一步概括或改进,以及它是否是惯用的Prolog代码?

%% range/2
range(End, List) :-
    End > 0, !,
    range_ascend(0, End, 1, List).

range(End, List) :-
    End < 0,
    range_descend(0, End, 1, List).

%% range/3
range(Start, End, List) :-
    ((Start =< End) ->
        (range_ascend(Start, End, 1, List))
    ;
        (range_descend(Start, End, 1, List))).

%% range/4 (+Start, +End, +Step, -List)
range(Start, End, Step, List) :-
    ((Start =< End) ->
        (range_ascend(Start, End, Step, List))
    ;
        (range_descend(Start, End, Step, List))).

range_descend(Start, End, _, []) :-
    End >= Start, !.
range_descend(Start, End, Step, [Start|Rest]) :-
    Next is Start - Step,
    range_descend(Next, End, Step, Rest).

range_ascend(Start, End, _, []) :-
    Start >= End, !.
range_ascend(Start, End, Step, [Start|Rest]) :-
    Next is Start + Step,
    range_ascend(Next, End, Step, Rest).

1
我觉得在range/2的情况下,如果End是负数,那么生成一个降序列表有点奇怪。此外,它似乎没有考虑到End = 0 - Willem Van Onsem
Xs = [0,1],range(1,Xs)。会错误地成功,而range(1,Xs),Xs = [0,1]。则会正确地失败。因此,该谓词不是坚定的。向执政官们致以问候! - false
1个回答

5

你的实现主要问题之一是它只能“单向”工作,也就是说,当End设置为某个值时,你的代码可以正常运行,但当它是一个变量时,代码无法正常工作:

?- range(X,[0,1,2,3]).
ERROR: >/2: Arguments are not sufficiently instantiated

也许在其他编程语言中,你不需要这样的行为来运行程序,但在Prolog中,你的谓词通常应该作为真实关系运行,以便以多种不同的方式工作,这既是理想的,也是优雅的。
然而,将谓词实现为这样的功能通常比将其实现为函数式的更加困难,特别是对于初学者来说。
我不打算详细介绍如何具体改进您的代码(我认为这更适合于Code Review SE网站),但是我在下面提供了一个比您的行为更好的范围谓词:
range(I, S, [I|R]) :-
    I #=< S,
    if_(I = S,
        R = [],
        (   J #= I + 1,
            range(J, S, R)
        )
    ).

这个谓词需要从library(reif)导入if_/3(=)/3,以及library(clpfd)(你可以使用:- use_module(library(clpfd)).将其包含在程序中)。
正如您所看到的,它比您的实现要短得多,但在多种不同的情况下也能很好地工作。
?- range(0,5,L).             % Same behavior as your predicate
L = [0, 1, 2, 3, 4, 5].

?- range(-5,0,L).            % Different behavior from your predicate, but logically sounder
L = [-5, -4, -3, -2, -1, 0]

?- range(1,S,[1,2,3,4,5]).   % Finding the max of the range
S = 5 ;
false.

?- range(I,S,[1,2,3,4,5]).   % Finding both the min and max of the range
I = 1,
S = 5 ;
false.

?- range(I,S,[1,2,X,Y,5]).   % With variables in the range
I = 1,
S = 5,
X = 3,
Y = 4 ;
false.

?- range(1,S,L).             % Generating ranges
S = 1,
L = [1] ;
S = 2,
L = [1, 2] ;
S = 3,
L = [1, 2, 3] ;
S = 4,
L = [1, 2, 3, 4] ;
…

?- range(I,1,L).             % Generating ranges
I = 1,
L = [1] ;
I = 0,
L = [0, 1] ;
I = -1,
L = [-1, 0, 1] ;
I = -2,
L = [-2, -1, 0, 1] ;
…

?- range(I,S,L).             % Generating all possible ranges
I = S,
L = [S],
S in inf..sup ;
L = [I, S],
I+1#=S,
S#>=I,
dif(I, S) ;
L = [I, _G6396, S],
I+1#=_G6396,
S#>=I,
dif(I, S),
_G6396+1#=S,
S#>=_G6396,
dif(_G6396, S) ;
…

我认为你可以看到这里展示了多少行为,以及只使用一个谓词就能访问它们有多有用。
这个谓词使用了“约束逻辑编程”(即“clpfd”库)。CLP允许写关于整数之间的关系(在代码中看到的“#=<”和“#=”,而不是基本算术运算中使用的“=<”和“is”符号)。这正是大部分重活的完成方式, 它使我们能够简洁地对整数进行陈述性说明(这是用"is"很难做到的)。
我建议您阅读Markus Triska撰写的Prolog算术章节的力量,学习Prolog中的CLP算术。如果您打算严肃地使用Prolog(正如我在此回答中所希望的那样),那么这绝对是您需要学习的主题。

OP的主要问题是缺乏坚定性。 - false
2
library(reif) 可以在 SICStusSWI 中使用! - false

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接