简体   繁体   English

Prolog比较变量的方法

[英]Prolog ways to compare variables

I was trying to implement some graph algorithms in Prolog. 我试图在Prolog中实现一些图算法。 I came up with an idea to use unification to build a tree from the graph structure: 我提出了一个使用统一来构建图结构树的想法:

The graph would be defined as follows: 该图将定义如下:

  • A list of Vertex-Variable pairs where Vertex is a constant representing the vertex and Variable is a corresponding variable, that would be used as a "reference" to the vertex. Vertex-Variable对列表,其中“ Vertex是表示顶点的常量,“ Variable是相应的变量,可用作顶点的“参考”。 eg: 例如:

    [aA, bB, cC, dD]

  • A list of VertexVar-NeighboursList pairs, where the VertexVar and the individual neighbours in the NeighboursList are the "reference variables". VertexVar-NeighboursList对的列表,其中VertexVarNeighboursList中的各个邻居是“参考变量”。 eg: 例如:

    [A-[B, C, D], B-[A, C], C-[A, B], D-[A]] meaning b , c , d are neighbours of a etc. [A-[B, C, D], B-[A, C], C-[A, B], D-[A]]意思bcd是邻居a

Then before some graph algorithm (like searching for components, or simple DFS/BFS etc.) that could use some kind of tree built from the original graph, one could use some predicate like unify_neighbours that unifies the VertexVar-NeighbourList pairs as VertexVar = NeighboursList . 然后在一些图形算法(如搜索组件,或简单的DFS / BFS等)之前可以使用从原始图形构建的某种树,可以使用一些谓词,如unify_neighbours ,将VertexVar-NeighbourList对统一为VertexVar = NeighboursList After that, the vertex variables may be interpreted as lists of its neighbours, where each neighbour is again a list of its neighbours. 之后,顶点变量可以被解释为其邻居的列表,其中每个邻居再次是其邻居的列表。

So this would result in a good performance when traversing the graph, as there is no need in linear search for some vertex and its neighbours for every vertex in the graph. 因此,这将在遍历图形时产生良好的性能,因为不需要对图形中的每个顶点的某些顶点及其邻域进行线性搜索。

