Prolog打高尔夫球的秘诀


16

您在Prolog中打高尔夫球有哪些一般秘诀?我正在寻找可以应用于编码高尔夫问题的想法,这些想法至少在某种程度上是特定于Prolog的(例如,一个字母变量并不特定于Prolog以减小程序的大小)。

请在提示中指出它是否特定于Prolog的实现(例如,SWI-Prolog特定的内置程序)

请为每个答案仅张贴一个提示,或与相同主要思想密切相关的提示列表。


1
prolog标签是有点没用。除非我们有口译Prolog挑战,否则我们就不需要它。

Answers:


10

将运算符用于谓词名称

只要谓词运算符是预定义运算符之一(在此处列出)并且尚未被定义为谓语,就可以将谓词运算符作为名称来使用。定义和调用谓词时,这可以节省一些字节,因为运算符的谓词不需要以普通形式编写name(arg1,arg2,etc..)形式并且可以像对运算符所期望的那样进行调用。

对于一个和两个参数谓词,分别可以给它们一元和二元运算符的名称。对于较高arity的谓词,我们仍然可以使用模式匹配来避免括号。例如,如果我们有一个谓词A+B+C:-...,则Prolog将使用其运算符优先级和关联性规则将其转换(A+B)+C:-...为第一个参数与模式匹配的运算符谓词A+B。或A-B+C*D:-...变为,(A-B)+(C*D)因此其第一个参数与模式匹配,A-B第二个参数与模式匹配C*D

例子

_+_+_.
A-B+C*D:-between(A,B,C),C+D.
\X:-X>1,X<10.
X+Y:-length(Y,X),member(X,Y).



5+[10,5,3,2,5],a+b+c,0-20+X*[2,4,6,5,40],\9.

输出将是 X = 5.

在线尝试!

推论

由于DCG是谓词的语法糖,因此它们也可以被赋予名称的运算符。从DCG或使用phrase谓词或其他旨在与DCG一起使用的谓词将它们作为DCG调用时,这可以按预期工作。当将它们作为谓词调用时,需要使用括号(例如,A+B-->...必须像+(A,B,...)),因为DCG谓词在其差异列表中还要使用另外两个参数。对于使用两个以上两个参数的运算符命名为DCG的运算符,使用运算符模式匹配时,重要的是要确保在调用它时作为谓词,以确保模式匹配的运算符正确分配。

如果需要在程序中调用不带附加参数的DCG,则将其赋给操作符名称会很有用,因为这样您就可以不使用括号了。需要小心,因为在这种情况下,您保存在括号中的内容可能会丢失为解析相邻运算符所需的额外间隔。

例子

/ -->a+b+X,X+d+e.
A+B+C-->[A],[B],[C].


X/[],member(c,X),phrase(f+o+o,Y),+(b+a,r,Z,[]).

输出将是

X = [a, b, c, c, d, e],
Y = [f, o, o],
Z = [b, a, r].

在线尝试!

注意事项

对于一元运算符+-,Prolog将解释为+20-20为数字,而不是调用a +/1-/1谓词。仍可以使用括号(,)在数字上调用以一元+-名称命名的谓词。如果避免从括号中的额外字节是期望的其它一元运算符,例如,等可以用来代替为名称。+(20)-(20)\$

模式匹配和运算符命名谓词的结合并非完全没有缺陷。如果您有两个谓词与它们的名称具有相同的运算符,并且模式匹配严格,则一个谓词比另一个谓词更通用,则可能首先调用更通用的谓词,或者如果通用性较差的谓词失败(取决于它们在源中的顺序) 。例如,在上面的示例中,如果A-B+C*D未能匹配其输入,则Prolog将尝试调用X+Y。这将导致错误,因为length/2require Y必须是一个整数,因为它将采用形式,所以它不是整数C*D。通过确保没有两个谓词具有与其名称相同的运算符,可以简单地避免这种情况;如果失败,则可以使用剪切和仔细排序源。


