2

我正试图围绕 CLP(FD)。这是一个简单的例子,我不确定最惯用的方法。假设我们有一个数字列表 ( L),其中一些数字已经填充,如下所示。

L = [_, _, _, 3, _, _, _, 4, _, _, 2, _]

我想说的是,一个固定数字的左右数字必须加总该数字。上述情况的可能解决方案是:

L = [0, 0, 1, 3, 0, 1, 1, 4, 2, 0, 2, 0]

当然,还有其他解决方案。我的第一种方法是:

  1. 搜索第一个枢轴号;
  2. 获取pivot的左右子列表;
  3. Append两个列表;
  4. 使用 predicate sum/3,将结果列表约束为#=枢轴。

这会是意识形态的吗?我错过了更明显的东西吗?


2 级。我认为谓词sum/3完成了大部分工作。让我们稍微改变一下问题:

枢轴左侧和右侧的连续 1 字符串必须求和到枢轴。

所以鉴于此:

L = [_, _, _, 3, _, _, _, 5, _, _, 2, _]

一个可能的解决方案是:

L = [0, 0, 0, 3, 1, 1, 1, 5, 1, 1, 2, 0]

现在更棘手了……我想说的是,枢轴 N 的附加左右子列表必须包含长度为 N 的 1 的子列表。这有意义吗?我将如何使用 CLP(FD) 来表达这些观点?

4

1 回答 1

6

“本来可以的解决方案”

你的推理是完全正确的,并且会奏效。

然而,它在一个关键方面有所不足:您的描述目前非常迫切,如果您按照您描述的方式实现它,那么您将能够解决给定的实例,但您将无法使用相同的程序来解决生成这样的拼图实例。

让我更详细地解释我的意思。

干净的表示

我们从这些谜题的表示开始。您当前正在使用:

L = [_, _, _, 3, _, _, _, 4, _, _, 2, _]

这称为默认表示,因为您无法清楚地区分不同的情况。如果你从逻辑上思考,并认为这是拼图的一个“实例”,那么这个术语的任何更特殊的情况都必须被视为实例的更具体的情况,例如部分或完全解决方案。但在这种表示中,情况并非如此,因为进一步实例化其中一个论点会突然将其变成一个完全不同的实例,而不仅仅是一个更具体的表现形式。

因此,这是此类难题的清晰表示:

示例([?,?,?,p(3),?,?,?,p(4),?,?,p(2),?])。

在这里,我用不同的术语来区分这两种可能的情况。我在用:

  • 未知整数原子 ?
  • p(P)表示枢轴元素 的形式术语P

许多其他的表示是可能的,如果你需要表达变量共享,你可以例如使用i(I)来表示一个未知的整数 I。关键是您可以通过模式匹配而不是测试实例化的非单调构造来区分情况。这是可以在各个方向使用的通用解决方案的关键。

声明性任务描述

让我们重新考虑您给出的命令式描述:

  • "搜索第一个枢轴号"
  • "获取pivot的左右子列表"
  • 附加两个列表”

现在让我们以声明的方式表达这一点。我们必须避免使用诸如“搜索”、“获取”和“附加”之类的命令,因为这些都暗示了特定的使用方向

声明性描述可能如下所示:我们正在描述形式的三元组Lefts- Pivot- Rights,其中以下成立:

整数LeftsRights等于。_ Pivot

如果我们设法编写一个描述此类三元组的 Prolog 程序,并从任务实例到此类三元组绘制适当的连接,那么我们就完成了并且可以在各个方向使用它。

转换为三元组

处理干净的表示很有趣,我们可以很容易地将它们转换为其他形式,因为我们可以很容易地区分这些情况。因此,现在让我们将实例转换为三元组,我们可以更轻松地对其进行推理。

让我们反映任务表示:它是一个元素列表。很多时候,在 Prolog 中描述列表时, DCG 表示法( ) 会派上用场。所以,让我们描述任务,同时描述任务描述和三元组之间的关系:

is_p_is([Ls|Rest]) -->
        是(Ls),
        is_p_is_(休息)。

is_p_is_([Pivot,Rs]) --> [p(Pivot)], is(Rs)。
is_p_is_([Pivot,Rs, Rs |IPIs]) -->
        [p(枢轴)],
        是(Rs),
        is_p_is_(IPIs)。

是([])-> []。
is([_|Is]) --> [?], is(Is).

这个DCG是整个答案的重点。

以下是如何使用它,看看它描述了什么:

?-示例(Es),
   短语(is_p_is(Ls),Es)。
Es = [?, ?, ?, p(3), ?, ?, ?, p(4), ?, ?, p(2), ?],
Ls = [[_G902, _G905, _G908], 3, [_G920, _G923, _G926], [_G920, _G923, _G926], 4, [_G938, _G941], [_G938, _G941], 2, [_G950]];
错误的。

由此可见,我已经排除了“找什么在哪里,找多远”的复杂推理。相反,它是实例和表单列表之间的简单而普遍的关系:

[Left0,Pivot0,Right0,Left1,Pivot1,Right1,Left2,...]

其中“三元组”是隐含的,可以按如下方式分组:

[(Left0,Pivot0,Right0),(Left1,Pivot1,Right1),...]

此外,Rightn与n +1相同Left

发布限制

给定这样的列表,发布CLP(FD) 约束是直截了当的:

