我正在尝试在Prolog中创建一个程序,但是我还是一个新手,不知道在Prolog中生成网格状结构的最佳方法是什么。
作为一个例子,假设我有一个谓词cell/4
,它有4个参数; North
,West
,South
和East
。我要创建的是一个谓词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)
,其中每个参数都是一个列表。我正在努力生成这种类似网格的结构。
答案 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)
代表坐标X
和Y
的网格单元,其中X
和Y
大于0
。现在,您可以编写有关网格单元的有趣内容。例如,您可以捕获一个单元格有效的含义。
valid(cell(X, Y)) :- X > 0, Y > 0.
或者捕获诸如right_neighbor
,left_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)
,其中North
,West
,South
和East
是分别指向其北,西,南的像元连接点,和东单元格(如果有),则解决方案是相似的。我们首先定义单行的单元格,如下所示。
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)
结构。 NextRight
和NextDown
都是单元格,分别是网格中的东和南方向。我使用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
进行双向链接列表推广的方式来推广此解决方案。希望这里有足够的资源供您查看,如果您决定必须这样做,那是必须要做的,但这是我目前愿意接受的。