【问题标题】:How to generate a grid of predicates in Prolog?如何在 Prolog 中生成谓词网格?
【发布时间】:2019-08-07 11:26:24
【问题描述】:

我正在尝试在 Prolog 中创建一个程序,但我还是新手,不确定在 Prolog 中生成网格状结构的最佳方法是什么。

例如,假设我有一个谓词 cell/4,它有 4 个参数; NorthWestSouthEast。我要创建的是谓词 cell/4 的 NxN 网格,它根据它们在网格中的位置相互连接。

例如,我可以手动创建一个 2x2 网格。期望的结果如下:

               North_1                               North_2
                  |                                     |
        +---------+----------+                +---------+----------+
        |       North        |                |       North        |
        |                    | Interconnect_1 |                    |
West_1 -+ West  cell/4  East +----------------+ West  cell/4  East +- East_1
        |                    |                |                    |
        |       South        |                |       South        |
        +---------+----------+                +---------+----------+
                  |                                     |
            Interconnect_2                        Interconnect_3
                  |                                     |
        +---------+----------+                +---------+----------+
        |       North        |                |       North        |
        |                    | Interconnect_4 |                    |
West_2 -+ West  cell/4  East +----------------+ West  cell/4  East +- East_2
        |                    |                |                    |
        |       South        |                |       South        |
        +---------+----------+                +---------+----------+
                  |                                     |
               South_1                               South_2
cell(North, West, South, East) :- % Processing logic.
problem(North_1, North_2, West_1, West_2, South_1, South_2, East_1, East_2) :-
    cell(North_1, West_1, Interconnect_2, Interconnect_1),
    cell(North_2, Interconnect_1, Interconnect_3, East_1),
    cell(Interconnect_2, West_2, South_1, Interconnect_4),
    cell(Interconnect_3, Interconnect_4, South_2, East_2).

所以我的问题是,如何将这个过程概括为 NxN 网格?我确信有很好的方法来处理problem 谓词的参数,使其更通用problem(North, West, South, East),其中每个参数都是一个列表。不过,我正在努力生成这种类似网格的结构。

【问题讨论】:

  • 我会先尝试创建一个双向链表。这在我看来有点像“四重链表”。您认为这样做可能会使这更容易处理。
  • @DanielLyons:双向链表将不可避免地是一个无限项。不是最好的开始。
  • @false 如果你从他的例子中删除 North 和 South,它是一个双向链表。
  • @DanielLyons,感谢您的观点。我将研究如何实现各种链表,看看它会把我带到哪里。

标签: prolog swi-prolog


【解决方案1】:

这是我的解决方案:

首先我们准备好一些访问谓词:

cell(_, _, _, _). % Define the cell

% Access the positions
north(cell(C, _, _, _), C).
south(cell(_, C, _, _), C).
east( cell(_, _, C, _), C).
west( cell(_, _, _, C), C).

现在我们需要思考建立联系意味着什么。 有两个方向,所以我们统一变量来连接两个单元格

connect_horizontally(C1, C2) :-
    east(C1, C), west(C2, C). % ltr Unify variables in cells
connect_vertically(C1, C2) :-
    south(C1, C), north(C2, C). % top to bottom

这让我想到,我们可以将其应用于整行或整列

connect_row([_|[]]). % Base case is single element
connect_row([H1, H2|T]) :-
    connect_horizontally(H1, H2),
    connect_row([H2|T]).

connect_column([_|[]]).
connect_column([H1, H2|T]) :-
    connect_vertically(H1, H2),
    connect_column([H2|T]).

因此我们可以连接所有的行和所有的列。我假设一个平面列表 为了表示网格,我们很快就会声明。

connect_rows(_, []).
connect_rows(RL, Grid) :-
    \+ Grid = [],
    length(Row, RL), % A template row list
    connect_row(Row), % Make the row and connect
    append(Row, Tail, Grid), % The row appended to the tail makes the grid
    connect_rows(RL, Tail). % Recurse with the tail.

connect_cols(N, Grid) :-
    connect_cols(1, N, Grid). % 1 to start from first column
connect_cols(M, M, Grid) :- % This'll be the last column
    get_col(M, M, Grid, Col), 
    connect_column(Col), !.
connect_cols(N, M, Grid) :- 
    get_col(N, M, Grid, Col), % Get column numbered N
    connect_column(Col), % Connect them
    succ(N, O),
    connect_cols(O, M, Grid). % Carry on with the next column

所以现在我们可以通过创建一个合适长度的列表并连接 所有的行和列。

% to make an N by N Grid
grid(N, Grid) :-
    M is N*N,
    length(Grid, M),
    connect_rows(N, Grid),
    connect_cols(N, Grid).

我们仍然有一个未声明的实用程序谓词:get_col/4。这会很不雅...

%! get_col(+Index, +RowWidth, +Grid, -Col)
get_col(N, W, Grid, Col) :-
    N =< W,
    length(Grid, L),
    col_indexes(N, W, L, I), % Get a list of the column indexes for the given width and length of the whole list
    maplist(nth(Grid), I, Col). % Get those cols

% nth is nth1/3 with arguments re-ordered for the sake of maplist
nth(List, N, El) :-
    nth1(N, List, El).

% Indexes is a list of numbers starting from S, incremented by N, up to M.
col_indexes(S, N, M, Indexes) :-
    col_indexes(N, S, M, [S|H]-H, Indexes).
col_indexes(N, A, M, Indexes-[], Indexes) :-
    N + A > M, !.
col_indexes(N, A, M, Acc-[NA|H], Indexes) :-
    NA is N + A,
    col_indexes(N, NA, M, Acc-H, Indexes).

最后是问题谓词(它也需要一个大小,以便它可以 生成网格):

problem(Size, North, South, East, West) :-
    grid(Size, Grid),
    maplist(north, Grid, North),
    maplist(south, Grid, South),
    maplist(east, Grid, East),
    maplist(west, Grid, West).

我尝试的另一种方法是将连接谓词传递给列表的二维列表,该列表将在一次传递中生成所有连接,而不是传递行和列的这种方法。此方法还有几个标志表明效率低下,例如递归中的append/3,并且可以改进列连接方法。但是,如果您理解它并且它可以在足够的时间内为您的用例工作,那么它就可以完成这项工作。

【讨论】:

  • 感谢您花时间回答这个问题。当我回答另一个答案时,请知道我可能需要一些时间来消化这个,因为我对 Prolog 还是很陌生。如果您碰巧知道一个更好的论坛来提出一般性问题,我将不胜感激。 StackOverflow 适用于特定问题,但由于我仍在学习,我担心我的问题可能过于模糊。
  • 您可以使用 lambda 库来避免定义 nth/3 辅助谓词。如果您使用yall + apply 执行此操作,则不会产生任何开销。
【解决方案2】:

我很难想象你会对cell/4 感到满意,因为你有四个指向其他方向的指针,但是这些单元格中的只是指向其他单元格的指针,它们只是指针到其他单元格...我想你可能真的想要cell/5,它更像是某个值加上每个方向的指针。

一般来说,如果你想要一个大小为 N 的列表,你可以使用length/2 为你生成一个,如下所示:

?- length(L, 3).
L = [_772, _778, _784].

然后您可以传递该变量列表。大概你正在制作一个迷宫或其他东西,并且你想将你的网格传递给一些将在其中放置泥浆和墙壁或其他东西的过程,这就是你想要这个网格的原因。我上面的评论是说这个结构,在 Mx1 排列中,类似于一个双向链表:

            +--------------------+                +--------------------+
            |                    | Interconnect_1 |                    | 
    West_1 -+ West  cell/2  East +----------------+ West  cell/2  East +- East_1
            |                    |                |                    |
            +--------------------+                +--------------------+  

您可以以类似的方式手动构建此结构:

?- West = cell(West_1, East), East = cell(West, East_1).
West = cell(West_1, cell(West, East_1)),
East = cell(West, East_1).

@false 正确地指出这将是递归的,因为 West 等于其中包含 West 的某个结构。我与它分享他的疑虑,因为无限项提出了有趣的问题,而且,通常你可以在遍历过程中保持先前的值并避免问题。 (在你的情况下,我猜这将形成一个只有东指针和南指针的网格,或者一个纬度和一个纵向的其他组合,而不是两者的组合)。

无论如何,您都可以按照length/2 的示例构建双向链表,并传入您想要的长度并一次构建一个节点:

generate(0, Prev, cell(Prev, _)).
generate(N, Prev, cell(Prev, Next)) :-
    succ(N0, N),
    generate(N0, cell(Prev, Next), Next).

这是 N=3 的情况:

?- generate(3, Start, X).
X = cell(Start, cell(_S1, cell(_S2, _S3))), % where
    _S1 = cell(Start, cell(_S1, cell(_S2, _S3))),
    _S2 = cell(_S1, cell(_S2, _S3)),
    _S3 = cell(cell(_S2, _S3), _656) 