约束([])。
约束([左,枢轴,右|休息]): -
        左 ins 0..sup,
        正确的 ins 0..sup,
        总和(左,#=,SL),
        总和(右,#=,SR),
        枢轴#= SL + SR,
        约束(休息)。

如果你愿意,你可以在这里使用append/3,但为什么还要麻烦呢?使用append/3会带来一些引入非终止的风险,而 CLP(FD) 约束(理想情况下)总是终止,所以我使用了两个sum/3 约束。

提取变量

提取变量也很简单:

变量([])-> []。
变量([左,_,右|休息])-->
        序列(左),
        序列(右),
        变量(休息)。

序列([])-> []。
seq([L|Ls]) --> [L], seq(Ls)。

示例查询和答案

这是一个示例查询和一些答案:

?-示例(Es),
   短语(is_p_is(Ls),Es),
   约束(Ls),
   短语(变量(Ls),Vs),
   标签(Vs)。
Es = [?, ?, ?, p(3), ?, ?, ?, p(4), ?, ?, p(2), ?],
Ls = [[0, 0, 0], 3, [0, 0, 3], [0, 0, 3], 4, [0, 1], [0, 1], 2, [1]] ,
Vs = [0, 0, 0, 0, 0, 3, 0, 0, 3, 0, 1, 0, 1, 1] ;
Es = [?, ?, ?, p(3), ?, ?, ?, p(4), ?, ?, p(2), ?],
Ls = [[0, 0, 0], 3, [0, 0, 3], [0, 0, 3], 4, [1, 0], [1, 0], 2, [1]] ,
Vs = [0, 0, 0, 0, 0, 3, 0, 0, 3, 1, 0, 1, 0, 1] ;
Es = [?, ?, ?, p(3), ?, ?, ?, p(4), ?, ?, p(2), ?],
Ls = [[0, 0, 0], 3, [0, 1, 2], [0, 1, 2], 4, [0, 1], [0, 1], 2, [1]] ,
Vs = [0, 0, 0, 0, 1, 2, 0, 1, 2, 0, 1, 0, 1, 1] ;
等等

我已经强调了“三重”表示。

胜利的普遍性

现在关键点:所有这些都是完全通用的,我们可以用它来自己生成实例!

例子:

?-长度(Es,_),短语(is_p_is(Ls),Es)。
Es = [p(_G874)],
LS = [[],_G874,[]];
Es = [p(_G877), ?],
LS = [[],_G877,[_G885]];
Es = [p(_G877), p(_G888)],
Ls = [[], _G877, [], [], _G888, []] ;
Es = [?, p(_G880)],
LS = [[_G877],_G880,[]];
Es = [p(_G880), ?, ?],
Ls = [[], _G880, [_G888, _G891]]。

这是根据关系进行思考和使用干净表示的自然副产品。

我将更具体的第二个问题作为第一个练习留给你。我希望上面显示的原则可以帮助您获得一个干净通用的解决方案,让您充分利用逻辑编程的最大优势。

作为第二个练习,我将重写上面的内容以使用i(_) 未知整数的表示。有了这样的表示,我们可以摆脱上面的几个缺点。例如,提取变量会变得更方便一些:

变量([])-> []。
变量([L|Ls])-->
        变量_(L),
        变量(Ls)。

变量_(i(I))-> [I]。
变量_(p(_))-> []。

例子:

?-短语(变量([i(X),p(3),i(Y)]),Vs)。
Vs = [X,Y]。

此外,我们不会不必要地复制此类列表中的剩余变量。

此外,检查一下:

?-长度(Ls,_),短语(变量(Ls),Vs)。
Ls = Vs,Vs = [];
Ls = [i(_2066)] ,
VS = [_2066] ;
Ls = [p(_2066)] ,
VS = [];
Ls = [i(_2072), i(_2082)] ,
VS = [_2072, _2082] ;
Ls = [i(_2072), p(_2082)] ,
VS = [_2072] ;
Ls = [p(_2072), i(_2076)] ,
VS = [_2076]。

这种关系与使用不纯谓词 like相比如何exclude/3?你还能用它生成所有可能的情况吗?有关详细信息,请参阅

艰难的部分

作为第三个也是最后一个练习,我留下了最难的一个:重读我写的东西,找到我所在的所有实例,通过使用命令式措辞,而不是对实际实施的普遍性进行公正,并重新措辞,使其反映实际情况

我举个例子:我说“提取变量”,还有什么更自然的,对吧?但我实际上已经实现了 variables 和 triples 之间的完整关系,您可以使用它来:

  • 从三元组中提取变量
  • 测试这些变量是否对应于给定的三元组
  • 生成变量和三元组
  • 等等

例如,我们可以使用最通用的查询

?-短语(变量(Ts),Vs)。
Ts = Vs,Vs = [];
Ts = [[], _1960, []],
VS = [];
Ts = [[], _1960, [], [], _1978, []],
VS = [];
Ts = [[], _1960, [], [], _1978, [], [], _1996, []],
VS = [] 。

这当然不是一个非常公平的枚举,但它远远超出了提取某些内容的范围,因为查询中甚至没有给出任何内容。

你怎么地道!

所以,回答你的问题:在我看来,我们认为最惯用的 Prolog 那些解决方案最令人印象深刻地展示了我们期望从关系和逻辑程序的独特优势中获得的声明性属性,例如:

  • 全方位可用
  • 承认阅读更具体更笼统
  • 使用反映关系一般性的名称。

一个优雅、惯用的 Prolog 解决方案通常从实例的清晰表示和描述性谓词名称开始。

于 2016-12-04T07:49:45.743 回答