Smullyan数控机床的解决方案

时间:2014-06-19 18:33:57

标签: prolog constraint-programming

在这里,我建议找到Smullyan数值机器的解决方案为defined here

问题陈述

它们是将数字列表作为输入的机器,并根据输入模式将其转换为遵循某些规则的另一个数字列表。 以下是上面链接中给出的机器规则,更正式地表达了一下。 假设M是机器,M(X)是X的变换。 我们定义了一些这样的规则:

M(2X) = X
M(3X) = M(X)2M(X)
M(4X) = reverse(M(X)) // reverse the order of the list.
M(5X) = M(X)M(X)

任何与任何规则都不匹配的内容都会被拒绝。 以下是一些例子:

  • M(245)= 45
  • M(3245)= M(245)2M(245)= 45245
  • M(43245)=反向(M(3245))=反向(45245)= 54254
  • M(543245)= M(43245)M(43245)= 5425454254

问题是,找到X:

  • M(X)= 2
  • M(X)= X
  • M(X)= X2X
  • M(X)=反向(X)
  • M(X)=反向(X2X)反向(X2X)

这是第二个与穷举搜索相比更复杂的例子(特别是如果我想要前10个或100个解决方案)。

M(1X2) = X
M(3X) = M(X)M(X)
M(4X) = reverse(M(X))
M(5X) = truncate(M(X)) // remove the first element of the list truncate(1234) = 234. Only valid if M(X) has at least 2 elements.
M(6X) = 1M(X)
M(7X) = 2M(X)

问题:

  • M(X)= XX
  • M(X)= X
  • M(X)=反向(X)

(非)解决方案

在Prolog中编写求解器非常简单。除了它只是详尽的探索(a.k.a蛮力),可能需要一些时间来制定一些规则。

我试过但无法用CLP(FD)的逻辑约束表达这个问题,所以我尝试用CHR(约束处理规则)来表达对列表的约束(特别是append约束) ,但无论我如何表达,它总是归结为一个详尽的搜索。

问题

知道我可以采取什么方法在合理的时间内解决这类问题? 理想情况下,我希望能够生成比某些更短的所有解决方案。

5 个答案:

答案 0 :(得分:5)

让我们来看看你的“更复杂”的问题。详尽的搜索功能非常出色!

这是与Сергей解决方案的比较,可以通过考虑共同目标来显着改善:

m([1|A], X) :-
    A = [_|_],
    append(X, [2], A).
m([E | X], Z) :-
    m(X, Y),
    (  E = 3,
       append(Y, Y, Z)
    ;  E = 4,
       reverse(Y, Z)
    ;  E = 5,
       Y = [_ | Z]
    ;  E = 6,
       Z = [1 | Y]
    ;  E = 7,
       Z = [2 | Y]
    ).

对于查询time(findall(_, (question3(X), write(X), nl), _)).我得到B 8.1,SICStus 4.3b8:

Серге́й B tabled   104.542s
Серге́й B          678.394s
false  B           16.013s
false  B tabled    53.007s
Серге́й SICStus    439.210s
false  SICStus      7.990s
Серге́й SWI       1383.678s, 5,363,110,835 inferences
false  SWI         44.743s,   185,136,302 inferences

其他问题并不难回答。只有SICStus以上m/2call_nth/2

| ?- time(call_nth( (
        length(Xs0,N),append(Xs0,Xs0,Ys),m(Xs0,Ys),
          writeq(Ys),nl ), 10)).
[4,3,7,4,3,1,4,3,7,4,3,1,2,4,3,7,4,3,1,4,3,7,4,3,1,2]
[3,4,7,4,3,1,3,4,7,4,3,1,2,3,4,7,4,3,1,3,4,7,4,3,1,2]
[4,3,7,3,4,1,4,3,7,3,4,1,2,4,3,7,3,4,1,4,3,7,3,4,1,2]
[3,4,7,3,4,1,3,4,7,3,4,1,2,3,4,7,3,4,1,3,4,7,3,4,1,2]
[3,5,4,5,3,1,2,2,1,3,5,4,5,3,1,2,3,5,4,5,3,1,2,2,1,3,5,4,5,3,1,2]
[3,5,5,3,4,1,2,1,4,3,5,5,3,4,1,2,3,5,5,3,4,1,2,1,4,3,5,5,3,4,1,2]
[5,4,7,4,3,3,1,2,5,4,7,4,3,3,1,2,5,4,7,4,3,3,1,2,5,4,7,4,3,3,1,2]
[4,7,4,5,3,3,1,2,4,7,4,5,3,3,1,2,4,7,4,5,3,3,1,2,4,7,4,5,3,3,1,2]
[5,4,7,3,4,3,1,2,5,4,7,3,4,3,1,2,5,4,7,3,4,3,1,2,5,4,7,3,4,3,1,2]
[3,5,4,7,4,3,1,_2735,3,5,4,7,4,3,1,2,3,5,4,7,4,3,1,_2735,3,5,4,7,4,3,1,2]
196660ms

| ?- time(call_nth( (
        length(Xs0,N),m(Xs0,Xs0),
          writeq(Xs0),nl ), 10)).
[4,7,4,3,1,4,7,4,3,1,2]
[4,7,3,4,1,4,7,3,4,1,2]
[5,4,7,4,3,1,_2371,5,4,7,4,3,1,2]
[4,7,4,5,3,1,_2371,4,7,4,5,3,1,2]
[5,4,7,3,4,1,_2371,5,4,7,3,4,1,2]
[3,5,4,7,4,1,2,3,5,4,7,4,1,2]
[4,3,7,4,5,1,2,4,3,7,4,5,1,2]
[3,4,7,4,5,1,2,3,4,7,4,5,1,2]
[4,7,5,3,6,4,1,4,7,5,3,6,4,2]
[5,4,7,4,3,6,1,5,4,7,4,3,6,2]
6550ms

| ?- time(call_nth( (
        length(Xs0,N),reverse(Xs0,Ys),m(Xs0,Ys),
          writeq(Ys),nl ), 10)).
[2,1,3,4,7,1,3,4,7]
[2,1,4,3,7,1,4,3,7]
[2,1,3,5,4,7,_2633,1,3,5,4,7]
[2,1,5,4,7,3,2,1,5,4,7,3]
[2,4,6,3,5,7,1,4,6,3,5,7]
[2,6,3,5,4,7,1,6,3,5,4,7]
[2,_2633,1,5,3,4,7,_2633,1,5,3,4,7]
[2,_2633,1,5,4,3,7,_2633,1,5,4,3,7]
[2,1,3,4,4,4,7,1,3,4,4,4,7]
[2,1,3,4,5,6,7,1,3,4,5,6,7]
1500ms

答案 1 :(得分:3)

(我假设这是一个数字列表,正如你的建议。与你提供的链接相反,它与数字有关。可能与前导零有差异。我没有花时间去考虑通过)

首先,Prolog是一种搜索蛮力的优秀语言。因为,即使在这种情况下,Prolog也能够缓解组合爆炸。感谢逻辑变量。

你的问题陈述本质上是存在主义的陈述:是否存在X这样的等等。这就是Prolog最擅长的地方。重点是你如何提问。不要询问[1]等具体值,而只是要求:

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

同样适用于其他查询。请注意,没有必要满足具体的价值!这使得搜索肯定更加昂贵!

?- length(Xs, N), maplist(between(0,9),Xs), m(Xs,Xs).
Xs = [3,2,3],
N = 3 ...

以这种方式,如果存在,可以非常有效地找到具体的解决方案。唉,我们无法确定解决方案不存在。

为了说明这一点,以下是“最复杂”难题的答案:

?- length(Xs0,N),
   append(Xs0,[2|Xs0],Xs1),reverse(Xs1,Xs2),append(Xs2,Xs2,Xs3), m(Xs0,Xs3).
Xs0 = [4, 5, 3, 3, 2, 4, 5, 3, 3],
N = 9,
...

立即出现。但是,查询:

?- length(Xs0,N), maplist(between(0,9),Xs0),
   append(Xs0,[2|Xs0],Xs1),reverse(Xs1,Xs2),append(Xs2,Xs2,Xs3), m(Xs0,Xs3).

仍在运行!

我使用的m/2

m([2|Xs], Xs).
m([3|Xs0], Xs) :-
   m(Xs0,Xs1),
   append(Xs1,[2|Xs1], Xs).
m([4|Xs0], Xs) :-
   m(Xs0, Xs1),
   reverse(Xs1,Xs).
