同时进行Prolog反向查询和输入验证

时间:2018-11-19 23:12:44

标签: recursion prolog imperative-programming

我最近开始学习Prolog,尽管从功能编程的角度出发令人耳目一新,但事情似乎仍然很陌生。我很难理解如何编写谓词,要检查其参数是否遵守特定规则集,同时还要检查是否给定变量将其设置为满足那些规则的可能值。

我正试图解决圆桌会议座位的问题,在此问题中,您定义了一组让人们坐在彼此旁边的条件。因此,知识库包含10个人,他们会说他们所使用的语言,并且其目标是使他们就座,使得两个彼​​此相邻的人必须说相同的语言。

我定义了一个谓词speaksSame(X, Y),如果X和Y个体使用相同的语言,则返回true。现在的目标是编写一个函数表位,如果列表中彼此相邻的两个人都说一种通用语言,则table-seating([mark, carl, emily, kevin, oliver])返回true。当然,每个人都会讲多种语言。还有表位(L)。将获得满足条件的可能的餐桌座位。

我的看法是,我可以编写一个谓词来检查先前定义的列表是否满足规则,也可以根据这些规则构建一个列表。我不了解如何使用一项功能同时完成这两项工作。

任何帮助将不胜感激,谢谢!

3 个答案:

答案 0 :(得分:2)

  

我不明白如何用一个功能同时完成这两项工作。

是的,乍一看这很奇怪,但是一旦掌握了它,便会以其他语言错过它。

引用该单词时要记住的单词是:mode
另请参阅Mercury Modes reference,以获取有关Mercury编程语言的更多详细信息。

在Prolog中,参数可以是输入和/或输出,也可以根据输入的方式用作输入或输出。

在底部的Type, mode and determinism declaration headers中列出了4个示例。

  1. length(+ List:list,-Length:int)是确定的。
  2. length(?List:list,-Length:int)是未删除的。
  3. length(?List:list,+ Length:int)是det。
  4. 如果List是一个长度为Length的列表,则为真。

length/2的定义显示

length(?List, ?Int)

意思
对于List参数,列表可以是绑定的也可以是未绑定的,并且
Int参数的值可以是绑定的或不绑定的。
因此,对于带有两个选项的两个参数,有四种使用length/2

的方法

此处再次列出了它们,但实际使用情况。

1。

length(+List:list, -Length:int) is det.

在这种情况下,列表是绑定的,而长度是未绑定的,并且总是给出一个答案。

?- length([1,2,3],N).
N = 3.

2。

 length(?List:list, -Length:int) is nondet.

在这种情况下,列表不受限制,长度不受限制,并且可以返回任意数量的答案。

?- length(List,N).
List = [],
N = 0 ;
List = [_5362],
N = 1 ;
List = [_5362, _5368],
N = 2 ;
List = [_5362, _5368, _5374],
N = 3 ;
List = [_5362, _5368, _5374, _5380],
N = 4 ;
List = [_5362, _5368, _5374, _5380, _5386],
N = 5 
...

3。

length(?List:list, +Length:int) is det.

在这种情况下,列表是不受约束的,而长度是受约束的,并且总是给出一个答案。

?- length(List,4).
List = [_5332, _5338, _5344, _5350].

4。

True if List is a list of length Length.

在这种情况下,列表是绑定的,长度是绑定的,并且始终充当返回truefalse的谓词。

?- length([1,2,3],3).
true.

?- length([1,2,3],5).
false.

那怎么可能?

Prolog使用syntactic unification(↦)而不是assignment(=)。

如果我们使用length/2查看listing/1的源代码,则会得到

?- listing(length/2).
system:length(B, A) :-
        var(A), !,
        '$skip_list'(D, B, C),
        (   C==[]
        ->  A=D
        ;   var(C)
        ->  C\==A,
            '$length3'(C, A, D)
        ;   throw(error(type_error(list, B), context(length/2, _)))
        ).
system:length(B, A) :-
        integer(A),
        A>=0, !,
        '$skip_list'(D, B, C),
        (   C==[]
        ->  A=D
        ;   var(C)
        ->  E is A-D,
            '$length'(C, E)
        ;   throw(error(type_error(list, B), context(length/2, _)))
        ).
system:length(_, A) :-
        integer(A), !,
        throw(error(domain_error(not_less_than_zero, A),
                    context(length/2, _))).
system:length(_, A) :-
        throw(error(type_error(integer, A), context(length/2, _))).

太多细节了,但是可以正确完成所有4种模式。

为了便于理解,我们将使用this版本,但它不正确支持其中一种模式,但是它不止一种模式,因此足以演示。

length_2([]     , 0 ).
length_2([_|Xs] , L ) :- 
    length_2(Xs,N),
    L is N+1 .

现在要查看统一的应用,我们将使用SWI-Prolog的trace功能,并使用box model启用visible/1的所有端口,以免停止运行时使用leash/1

?- visible(+all),leash(-all).
?- trace.

1。

[trace] ?- length_2([1,2,3],N).
   Call: (8) length_2([1, 2, 3], _2352)
   Unify: (8) length_2([1, 2, 3], _2352)
   Call: (9) length_2([2, 3], _2580)
   Unify: (9) length_2([2, 3], _2580)
   Call: (10) length_2([3], _2580)
   Unify: (10) length_2([3], _2580)
   Call: (11) length_2([], _2580)
   Unify: (11) length_2([], 0)
   Exit: (11) length_2([], 0)
   Call: (11) _2584 is 0+1
   Exit: (11) 1 is 0+1
   Exit: (10) length_2([3], 1)
   Call: (10) _2590 is 1+1
   Exit: (10) 2 is 1+1
   Exit: (9) length_2([2, 3], 2)
   Call: (9) _2352 is 2+1
   Exit: (9) 3 is 2+1
   Exit: (8) length_2([1, 2, 3], 3)
N = 3.

2。

[trace] ?- length_2(List,N).
   Call: (8) length_2(_2296, _2298)
   Unify: (8) length_2([], 0)
   Exit: (8) length_2([], 0)
List = [],
N = 0 ;
   Redo: (8) length_2(_2296, _2298)
   Unify: (8) length_2([_2528|_2530], _2298)
   Call: (9) length_2(_2530, _2550)
   Unify: (9) length_2([], 0)
   Exit: (9) length_2([], 0)
   Call: (9) _2298 is 0+1
   Exit: (9) 1 is 0+1
   Exit: (8) length_2([_2528], 1)
List = [_2528],
N = 1 ;
   Redo: (9) length_2(_2530, _2550)
   Unify: (9) length_2([_2534|_2536], _2556)
   Call: (10) length_2(_2536, _2556)
   Unify: (10) length_2([], 0)
   Exit: (10) length_2([], 0)
   Call: (10) _2560 is 0+1
   Exit: (10) 1 is 0+1
   Exit: (9) length_2([_2534], 1)
   Call: (9) _2298 is 1+1
   Exit: (9) 2 is 1+1
   Exit: (8) length_2([_2528, _2534], 2)
List = [_2528, _2534],
N = 2 ;
   Redo: (10) length_2(_2536, _2556)
   Unify: (10) length_2([_2540|_2542], _2562)
   Call: (11) length_2(_2542, _2562)
   Unify: (11) length_2([], 0)
   Exit: (11) length_2([], 0)
   Call: (11) _2566 is 0+1
   Exit: (11) 1 is 0+1
   Exit: (10) length_2([_2540], 1)
   Call: (10) _2572 is 1+1
   Exit: (10) 2 is 1+1
   Exit: (9) length_2([_2534, _2540], 2)
   Call: (9) _2298 is 2+1
   Exit: (9) 3 is 2+1
   Exit: (8) length_2([_2528, _2534, _2540], 3)
List = [_2528, _2534, _2540],
N = 3 

3。

[trace] ?- length_2(List,3).
   Call: (8) length_2(_5534, 3)
   Unify: (8) length_2([_5724|_5726], 3)
   Call: (9) length_2(_5726, _5746)
   Unify: (9) length_2([], 0)
   Exit: (9) length_2([], 0)
   Call: (9) 3 is 0+1
   Fail: (9) 3 is 0+1
   Redo: (9) length_2(_5726, _5746)
   Unify: (9) length_2([_5730|_5732], _5752)
   Call: (10) length_2(_5732, _5752)
   Unify: (10) length_2([], 0)
   Exit: (10) length_2([], 0)
   Call: (10) _5756 is 0+1
   Exit: (10) 1 is 0+1
   Exit: (9) length_2([_5730], 1)
   Call: (9) 3 is 1+1
   Fail: (9) 3 is 1+1
   Redo: (10) length_2(_5732, _5752)
   Unify: (10) length_2([_5736|_5738], _5758)
   Call: (11) length_2(_5738, _5758)
   Unify: (11) length_2([], 0)
   Exit: (11) length_2([], 0)
   Call: (11) _5762 is 0+1
   Exit: (11) 1 is 0+1
   Exit: (10) length_2([_5736], 1)
   Call: (10) _5768 is 1+1
   Exit: (10) 2 is 1+1
   Exit: (9) length_2([_5730, _5736], 2)
   Call: (9) 3 is 2+1
   Exit: (9) 3 is 2+1
   Exit: (8) length_2([_5724, _5730, _5736], 3)
List = [_5724, _5730, _5736] 
Action (h for help) ? abort

% Execution Aborted

4。

[trace] ?- length_2([1,2,3],3).
   Call: (8) length_2([1, 2, 3], 3)
   Unify: (8) length_2([1, 2, 3], 3)
   Call: (9) length_2([2, 3], _2058)
   Unify: (9) length_2([2, 3], _2058)
   Call: (10) length_2([3], _2058)
   Unify: (10) length_2([3], _2058)
   Call: (11) length_2([], _2058)
   Unify: (11) length_2([], 0)
   Exit: (11) length_2([], 0)
   Call: (11) _2062 is 0+1
   Exit: (11) 1 is 0+1
   Exit: (10) length_2([3], 1)
   Call: (10) _2068 is 1+1
   Exit: (10) 2 is 1+1
   Exit: (9) length_2([2, 3], 2)
   Call: (9) 3 is 2+1
   Exit: (9) 3 is 2+1
   Exit: (8) length_2([1, 2, 3], 3)
true.

[trace] ?- length_2([1,2,3],5).
   Call: (8) length_2([1, 2, 3], 5)
   Unify: (8) length_2([1, 2, 3], 5)
   Call: (9) length_2([2, 3], _2442)
   Unify: (9) length_2([2, 3], _2442)
   Call: (10) length_2([3], _2442)
   Unify: (10) length_2([3], _2442)
   Call: (11) length_2([], _2442)
   Unify: (11) length_2([], 0)
   Exit: (11) length_2([], 0)
   Call: (11) _2446 is 0+1
   Exit: (11) 1 is 0+1
   Exit: (10) length_2([3], 1)
   Call: (10) _2452 is 1+1
   Exit: (10) 2 is 1+1
   Exit: (9) length_2([2, 3], 2)
   Call: (9) 5 is 2+1
   Fail: (9) 5 is 2+1
   Fail: (8) length_2([1, 2, 3], 5)
false.

并关闭跟踪

[trace] ?- notrace.
true.

[debug] ?- nodebug.
true.

我不会遍历跟踪输出中的每一行,但是,如果您了解语法统一并且可以跟踪跟踪,那么在完成给出的示例之后,您将看到Prolog中的变量如何统一,从而导致不同的模式。与命令式编程相比。