3
令人惊讶的是,此技巧对代码高尔夫有用。仅使用此技巧,我就将答案的字节数减少了16%!
DLosc

6

尝试将所有可能的情况纳入一条规则

在Prolog中进行编程的干净方法是为同一谓词声明多个规则。例如,使用累加器反转列表的谓词看起来像这样:

r([],Z,Z).
r([H|T],Z,R):-r(T,[H|Z],R).

在Code-golf中,我们可以删除第一条规则,并;在第二条规则的末尾添加一个以对递归端进行编码:

r([H|T],Z,R):-r(T,[H|Z],R);R=[H|Z].

我们知道第一个条件 r(T,[H|Z],R)如果T为空(即,递归需要结束)将失败,因此可以在其后添加一个终止符作为or子句。

相同的原理在很多情况下都适用。但是请注意,有时实际上声明另一个规则比执行此​​规则要短。


6

一个经常有用的技巧:使用CLP(FD)约束对整数算术来获取可以在多个方向上自动使用的谓词,从而避免出现条件以及专用的分支和变体。

使用B-Prolog或GNU Prolog,这些约束可以直接使用,而无需加载任何库。


2
在这里进行挑战时,我总是讨厌SWI-Prolog。使用CLPFD会使某些事情变得更简洁,更短,但是您必须添加很多额外的代码才能使其正常工作(其中包含很多内容……)通常不值得这样做。我想我只在这个答案中用过它。
致命

3
我也很讨厌,并且可以肯定的是,最终的时机将在SWI-Prolog中library(clpfd)作为预加载或至少自动加载的库提供。所有用户都可能需要花费几年的时间才能完全理解并理解声明式算法,而这些用户到目前为止已经积累了数十年的过时,低级功能的经验。您使用和倡导CLP(FD)的次数越多,默认情况下我们就会越早获得它。在那之前,你可以简单地把:- use_module(library(clpfd)).你的~/.swiplrc,只是说明你正在使用SWI-Prolog的的“变种”。

6

使用算术运算符作为元组构造函数和缺点对

如果您需要传递包含两个或多个值的单个结构,则最明显的使用方法是列表,例如[A,B]。不过,这确实很冗长。

还有一种选择。Prolog值可以存储几乎任意的嵌套结构,该结构不会进行评估。这是显示其工作原理的示例:

| ?- member(member(A,B),C).
C = [member(A,B)|_] ? ;
C = [_,member(A,B)|_] ? ;
(etc.)

member(A,B)在这种情况下,它只是一个命名的元组,而外部member(这是一个函数调用)将其视为此类。

尽管命名元组在非高尔夫Prolog编程中非常有用,但它们似乎比列表方法更冗长。但是,我们可以在元组构造函数的名称中使用几乎任意的字符(假设它们被正确地引用了);而不是可爱的东西member或单个字符的东西a,我们可以做这样的事情:

| ?- A = '-'('/'(1,2), '/'(3,4)).
A = 1/2-3/4

在这里,我们的元组构造函数是'-''/'。有趣的是,漂亮打印机对它们做了什么;它对元组使用中表示法。这确实很简洁,并且以与可比算术运算相同的方式进行解析。(这也解释了为什么算术is不使用=; A = 1+2A元组 统一'+'(1,2),因此需要使用单独的语法来实际评估未评估的算术表达式。)因为必须调用元组构造函数,所以某些事情,你不妨使用具有简洁的字符语法(作为奖励,-以及/当它们想要快速抛弃元组构造函数而不是有意义的东西时,它们也是非Golf代码中最常见的选择,其方式与方式大致相同i 通常用作循环变量,因此,如果由于某种原因而在其中想要一个元组,在输入和输出中使用它们是完全合理的)。

'-'并且'/'是元组构造函数的不错选择,因为它们具有良好的行为习惯和有用的优先级,使您可以简洁地编写元组文字。但是,请注意,在程序内部生成中间值时,您不必担心优先级。Prolog将元组存储为树而不是源代码,漂亮的打印机可以明确地输出它:

| ?- A = '-'('-'(1,2), '-'(3,4)).
A = 1-2-(3-4)

因为元组语法非常简洁(f(A,B)不短于f(A-B)),因此您可以免费用元组替换多个谓词参数,这意味着,如果一个谓词需要将其两个或多个参数传递给另一个谓词,则通常可以将它们形成为一个元组并只传递该元组(尽管这将需要更改除谓词本身之外的所有对谓词的调用,以使用元组构造函数和逗号的适当组合)。

这种语法的另一个优点是,如果您需要在内部使用列表(而不是与标准谓词进行互操作);列表基本上只是一组嵌套的cons单元格,而cons单元格只是具有Constructor的元组'.',如下所示:

| ?- Q = '.'('.'(A,B),'.'(C,D)).
Q = [[A|B],C|D]

如果您的代码“手动”使用列表,那么使用不那么笨重的元组构造函数可能会很有意义'.'。对我来说,一个常见的选择是将一个cons单元表示为'/'(Tail,Head)(因为这是您在调试输出中可以获得的最可读的信息,而不会浪费字符)。请注意,您可能还需要自己的[]同等产品。您可以使用,[]但是它有两个字节长,并且可以使用很多一字节的原子(所有小写字母)。

因此,例如,以下列表:

[1,2,3]

可以转换为具有相同数量字符的手动表示形式,如下所示:

x/3/2/1

同时获得了优势,[H|T]样式样式匹配现在可以写得更简洁T/H,而对空列表的测试只是写x而不是更长[]。(当然,这带有明显的缺点是memberappend等,将无法在此表示的工作。)


+1!使用-和时不需要单引号/。这些已经是完全正常的原子了。

并且,如果.以下字符既不是a %也不是布局,则不需要在双引号周围。
false

5

列表列表的较短语法和声明映射的方法

您可以将字节保存在列表列表中。如果有一个列表[[1,2],[3,4]],则实际上可以将其声明为[1:2,3:4],这样可以节省4个方括号= 4个字节。请注意,您可以使用以外的其他名称:(例如,^)。

1:2在那种情况下(实际上是)实际上不是列表[1,2],它在内部表示为:(1,2)。因此,您不能使用在使用冒号的那些子列表上的列表上工作的谓词。

该技巧主要用于声明映射,即带有键值的键列表。例如,如果要声明M包含英语和法语数字拼写的地图,则可以执行以下操作:

M=[0:'Zero':'Zéro',1:'One':'Un',2:'Two':'Deux', ... ]

然后,您可以使用内置谓词(例如)来检索地图的元素member/2。例如,如果您想要与'Quatre'in 相对应的数字和英文单词M,则可以执行以下操作:

member(Digit:Name:'Quatre',M).

1
您可以对此进行概括。例如,您可以将wirte设置[[1,2],[3,4]]1*2+3*4,即+(*(1,2),*(3,4)),因此也仅使用一个字节,否则需要两个字节来打开和关闭括号。

双引号列表更短:"abc"代替[a,b,c]
false

5

一个巧妙的技巧:当您需要失败时,请使用与false / 0等效  但较短的内容,例如:

?-重复,writeln(hi),0 = 1

3
Prolog的强制性语录:“ 在Prolog编程中(也许相反,对于一般生活而言),我们的目标是尽快失败 ”。有趣的事实:你也可以使用\+!一个字节3不可思议的方式失败,它实际上并不触发切口!(见原因)。我认为失败少于3个字节是不可能的。
致命

我也考虑过这个问题时,引用我写这篇;-)

2
我们确实需要这两个版本,\+!将左侧的胶水粘贴到其他图形字符上,而0=1将左侧的胶水粘贴为名称。

5

重用具有不同调用方式的谓词

例如,您可以解析和打印具有相同谓词的结构,一次使用变量参数,而另一次使用底语。我在“ 制作可拉伸的蛇之吻”中使用了这种方法。当然,在所有挑战中这都是不可能的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.