再次,让我指出,单链表中的 cons 单元格将类似于 cell/2,因为有一个值和一个 next 指针,所以我们可能需要为此添加一个值槽并返回cell/3 结构。

所以,回到网格,你可能需要做的是生成一个 NxN 网格,类似于一次生成一行,每次都保持上一行并将其传递给某种 zip- up 进程将前一行的South 指针等同于当前行的North 指针。

我在这里有一个单链接网格案例的解决方案。我希望这足以满足您的需求。想出来有点棘手!

首先,我们需要能够生成一行:

generate_row(1, cell(_, nil, _)).
generate_row(N, cell(_, Next, _)) :-
    succ(N0, N),
    generate_row(N0, Next).

这里的计划是我们有一个cell(Value, NextRight, NextDown) 结构。 NextRightNextDown 都是单元格,分别是网格中的东和南方向。我使用nil 来表示空列表的作用;它终止我们的递归并表示一个空指针。事实证明这是一件很重要的事情,否则我的拼接过程将有无限递归。

现在我们有了一行,让我们考虑如何组合上排和下排。我们在这里真正要做的就是从左到右遍历两行,将上一行的 NextBelow 等同于第二个列表中它下面的单元格。这读起来有点奇怪,但确实有效:

stitch(cell(_, NextAbove, Below), Below) :-
    Below = cell(_, NextBelow, _),
    stitch(NextAbove, NextBelow).
stitch(cell(_, nil, Below), Below).

因为我们需要Below 来保持完整,所以我们将其分解为身体而不是头部。我在这里匹配nil 以终止递归。

现在我们有了生成整个网格所需的所有部分:我们将生成一行,递归生成其余行,并使用stitch/2 将新行放在递归生成的行之上。现在我们还需要一个辅助参数,以便我们可以倒数行数。

generate_rows(2, N, Above) :-
    generate_row(N, Above),
    generate_row(N, Below),
    stitch(Above, Below).
generate_rows(M, N, Grid) :-
    succ(M0, M),
    generate_row(N, Grid),
    generate_rows(M0, N, Below),
    stitch(Grid, Below).

我觉得我的基本情况是一个 2xM 矩阵;它可能可以通过更仔细的编码使其适用于 1xM,但这就是我想出的。

运行它,你不会立即看到有用的结果:

?- generate_rows(3, 3, X).
X = cell(_8940, cell(_8948, cell(_8956, nil, cell(_8980, nil, cell(_9004, nil, _9008))), cell(_8972, cell(_8980, nil, cell(_9004, nil, _9008)), cell(_8996, cell(_9004, nil, _9008), _9000))), cell(_8964, cell(_8972, cell(_8980, nil, cell(_9004, nil, _9008)), cell(_8996, cell(_9004, nil, _9008), _9000)), cell(_8988, cell(_8996, cell(_9004, nil, _9008), _9000), _8992))) ;

但是,如果你格式化它,它就会开始有意义:

X = cell(_8940,
         cell(_8948,
              cell(_8956,
                   nil,
                   cell(_8980,
                        nil,
                        cell(_9004, nil, _9008))),
              cell(_8972,
                   cell(_8980,
                        nil,
                        cell(_9004, nil, _9008)),
                   cell(_8996, cell(_9004, nil, _9008), _9000))),
         cell(_8964,
              cell(_8972,
                   cell(_8980,
                        nil,
                        cell(_9004, nil, _9008)),
                   cell(_8996, cell(_9004, nil, _9008), _9000)),
              cell(_8988,
                   cell(_8996,
                        cell(_9004, nil, _9008),
                        _9000),
                   _8992))) ;

好的,读取第一个位置的所有变量,直到您尽可能缩进为止。然后从最左边第二个单元格的第一个位置读取它们,然后重复。你应该得到一个 3x3 变量表:

_8940 _8948 _8956
_8964 _8972 _8980
_8988 _8996 _9004

现在查看该表,您应该注意到 _8940 应该有两个孩子:8948(东)和 8964(南),而且确实如此。您会注意到 8972 应该有两个孩子:8980(东)和 8996(南),确实如此。树中有很多重复,但它是一致的。

无论如何,这并不完全是您问题的解决方案。但我希望它对你有帮助。如果您仍然决定对其进行双向链接,则必须按照从length/2 推广双向链表的方式来推广此解决方案。希望这里有足够的内容让您了解如果您决定必须这样做,您必须做些什么,但这是我现在愿意接受的范围。