请记住,变量在Prolog中仅绑定一次,并且永远不会重新分配,并且括号中轨迹左侧的数字(例如, (10)是堆栈级别,因此当再次调用谓词时,将提供一组新的变量,并且似乎在重新分配了值时,它实际上是堆栈中的另一个变量,只是在另一个stack frame中。

顺带一提,在学习Prolog时,我给出的一条建议是,如果您撇开对命令式和函数式编程的了解,那么也许更容易学习,除了递归之外,然后从头开始并统一。 backward chaining

如果您可以阅读OCaml,则here是统一和反向链接的简化版本。请注意,这不是Prolog,因为它没有列表或cut运算符,但是如果您能理解它,那么统一和向后链接的方式就很明显了。

我必须补充一点,因为我知道您是一个初学者,所以我对我的答案并不完全满意,并且此答案涉及很多信息,需要您做大量工作才能通过4个跟踪示例进行工作。但是,它的确回答了这个问题,并给出了一个实际的例子,骨骼上有足够的肉。我正在尝试考虑一个更好的示例,其中将包括logical purity,这表明不仅统一而且关系对于在一个谓词中如何实现多种模式至关重要。很高兴我没有使用相对论约翰·阿奇博尔德·惠勒spacetime tells matter how to move; matter tells spacetime how to curve所解释的广义相对论。

答案 1 :(得分:1)

我从事Prolog已有几年了,我觉得我对不同的实例化模式的满意和理解已经分几步走了。当然,第一个重要的障碍是递归,这是您真正需要解决的所有问题。基本上,您知道两个人的表分配正确(如果他们说相同的语言),所以这是您的基本情况:

table_seating([X,Y]) :- speaksSame(X, Y).

那么,如果您将第三个人添加到混合中怎么办?您将执行以下操作:

% for exposition only; do not include this clause
table_seating([A,X,Y]) :- speaksSame(A,X), speaksSame(X, Y).

现在希望您注意到您的新作品是speaksSame(A,X),但您的旧作品却保持不变。让我们担心新人和 trust ,我们可能会在列表的其余部分中对其进行处理。

table_seating([X,Y,Z|Rest]) :- 
   speaksSame(X, Y),
   table_seating([Y,Z|Rest]).

我们在这里说的是,假设列表中至少有三个项目。然后,如果前两个说相同的语言,而后两个加上其余的就可以就座,那么他们都可以就座。如果他们说的语言与当前在桌子前面的人使用相同的语言,则始终可以选择一个座位正确的桌子并将一个人添加到桌子的前面。

递归几乎总是具有这种风格:如何设置最小的正确情况(基本情况),然后如何为该情况正确添加其他内容?

现在有趣的是,如果您为该谓词提供一定长度的列表,它将“起作用”并产生该长度的解决方案。像这样尝试:

?- length(L, 6), table_seating(L).

您可能会获得解决方案(我假设speaksSame/2会生成解决方案)。这是因为所有Prolog都知道这些变量,也因为您的speaksSame/2谓词而知道。因此,只要您在谓词中使用具有许多实例化模式的谓词,并且不强制对事物进行赋值或对事物进行奇怪的排序,则谓词通常会继承这些模式。这就是我建议人们使用succ/2而不是N is N0 + 1N0 is N - 1的原因,因为succ/2定义了两个数字之间的关系而不是执行一些算术运算(clpfd将这个想法推得更远)。

答案 2 :(得分:0)

在 Prolog 中,生成和验证是一回事:table_seating 谓词在任何情况下都可以,除非您故意生成非关系代码。即语句 table_seating(X) 说“X 是桌椅”。那么,如果 X 被绑定并且是一个有效的餐桌座位,则“调用”将成功。如果 X 已绑定且无效,则调用失败,如果未绑定,则会填充有效座位,调用将通过。

即Prolog 确保真理总是获胜。而“确保”是执行/评估(这是作为一个潜在的失败搜索来实现的)。

所以谓词只需要表达什么是桌子座位。