But my problem is: How to compare those vertex variables? 但我的问题是:如何比较这些顶点变量? (To check if they're the same.) I tried to use A == B , but there are some conflicts. (检查它们是否相同。)我尝试使用A == B ,但存在一些冲突。 For the example above, (with the unify_neighbours predicate) Prolog interprets the graph internally as: 对于上面的示例,(使用unify_neighbours谓词)Prolog在内部将图解释为:

[a-[S_1, S_2, S_3], b-S_1, c-S_2, d-S_3]

where: 哪里:

S_1 = [[S_1, S_2, S_3], S_2]
S_2 = [[S_1, S_2, S_3], S_1]
S_3 = [[S_1, S_2, S_3]]

The problem is with S_1 and S_2 (aka b and c ) as X = [something, Y], Y = [something, X], X == Y is true . 问题在于S_1S_2 (又名bc ),因为X = [something, Y], Y = [something, X], X == Ytrue The same problem would be with vertices, that share the same neighbours. 同样的问题是顶点,它们共享相同的邻居。 eg U-[A, B] and V-[A, B] . 例如U-[A, B]V-[A, B]

So my question is: Is there any other way to compare variables, that could help me with this? 所以我的问题是:有没有其他方法来比较变量,这可以帮助我吗? Something that compares "the variables themselves", not the content, like comparing addresses in procedural programming languages? 比较“变量本身”而不是内容的东西,比如在程序编程语言中比较地址? Or would that be too procedural and break the declarative idea of Prolog? 或者这是否过于程序化并打破Prolog的陈述性思想?

Example

graph_component(Vertices, Neighbours, C) :-
    % Vertices and Neighbours as explained above.
    % C is some component found in the graph.
    vertices_refs(Vertices, Refs),
    % Refs are only the variables from the pairs.
    unify_neighbours(Neighbours), % As explained above.
    rec_(Vertices, Refs, [], C).

rec_(Vertices, Refs, Found, RFound) :-
    % Vertices as before.
    % Refs is a stack of the vertex variables to search.
    % Found are the vertices found so far.
    % RFound is the resulting component found.
    [Ref|RRest] = Refs,
    vertices_pair(Vertices, Vertex-Ref),
    % Vertex is the corresponding Vertex for the Ref variable
    not(member(Vertex, Found)),
    % Go deep:
    rec_(Vertices, Ref, [Vertex|Found], DFound),
    list_revpush_result([Vertex|Found], DFound, Found1),
    % Go wide:
    rec_(Vertices, RRest, Found1, RFound).

rec_(Vertices, Refs, Found, []) :-
    % End of reccursion.
    [Ref|_] = Refs,
    vertices_pair(Vertices, Vertex-Ref),
    member(Vertex, Found).

This example doesn't really work, but it's the idea. 这个例子并没有真正起作用,但这是个主意。 (Also, checking whether the vertices were found is done linearly, so the performance is still not good, but it's just for demonstration.) Now the predicate, that finds the corresponding vertex for the variable is implemented as: (另外,检查顶点是否被找到是线性完成的,所以性能仍然不好,但它仅用于演示。)现在,查找变量的相应顶点的谓词实现为:

vertices_pair([Vertex-Ref|_], Vertex-Ref).
vertices_pair([_-OtherRef|Rest], Vertex-Ref) :-
    Ref \== OtherRef,
    vertices_pair(Rest, Vertex-Ref).

where the \\== operator is not really what I want and it creates those conflicts. \\==运算符不是我想要的,它会产生这些冲突。

It is an intrinsic feature of Prolog that, once you have bound a variable to a term, it becomes indistinguishable from the term itself . Prolog的一个固有特征是, 一旦你将变量绑定到一个术语,它就会与术语本身无法区分 In other words, if you bind two variables to the same term, you have two identical things, and there is no way to tell them apart. 换句话说,如果将两个变量绑定到同一个术语,则有两个相同的东西,并且没有办法区分它们。

Applied to your example: once you have unified every vertex-variable with the corresponding neighbours-list, all the variables are gone: you are left simply with a nested (and most likely circular) data structure, consisting of a list of lists of lists... 应用于您的示例:一旦您将每个顶点变量与相应的邻居列表统一起来,所有变量都将消失:您只需使用嵌套(并且很可能是循环)数据结构,该列表包含列表列表...

But as you suggest, the nested structure is an attractive idea because it gives you direct access to adjacent nodes. 但正如您所建议的那样,嵌套结构是一个很有吸引力的想法,因为它可以让您直接访问相邻节点。 And although Prolog system vary somewhat in how well they support circular data structures, this need not stop you from exploiting this idea. 虽然Prolog系统在支持循环数据结构方面有所不同,但这并不能阻止你利用这个想法。

The only problem with your design is that a node is identified purely by the (potentially deeply nested and circular) data structure that describes the sub-graph that is reachable from it. 设计的唯一问题是节点完全由(可能深度嵌套和循环)数据结构识别,该数据结构描述了可从其访问的子图。 This has the consequence that 这导致了这样的结果

  • two nodes that have the same descendants are indistinguishable 具有相同后代的两个节点是无法区分的
  • it can be very expensive to check whether two "similar looking" sub-graphs are identical or not 检查两个“相似的”子图是否相同可能是非常昂贵的

A simple way around that is to include a unique node identifier (such as a name or number) in your data structure. 一种简单的方法是在数据结构中包含唯一的节点标识符 (例如名称或编号)。 To use your example (slightly modified to make it more interesting): 要使用您的示例(稍加修改以使其更有趣):

make_graph(Graph) :-
    Graph = [A,B,C,D],
    A = node(a, [C,D]),
    B = node(b, [A,C]),
    C = node(c, [A,B]),
    D = node(d, [A]).

You can then use that identifier to check for matching nodes, eg in a depth-first traversal: 然后,您可以使用该标识符来检查匹配的节点,例如在深度优先遍历中:

dfs_visit_nodes([], Seen, Seen).
dfs_visit_nodes([node(Id,Children)|Nodes], Seen1, Seen) :-
    ( member(Id, Seen1) ->
        Seen2 = Seen1
    ;
        writeln(visiting(Id)),
        dfs_visit_nodes(Children, [Id|Seen1], Seen2)
    ),
    dfs_visit_nodes(Nodes, Seen2, Seen).

Sample run: 样品运行:

?- make_graph(G), dfs_visit_nodes(G, [], Seen).
visiting(a)
visiting(c)
visiting(b)
visiting(d)

G = [...]
Seen = [d, b, c, a]
Yes (0.00s cpu)

Thanks, @jschimpf, for the answer. 谢谢,@ jschimpf,答案。 It clarified a lot of things for me. 它为我澄清了很多东西。 I just got back to some graph problems with Prolog and thought I'd give this recursive data structure another try and came up with the following predicates to construct this data structure from a list of edges: 我刚刚回到Prolog的一些图形问题,并认为我会给这个递归数据结构另一次尝试,并提出以下谓词从边列表构造这个数据结构:

The "manual" creation of the data structure, as proposed by @jschimpf: 由@jschimpf提出的“手动”创建数据结构:

my_graph(Nodes) :-
    Vars  = [A, B, C, D, E],
    Nodes = [
        node(a, [edgeTo(1, B), edgeTo(5, D)]),
        node(b, [edgeTo(1, A), edgeTo(4, E), edgeTo(2, C)]),
        node(c, [edgeTo(2, B), edgeTo(6, F)]),
        node(d, [edgeTo(5, A), edgeTo(3, E)]),
        node(e, [edgeTo(3, D), edgeTo(4, B), edgeTo(1, F)]),
        node(e, [edgeTo(1, E), edgeTo(6, C)])
    ],
    Vars = Nodes.

Where edgeTo(Weight, VertexVar) represents an edge to some vertex with a weight assosiated with it. 其中edgeTo(Weight, VertexVar)表示某个顶点的边,其重量与其相关。 The weight is just to show that this can be customized for any additional information. 重量只是为了表明这可以为任何其他信息定制。 node(Vertex, [edgeTo(Weight, VertexVar), ...]) represents a vertex with its neighbours. node(Vertex, [edgeTo(Weight, VertexVar), ...])表示一个带有邻居的顶点。

A more "user-friendly" input format: 更“用户友好”的输入格式:

[edge(Weight, FromVertex, ToVertex), ...]

With optional list of vertices: 使用可选的顶点列表:

[Vertex, ...]

For the example above: 对于上面的例子:

[edge(1, a, b), edge(5, a, d), edge(2, b, c), edge(4, b, e), edge(6, c, f), edge(3, d, e), edge(1, e, f)]

This list can be converted to the recursive data structure with the following predicates: 可以使用以下谓词将此列表转换为递归数据结构:

% make_directed_graph(+Edges, -Nodes)
make_directed_graph(Edges, Nodes) :-
    vertices(Edges, Vertices),
    vars(Vertices, Vars),
    pairs(Vertices, Vars, Pairs),
    nodes(Pairs, Edges, Nodes),
    Vars = Nodes.

% make_graph(+Edges, -Nodes)
make_graph(Edges, Nodes) :-
    vertices(Edges, Vertices),
    vars(Vertices, Vars),
    pairs(Vertices, Vars, Pairs),
    directed(Edges, DiretedEdges),
    nodes(Pairs, DiretedEdges, Nodes),
    Vars = Nodes.

% make_graph(+Edges, -Nodes)
make_graph(Edges, Nodes) :-
    vertices(Edges, Vertices),
    vars(Vertices, Vars),
    pairs(Vertices, Vars, Pairs),
    directed(Edges, DiretedEdges),
    nodes(Pairs, DiretedEdges, Nodes),
    Vars = Nodes.

% make_directed_graph(+Vertices, +Edges, -Nodes)
make_directed_graph(Vertices, Edges, Nodes) :-
    vars(Vertices, Vars),
    pairs(Vertices, Vars, Pairs),
    nodes(Pairs, Edges, Nodes),
    Vars = Nodes.

The binary versions of these predicates assume, that every vertex can be obtained from the list of edges only - There are no "edge-less" vertices in the graph. 这些谓词的二进制版本假设每个顶点只能从边列表中获得 - 图中没有“无边缘”顶点。 The ternary versions take an additional list of vertices for exactly these cases. 对于这些情况,三元版本采用额外的顶点列表。

make_directed_graph assumes the input edges to be directed, make_graph assumes them to be undirected, so it creates additional directed edges in the opposite direction: make_directed_graph假定要定向的输入边, make_graph假定它们是无向的,因此它会在相反的方向上创建额外的有向边:

% directed(+UndirectedEdges, -DiretedEdges)
directed([], []).
directed([edge(W, A, B)|UndirectedRest], [edge(W, A, B), edge(W, B, A)|DirectedRest]) :-
    directed(UndirectedRest, DirectedRest).

To get all the vertices from the list of edges: 要从边列表中获取所有顶点:

% vertices(+Edges, -Vertices)
vertices([], []).
vertices([edge(_, A, B)|EdgesRest], [A, B|VerticesRest]) :-
    vertices(EdgesRest, VerticesRest),
    \+ member(A, VerticesRest),
    \+ member(B, VerticesRest).
vertices([edge(_, A, B)|EdgesRest], [A|VerticesRest]) :-
    vertices(EdgesRest, VerticesRest),
    \+ member(A, VerticesRest),
    member(B, VerticesRest).
vertices([edge(_, A, B)|EdgesRest], [B|VerticesRest]) :-
    vertices(EdgesRest, VerticesRest),
    member(A, VerticesRest),
    \+ member(B, VerticesRest).
vertices([edge(_, A, B)|EdgesRest], VerticesRest) :-
    vertices(EdgesRest, VerticesRest),
    member(A, VerticesRest),
    member(B, VerticesRest).

To construct uninitialized variables for every vertex: 为每个顶点构造未初始化的变量:

% vars(+List, -Vars)
vars([], []).
vars([_|ListRest], [_|VarsRest]) :-
    vars(ListRest, VarsRest).

To pair up verticies and vertex variables: 要配对verticies和顶点变量:

% pairs(+ListA, +ListB, -Pairs)
pairs([], [], []).
pairs([AFirst|ARest], [BFirst|BRest], [AFirst-BFirst|PairsRest]) :-
    pairs(ARest, BRest, PairsRest).

To construct the recursive nodes: 构造递归节点:

% nodes(+Pairs, +Edges, -Nodes)
nodes(Pairs, [], Nodes) :-
    init_nodes(Pairs, Nodes).
nodes(Pairs, [EdgesFirst|EdgesRest], Nodes) :-
    nodes(Pairs, EdgesRest, Nodes0),
    insert_edge(Pairs, EdgesFirst, Nodes0, Nodes).

First, a list of empty nodes for every vertex is initialized: 首先,初始化每个顶点的空节点列表:

% init_nodes(+Pairs, -EmptyNodes)
init_nodes([], []).
init_nodes([Vertex-_|PairsRest], [node(Vertex, [])|NodesRest]) :-
    init_nodes(PairsRest, NodesRest).

Then the edges are inserted one by one: 然后逐个插入边缘:

% insert_edge(+Pairs, +Edge, +Nodes, -ResultingNodes)
insert_edge(Pairs, edge(W, A, B), [], [node(A, [edgeTo(W, BVar)])]) :-
    vertex_var(Pairs, B, BVar).
insert_edge(Pairs, edge(W, A, B), [node(A, EdgesTo)|NodesRest], [node(A, [edgeTo(W, BVar)|EdgesTo])|NodesRest]) :-
    vertex_var(Pairs, B, BVar).
insert_edge(Pairs, edge(W, A, B), [node(X, EdgesTo)|NodesRest], [node(X, EdgesTo)|ResultingNodes]) :-
    A \= X,
    insert_edge(Pairs, edge(W, A, B), NodesRest, ResultingNodes).

To get a vertex variable for a given vertex: (This actually works in both directions.) 获取给定顶点的顶点变量:(这实际上适用于两个方向。)

% vertex_var(+Pairs, +Vertex, -Var)
vertex_var(Pairs, Vertex, Var) :-
    member(Vertex-Var, Pairs).
```Prolog

This, of course, brings additional time overhead, but you can do this once and then just copy this data structure every time you need to perform some graph algorithm on it and access neighbours in constant time.

You can also add additional information to the `node` predicate. For example:

```Prolog
node(Vertex, Neighbours, OrderingVar)

Where the uninitialized variable OrderingVar can be "assigned" (initialized) in constant time with information about the vertex' position in a partial ordering of the graph, for example. 例如,未初始化变量OrderingVar可以在恒定时间内“分配”(初始化),并且具有关于图形的部分排序中的顶点位置的信息。 So this may be used as output. 所以这可以用作输出。 (As sometimes denoted by +- in Prolog comments - an uninitialized variable as a part of an input term, that is yet to be initialized by the used predicate and provides output.) (在Prolog注释中有时用+-表示+-作为输入项的一部分的未初始化变量,尚未由使用的谓词初始化并提供输出。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM