问题 将列表理解翻译成Prolog


我在Haskell中有一个列表理解,我想翻译成Prolog。

列表理解的重点是旋转4乘4格:

rotate :: [Int] -> [Int]
rotate grid = [ grid !! (a + 4 * b) | a <- [0..3], b <- [0..3] ]

现在在Prolog中,我翻译成这样:

rotateGrid([T0,T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15],
       [T0,T4,T8,T12,T1,T5,T9,T13,T2,T6,T10,T14,T3,T7,T11,T15]).

我们可以做得更好吗?


9943
2018-04-12 19:35


起源

你为什么称之为轮换?它似乎更像是关于轴的转置或对称(它是具有固定轴的对合)。 - gniourf_gniourf
你实际上是对的,它不是严格意义上的轮换。但它具有与旋转相同的效果,即列成为行,行成为列。这对我使用这个功能至关重要。 - wvdz


答案:


我们可以用 findall/3 列表推导(参见 SWI-Prolog文档)。例如。,

?- findall(X, between(1,10,X), Xs).
Xs = [1,2,3,4,5,6,7,8,9,10]

Xs 是一个包含所有可以统一的值的列表 X 什么时候 X 是1到10之间的数字。这大致相当于Haskell表达式 let Xs = [x | x <- [1..10]](1)。你可以看一个 findall/3 声明:“查找[第一参数]的所有值,使[第二参数中的条件]成立,并将这些值放在列表中,[第三参数]”。

我用过 findall/3 写一个谓词 rotate_grid(+Grid, ?RotatedGrid)。这是我在谓词中使用的近似Haskell-Prolog等价的列表;每一行显示Haskell表达式将评估的值与具有相同值的Prolog变量之间的关系:

  • a <- [0..3] = A 在 between(0, 3, A)
  • b <- [0..3] = B 在 between(0, 3, B)
  • (a + 4 * d) = X 在 X is A + 4 * D
  • <Grid> !! <Index> = Element 在 nth0(Index, Grid, Element)

然后我们只需要找到所有的值 Element

rotate_grid(Grid, RotatedGrid) :-
    findall( Element,

            ( between(0,3,A),
              between(0,3,B),
              Index is A + 4 * B,
              nth0(Index, Grid, Element) ),

             RotatedGrid
           ).

为了验证这会产生正确的转换,我从问题中下载了Prolog代码并提出以下查询:

?- rotate_grid([t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15],
       [t0,t4,t8,t12,t1,t5,t9,t13,t2,t6,t10,t14,t3,t7,t11,t15]).
|    true.

脚注:

(1): between/3 实际上并不是类似的 [m..n],因为后者返回一个值列表 m 至 n 哪里 between(M,N,X) 将在回溯时使用M和N之间的每个值(包括)实例化X.要获取SWI-Prolog中的数字列表,我们可以使用 numlist(M,N,Ns)。所以更严格的类比 x <- [1.10] 将是结合 member(X, Ns), numlist(1, 10, Ns)


10
2018-04-12 20:47



非常好的解释,谢谢! - wvdz
@popovitsj很高兴我能帮忙! - Shon Feder
有关警告,请参阅我的回答。 - false


你想要一个列表的排列。不考虑具体要素。因此,您可以将Haskell签名推广到

rotate :: [x] -> [x]

对于Prolog来说,这已经是一个非常有价值的提示:不会考虑列表的元素 - 甚至不会比较元素。所以Prolog解决方案应该能够直接处理变量,如下所示:

| ? -  rotateGrid(L,R)。
L = [_ A,_B,_C,_D,_E,_F,_G,_H,_I,_J,_K,_L,_M,_N,_O,_P],
R = [_ A,_E,_I,_M,_B,_F,_J,_N,_C,_G,_K,_O,_D,_H,_L,_P]。

而您的原始定义完美地处理了这一点

使用列表推导的版本表明自己可以通过回溯来实现,必须采取某些预防措施。运用 findall/3,正如@aBathologist所建议的那样,将重命名变量:

| ? - 长度(L,16),rotate_grid(L,R)。
L = [_ A,_B,_C,_D,_E,_F,_G,_H,_I,_J,_K,_L,_M,_N,_O,_P],
R = [_ Q,_ R,_S,_T,_U,_V,_W,_X,_Y,_Z,_A1,_B1,_C1,_D1,_E1,_F1]。

内置谓词 bagof/3 解决了这个问题。请注意,我们必须明确声明所有本地存在变量:

rotate_grid2(Grid,RotatedGrid): - 
   bagof(
     元件,
     A ^ B ^ Index ^%存在变量的声明
       ((0,3,A)之间,
          之间(0,3,B),
          指数是A + 4 * B,
          nth0(索引,网格,元素)
       )
     RotatedGrid)。

对于短于16个元素的列表,Haskell版本会产生干净的错误,但在这里我们得到相当随机的结果:

| ? -  L = [1,2,3,4],rotate_grid(L,R)。
L = [1,2,3,4],
R = [1,2,3,4]?
是
| ? -  L = [1,2,3,4,5],rotate_grid(L,R)。
L = [1,2,3,4,5],
R = [1,5,2,3,4]?
是

这是由于枚举和“生成”具体元素的部分之间的分离不明确。最干净的方法是添加 length(Grid, 16) 在目标之前 bagof/3

Prolog中的列表理解

目前,只有B-Prolog提供了一种列表推导形式:

R @ = [E:A在0..3中,B在0..3中,[E,I],(I是A + 4 * B,nth0(I,L,E))]。

但是,它没有解决第二个问题:

| ? -  L = [1,2,3],R @ = [E:A在0..3,B在0..3,[E,I],(I是A + 4 * B,nth0(I ,L,E))。
L = [1,2,3]
R = [1,2,3]
是

4
2018-04-13 10:19





使用循环谓词foreach / 4

如果理解应该保留变量,这在约束编程中是重要的,那么Prolog系统可以提供谓词foreach / 4。这个谓词是foreach / 2的DCG伙伴。

以下是通过findall / 3保留变量的方法 结果R包含根据ISO的新变量 findall / 3的核心语义:

Welcome to SWI-Prolog (threaded, 64 bits, version 7.7.1)
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.

?- functor(L,foo,5), findall(X, 
     (between(1,5,N), M is 6-N, arg(M,L,X)), R).
L = foo(_5140, _5142, _5144, _5146, _5148),
R = [_5210, _5204, _5198, _5192, _5186].

以下是如何通过foreach / 4保留变量, 结果列表与化合物具有相同的变量 我们开始:

Jekejeke Prolog 3, Runtime Library 1.3.0
(c) 1985-2018, XLOG Technologies GmbH, Switzerland

?- [user].
helper(N,L) --> [X], {M is 6-N, arg(M,L,X)}.

Yes

?- functor(L,foo,5), foreach(between(1,5,N),helper(N,L),R,[]).
L = foo(_A,_G,_M,_S,_Y),
R = [_Y,_S,_M,_G,_A]

使用foreach / 4而不是bagof / 3可能看起来有点过头了。 foreach / 4可能只会在实现Picat循环时显示其全部潜力,因为它可以构建约束,bagof / 3无法做到。

foreach / 4是一个没有完全实现所有解决方案的实现,然后被回溯。它与bagof / 3共享变量的重建,但仍允许在闭包的结合中进行回溯。


2
2017-08-20 14:17