【讨论】:

  • 感谢您对这个答案的想法。请注意,我可能需要一些时间来消化这一点,因为我对 Prolog 还是很陌生。如果您碰巧知道一个更好的论坛来提出一般性问题,我将不胜感激。 StackOverflow 适用于特定问题,但由于我仍在学习,我担心我的问题可能过于模糊。
  • @pjbollinger 感谢您提出一个有趣的问题!如果您正在寻找一个讨论论坛,那么您将无法击败SWI-Prolog Discourse
【解决方案3】:

假设cell(X, Y) 表示坐标XY 处的网格单元,其中XY 大于0。现在,您可以编写有关网格单元的有趣内容。例如,您可以捕获单元格有效的含义。

valid(cell(X, Y)) :- X > 0, Y > 0.

或捕获right_neighborleft_neighbor 等关系。

right_neighbor(cell(X, Y1), cell(X, Y2)) :-
    Y2 is Y1 + 1,
    valid(cell(X, Y1)),
    valid(cell(X, Y2)).

left_neighbor(cell(X, Y1), cell(X, Y2)) :-
    Y1 is Y2 + 1,
    valid(cell(X, Y1)),
    valid(cell(X, Y2)).

您可以定义具有N 列的网格的第R 行,如下所示。

grid_row(R, 1, [cell(R, 1)]) :-
    valid(cell(R, 1)).

grid_row(R, N, [cell(R, N) | Row]) :-
    M is N - 1,
    valid(cell(R, M)),
    grid_row(R, M, Row).

那么定义一个M行和N列的网格就很简单了。

grid(1, N, [Row]) :-
    grid_row(1, N, Row).

grid(M, N, [Row | Rows]) :-
    grid_row(M, N, Row),
    P is M - 1,
    grid(P, N, Rows).

例如,查询grid(3,2,X). 会产生:

X = [[cell(3, 2), cell(3, 1)], [cell(2, 2), cell(2, 1)], [cell(1, 2), cell(1, 1)]]

如果我们使用cell(North, West, South, East) 表示,其中NorthWestSouthEast 是分别指向其北、西、南和东单元的单元连接点,如果有的话,那么解决方案类似。我们首先定义单行的单元格如下。

problem_row([N], W, [S], E, [cell(N, W, S, E)]).

problem_row([N|Ns], W, [S|Ss], E, [cell(N, W, S, I)|Cs]) :-
    problem_row(Ns, I, Ss, E, Cs).

查询problem_row([n1,n2], w1, [s1,s2], e1, R). 产生:

R = [cell(n1, w1, s1, _1428), cell(n2, _1428, s2, e1)]

我们定义problem谓词如下。

problem(Ns, [W], Ss, [E], Cs) :-
    problem_row(Ns, W, Ss, E, Cs).

problem(Ns, [W|Ws], Ss, [E|Es], Cs) :-
    problem_row(Ns, W, Is, E, R),
    problem(Is, Ws, Ss, Es, Rs),
    append(R, Rs, Cs).

查询problem([n1,n2], [w1,w2], [s1,s2], [e1,e2], G) 产生:

G = [cell(n1, w1, _1460, _1462), cell(n2, _1462, _1476, e1),
     cell(_1460, w2, s1, _1494), cell(_1476, _1494, s2, e2)]

您应该注意,我已将您的 cell 谓词具体化为函子,以避免在运行时向程序添加子句,这很可能不是您想要的。另外,请注意,网格答案包含您在问题中请求的未绑定变量。你可以为它们创建原子。例如,通过连接北部和西部原子的每个组合。

【讨论】:

  • Y2 = Y1 + 1 可能需要为Y2 is Y1 + 1,但最好是succ(Y1, Y2)
  • @Daniel Lyons - 谢谢!修好了!
  • 谢谢@RobertBaron,我想我明白你在这里做什么来生成网格。但是,我没有立即看到这个答案如何解释细胞之间的连接。例如,说我的原始示例cell(North, West, South, East) :- succ(North, South), succ(West, East). 我希望能够在South_1 的原始问题中将North_1 设置为1 以解析为3。这有意义吗?还是我只是添加到 cell(X, Y) 谓词来处理每个单元格的变量?
  • @pjbollinger - 不确定succ 是什么,因为它不在原始问题中,但使用您的定义cell 与互连点,解决方案基本相同。我更新了我的答案。
  • @RobertBaron succ/2 是一个用于进行 Peano 算术的 ISO Prolog 谓词。它的好处是它是双向的(你可以加或减一),它只适用于自然数(它不会变成负数)。使用它代替 X is Y+1 类型构造通常有助于扩展您的谓词有效的实例化模式。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多