m([5|Xs0],Xs) :-
   m(Xs0,Xs1),
   append(Xs1,Xs1,Xs).

这更有效的原因很简单,所有 n 数字的天真枚举都有10个 n 不同的候选者,而Prolog将仅搜索由3个递归规则给出的3 n

这是另一个优化:所有3个规则具有相同的递归目标。那么为什么这三次,当曾经绰绰有余时:

m([2|Xs], Xs).
m([X|Xs0], Xs) :-
   m(Xs0,Xs1),
   ( X = 3,
     append(Xs1,[2|Xs1], Xs)
   ; X = 4,
     reverse(Xs1,Xs)
   ; X = 5,
     append(Xs1,Xs1,Xs)
   ).

对于最后一个查询,这从410,014次推断,0.094s CPU减少到57,611次推断,0.015s CPU减少。

编辑:在进一步优化中,可以合并两个append/3目标:

m([2|Xs], Xs).
m([X|Xs0], Xs) :-
   m(Xs0,Xs1),
   ( X = 4,
     reverse(Xs1,Xs)
   ; append(Xs1, Xs2, Xs),
     ( X = 3, Xs2 = [2|Xs1]
     ; X = 5, Xs2 = Xs1
     )
   ).

...进一步将执行减少到39,096次推断和运行时间减少1ms。

还能做些什么?长度受“输入”长度的限制。如果 n 是输入的长度,则2 (n-1) -1是最长的输出。这有点帮助吗?可能不是。

答案 2 :(得分:3)

我在这里提出另一个基本上是详尽探索的解决方案。给定问题,如果知道m/2的第一个参数的长度,则知道第二个参数的长度。如果第二个参数的长度始终是已知的,则可以通过将一些约束传播到递归调用来更早地减少搜索。但是,这与false提出的优化不兼容。

appendh([], [], Xs, Xs).
appendh([_, _ | Ws], [X | Xs], Ys, [X | Zs]) :-
    appendh(Ws, Xs, Ys, Zs).

m([1 | A], X) :-
    append(X, [2], A).
m([3 | A], X) :-
    appendh(X, B, B, X),
    m(A, B).
m([4 | A], X) :-
    reverse(X, B),
    m(A, B).
m([5 | A], X) :-
    B = [_, _ | _],
    B = [_ | X],
    m(A, B).
m([H1 | A], [H2 | B]) :-
    m(A, B),
    (  H1 = 6, H2 = 1
    ;  H1 = 7, H2 = 2
    ).

answer3(X) :-
    between(1, 13, N),
    length(X, N),
    reverse(X, A),
    m(X, A).

以下是分别用以下时间的时间:此代码,此代码在使用每种情况的约束交换递归调用时(类似于Sergey Dymchenko的解决方案),以及解决递归调用的false的解决方案。测试在SWI上运行,并搜索长度小于或等于13的所有解决方案。

% 36,380,535 inferences, 12.281 CPU in 12.315 seconds (100% CPU, 2962336 Lips)
% 2,359,464,826 inferences, 984.253 CPU in 991.474 seconds (99% CPU, 2397214 Lips)
% 155,403,076 inferences, 47.799 CPU in 48.231 seconds (99% CPU, 3251186 Lips)

所有措施均通过以下呼叫执行:

?- time(findall(X, (answer3(X), writeln(X)), _)).

答案 3 :(得分:3)

以下是@ Celelibi改进版本(cele_n)的另一项改进。粗略地说,它通过约束第一个参数的长度得到两倍,而通过预先测试两个版本得到另一个因子。

cele_n SICStus  2.630s
cele_n SWI     12.258s 39,546,768 inferences
cele_2 SICStus  0.490s
cele_2 SWI      2.665s  9,074,970 inferences

appendh([], [], Xs, Xs).
appendh([_, _ | Ws], [X | Xs], Ys, [X | Zs]) :-
   appendh(Ws, Xs, Ys, Zs).

m([H|A], X) :-
   A = [_|_],                 % New
   m(H, X, A).

m(1, X, A) :-
   append(X, [2], A).
m(3, X, A) :-
   appendh(X, B, B, X),
   m(A, B).
m(4, X, A) :-
   reverse(X, B),
   m(A, B).
m(5, X, A) :-
   X = [_| _],
   m(A, [_|X]).
