Prolog谓词参数:可读性与效率

Prolog谓词参数:可读性与效率,prolog,Prolog,我想问一下谓词参数中不同Prolog表示法的优缺点 例如,在中:编写一个谓词second(X,List),它检查X是否是List的第二个元素。解决方案可以是: second(X,List):- [_,X|_]=List. 或者 这两个谓词的行为相似。至少对我来说,第一本书比第二本书更具可读性。但是第二个在执行过程中使用了更多的堆栈(我用trace检查了这一点) 一个更复杂的例子是:二叉树是所有内部节点正好有两个子节点的树。最小的二叉树只包含一个叶节点。我们将叶节点表示为叶(标签)。例如,叶(3

我想问一下谓词参数中不同Prolog表示法的优缺点

例如,在中:编写一个谓词second(X,List),它检查X是否是List的第二个元素。解决方案可以是:

second(X,List):- [_,X|_]=List.
或者

这两个谓词的行为相似。至少对我来说,第一本书比第二本书更具可读性。但是第二个在执行过程中使用了更多的堆栈(我用trace检查了这一点)

一个更复杂的例子是:二叉树是所有内部节点正好有两个子节点的树。最小的二叉树只包含一个叶节点。我们将叶节点表示为叶(标签)。例如,叶(3)和叶(7)是叶节点,因此是小的二叉树。给定两棵二叉树B1和B2,我们可以使用函子树/2将它们组合成一棵二叉树,如下所示:tree(B1,B2)。因此,从叶(1)和叶(2)我们可以建立二叉树(叶(1),叶(2))。从二叉树树(叶(1),叶(2))和叶(4)我们可以构建二叉树树(树(叶(1),叶(2)),叶(4))。现在,定义一个谓词swap/2,它生成作为其第一个参数的二叉树的镜像。解决办法是:

A2.1:

或者

A2.2:

第二个解决方案的步骤数远少于第一个(同样,我使用trace进行了检查)。但是关于可读性,我认为第一个更容易理解

可能可读性取决于一个人的序言技能水平。我是一个学习者的Prolog级别,我习惯于用C++、Python等编程,所以我怀疑熟练的Prolog程序员是否同意上述的可读性。 此外,我想知道步数是否可以很好地衡量计算效率

你能给我一些关于谓词参数设计的意见或指导方针吗


编辑

根据@coder的建议,我制作了第三个版本,其中包含一条规则:

A2.3:

我比较了每个解决方案跟踪中的步骤数:

  • A2.1:36个步骤
  • A2.2:8个步骤
  • A2.3:32个步骤
A2.3(可读的单规则版本)似乎优于A2.1(可读的四规则版本),但A2.2(不可读的四规则版本)仍优于A2.1

我不确定跟踪中的步数是否反映了实际的计算效率。 A2.2中的步骤较少,但在参数的模式匹配中使用了更多的计算成本。 因此,我比较了40000个查询的执行时间(每个查询都是一个复杂的查询)、swap(树(树(树(树(树)(叶(3)、叶(4))、叶(5))、树(树(树)(树(树)(叶(3)、叶(4))、叶(5))、树(树)(叶(3)、叶(4))、叶(5))、树(树(树)(树(树)(叶(树)(叶(3)、叶(4))、叶(5))、叶(4))、)。结果几乎相同(分别为0.954秒、0.944秒和0.960秒)。这表明三种重表示A2.1、A2.2、A2.3具有相近的计算效率。
你同意这个结果吗?(可能这是一个具体的案例;我需要改变实验设置)。

在第一种方式中,例如练习3.5,您使用规则
交换(T1,T2)
四次,这意味着prolog将检查所有这四个规则,并将为这四个调用中的每一个返回true或fail。因为这些规则不能同时都为true(每次其中一个将返回true),对于每个输入,您将浪费三个不会成功的调用(这就是为什么它需要更多步骤和更多时间)。上述情况的唯一优点是,使用第一种方式编写规则更具可读性。通常,在模式匹配的情况下,最好使用定义良好的方式编写规则,而不是使用两种(或更多)方式编写规则规则匹配输入,当然,如果您只需要一个答案,例如,编写上述示例的第二种方法。 最后,一个需要多个规则匹配输入的示例是编写输入的谓词成员:

member(H,[H|_]).
member(H,[_|T]):- member(H,T). 
在这种情况下,您需要不止一个答案

在第三种方法中,您只需编写第一种方法,而无需模式匹配。它的形式为
(条件1);…;(条件4)
如果条件1没有返回true,它将检查下一个条件。大多数情况下,第四个条件返回true,但它调用并测试了返回false的条件1-3。因此,这几乎是编写解决方案的第一种方法,但在第三个解决方案中,如果找到true条件1,它将不会测试其他条件条件,这样您将节省一些浪费的呼叫(与解决方案1相比)。
至于运行时间,预计几乎相同,因为在最坏的情况下,解决方案1和3的测试/调用次数是解决方案2的四倍。因此,如果解决方案2是O(g)复杂度(对于某些函数g),那么解决方案1和3是O(4g),即O(g)复杂性,因此运行时间将非常接近。

在第一种方法中,例如在练习3.5中,您将使用规则
交换(T1,T2)
四次,这意味着prolog将检查所有这四个规则,并将为这四个调用中的每一个返回true或fail。因为这些规则不能同时为true(每次其中一个将返回true),对于每个输入,您将浪费三个不会成功的调用(这就是为什么它需要更多步骤和更多时间)。上述情况的唯一优点是,使用第一种方式编写规则更具可读性。通常,在模式匹配的情况下,最好使用定义良好的方式编写规则,而不是使用两种(或更多)方式编写规则规则匹配输入,当然,如果您只需要一个答案,例如,编写上述示例的第二种方法。 最后,一个需要多个规则匹配输入的示例是编写输入的谓词成员:

member(H,[H|_]).
member(H,[_|T]):- member(H,T). 
在这种情况下你在哪里
swap(tree(leaf(L1),leaf(L2)), tree(leaf(L2),leaf(L1))).
swap(tree(tree(B1,B2),leaf(L3)), tree(leaf(L3),T3)):- swap(tree(B1,B2),T3).
swap(tree(leaf(L1),tree(B2,B3)), tree(T3,leaf(L1))):- swap(tree(B2,B3),T3).
swap(tree(tree(B1,B2),tree(B3,B4)), tree(T4,T3)):- swap(tree(B1,B2),T3),swap(tree(B3,B4),T4).
swap(T1,T2):-
    ( T1=tree(leaf(L1),leaf(L2)), T2=tree(leaf(L2),leaf(L1)) );
    ( T1=tree(tree(B1,B2),leaf(L3)), T2=tree(leaf(L3),T3), swap(tree(B1,B2),T3) );
    ( T1=tree(leaf(L1),tree(B2,B3)), T2=tree(T3,leaf(L1)), swap(tree(B2,B3),T3) );
    ( T1=tree(tree(B1,B2),tree(B3,B4)), T2=tree(T4,T3), swap(tree(B1,B2),T3),swap(tree(B3,B4),T4) ).
member(H,[H|_]).
member(H,[_|T]):- member(H,T). 
mirror(nil, nil).
mirror(t(X, L, R), t(X, MR, ML)) :-
    mirror(L, ML),
    mirror(R, MR).
mirror_x(T, MT) :-
    (   T = nil
    ->  MT = nil
    ;   T = t(X, L, R),
        MT = t(X, MR, ML),
        mirror_x(L, ML),
        mirror_x(R, MR)
    ).
read_string(In_stream, _, Input),
split_string(Input, "\n", "", Lines),
maplist(do_x, Lines, Xs),
atomics_to_string(Xs, "\n", Output),
format(Out_stream, "~s\n", Output)