如何在Prolog中生成谓词网格?

时间:2019-08-07 11:26:24

标签: prolog swi-prolog

我正在尝试在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),其中每个参数都是一个列表。我正在努力生成这种类似网格的结构。

3 个答案:

答案 0 :(得分:2)

这是我的解决方法:

首先,我们设置阶段并提出一些访问谓词:

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).

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

答案 1 :(得分:1)

让我们说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)).

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

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谓词作为函子进行了形式化,以避免在运行时必须向程序中添加子句,很可能不是您想要的。另外,请注意,网格答案包含您在问题中所要求的未绑定变量。您可以为它们创建原子。例如,通过串联北和西原子的每个组合。

答案 2 :(得分:1)

我很难想象您会对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,因为这里有一个值和下一个指针,因此我们可能需要为此添加一个值槽并返回cell/3结构。

因此,回到网格,生成NxN网格可能需要做的事情类似于一次生成一行,保持每次生成的上一行并将其传递给某种zip-向上过程,将上一行的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进行双向链接列表推广的方式来推广此解决方案。希望这里有足够的资源供您查看,如果您决定必须这样做,那是必须要做的,但这是我目前愿意接受的。