m(H1, [H2 | B], A) :-
   \+ \+ ( H2 = 1 ; H2 = 2 ),  % New
   m(A, B),
   (  H1 = 6, H2 = 1
   ;  H1 = 7, H2 = 2
   ).

answer3(X) :-
   between(1, 13, N),
   length(X, N),
   reverse(X, A),
   m(X, A).

run :-
   time(findall(X, (answer3(X), write(X), nl), _)).

答案 4 :(得分:2)

布线(memoization)可以帮助解决问题中更难的变种。

以下是我对B-Prolog中第二个例子的第三个问题的实现(返回长度为13或更短的所有解决方案):

:- table m/2.

m(A, X) :-
    append([1 | X], [2], A).
m([3 | X], Z) :-
    m(X, Y),
    append(Y, Y, Z).
m([4 | X], Z) :-
    m(X, Y),
    reverse(Y, Z).
m([5 | X], Z) :-
    m(X, Y),
    Y = [_ | Z].
m([6 | X], Z) :-
    m(X, Y),
    Z = [1 | Y].
m([7 | X], Z) :-
    m(X, Y),
    Z = [2 | Y].

question3(X) :-
    between(1, 13, N),
    length(X, N), 
    reverse(X, Z), m(X, Z).

执行命令

B-Prolog Version 8.1, All rights reserved, (C) Afany Software 1994-2014.
| ?- cl(smullyan2).
cl(smullyan2).
Compiling::smullyan2.pl
compiled in 2 milliseconds
loading...

yes
| ?- time(findall(_, (question3(X), writeln(X)), _)).
time(findall(_, (question3(X), writeln(X)), _)).
[7,3,4,1,7,3,4,1,2]
[7,4,3,1,7,4,3,1,2]
[3,7,4,5,1,2,3,7,4,5,1,2]
[7,4,5,3,1,_678,7,4,5,3,1,2]
[7,4,5,3,6,1,7,4,5,3,6,2]
[7,5,3,6,4,1,7,5,3,6,4,2]
[4,4,7,3,4,1,4,4,7,3,4,1,2]
[4,4,7,4,3,1,4,4,7,4,3,1,2]
[5,6,7,3,4,1,5,6,7,3,4,1,2]
[5,6,7,4,3,1,5,6,7,4,3,1,2]
[5,7,7,3,4,1,5,7,7,3,4,1,2]
[5,7,7,4,3,1,5,7,7,4,3,1,2]
[7,3,4,4,4,1,7,3,4,4,4,1,2]
[7,3,4,5,1,_698,7,3,4,5,1,_698,2]
[7,3,4,5,6,1,7,3,4,5,6,1,2]
[7,3,4,5,7,1,7,3,4,5,7,1,2]
[7,3,5,6,4,1,7,3,5,6,4,1,2]
[7,3,5,7,4,1,7,3,5,7,4,1,2]
[7,3,6,5,4,1,7,3,6,5,4,1,2]
[7,4,3,4,4,1,7,4,3,4,4,1,2]
[7,4,3,5,1,_698,7,4,3,5,1,_698,2]
[7,4,3,5,6,1,7,4,3,5,6,1,2]
[7,4,3,5,7,1,7,4,3,5,7,1,2]
[7,4,4,3,4,1,7,4,4,3,4,1,2]
[7,4,4,4,3,1,7,4,4,4,3,1,2]
[7,4,5,6,3,1,7,4,5,6,3,1,2]
[7,4,5,7,3,1,7,4,5,7,3,1,2]
[7,5,6,3,4,1,7,5,6,3,4,1,2]
[7,5,6,4,3,1,7,5,6,4,3,1,2]
[7,5,7,3,4,1,7,5,7,3,4,1,2]
[7,5,7,4,3,1,7,5,7,4,3,1,2]
[7,6,5,3,4,1,7,6,5,3,4,1,2]
[7,6,5,4,3,1,7,6,5,4,3,1,2]

CPU time 25.392 seconds.
yes

对于这个特殊问题,不到一分钟。

我不认为约束编程会对这类问题有任何帮助,尤其是"找到20个第一个解决方案"变体。

更新:我的计算机上不同系统上同一程序的运行时间:

B-Prolog 8.1 with tabling: 26 sec
B-Prolog 8.1 without tabling: 128 sec
ECLiPSe 6.1 #187: 122 sec
SWI-Prolog 6.2.6: 330 sec