Пролог способы сравнения переменных - PullRequest
2 голосов
/ 02 апреля 2019

Я пытался реализовать некоторые графовые алгоритмы в Прологе. Мне пришла в голову идея использовать унификацию для построения дерева из структуры графа:

График будет определен следующим образом:

  • Список Vertex-Variable пар, где Vertex - это константа, представляющая вершину, а Variable - соответствующая переменная, которая будет использоваться в качестве «ссылки» на вершину. e.g.:

    [a-A, b-B, c-C, d-D]

  • Список пар VertexVar-NeighboursList, где VertexVar и отдельные соседи в NeighboursList являются "ссылочными переменными". e.g.:

    [A-[B, C, D], B-[A, C], C-[A, B], D-[A]] означает b, c, d являются соседями a и т. Д.

Затем перед некоторым алгоритмом графа (таким как поиск компонентов или простой DFS / BFS и т. Д.), Который может использовать какое-то дерево, построенное из исходного графа, можно использовать некоторый предикат, такой как unify_neighbours, который объединяет VertexVar-NeighbourList пары как VertexVar = NeighboursList. После этого переменные вершины можно интерпретировать как списки своих соседей, где каждый сосед снова является списком своих соседей.

Таким образом, это приведет к хорошей производительности при обходе графа, поскольку нет необходимости в линейном поиске некоторой вершины и ее соседей для каждой вершины графа.

Но моя проблема: как сравнить эти вершинные переменные? (Чтобы проверить, одинаковы ли они.) Я пытался использовать A == B, но есть некоторые конфликты. В приведенном выше примере (с предикатом unify_neighbours) Пролог внутренне интерпретирует график как:

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

где:

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]]

Проблема с S_1 и S_2 (он же b и c), так как X = [something, Y], Y = [something, X], X == Y равен true. Та же проблема будет с вершинами, которые имеют одинаковых соседей. например U-[A, B] и V-[A, B].

Итак, мой вопрос: есть ли другой способ сравнения переменных, который мог бы помочь мне в этом? Что-то, что сравнивает «сами переменные», а не содержимое, как сравнение адресов в процедурных языках программирования? Или это будет слишком процедурно и нарушит декларативную идею Пролога?

* +1057 * Пример * * одна тысяча пятьдесят восемь
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).

Этот пример на самом деле не работает, но это идея. (Кроме того, проверка того, были ли найдены вершины, выполняется линейно, поэтому производительность по-прежнему невелика, но это только для демонстрации.) Теперь предикат, который находит соответствующую вершину для переменной, реализован так:

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

, где оператор \== не совсем то, что я хочу, и он создает эти конфликты.

Ответы [ 2 ]

3 голосов
/ 03 апреля 2019

Присвойство Пролога заключается в том, что после того, как вы связали переменную с термином, она становится неотличимой от самого термина . Другими словами, если вы связываете две переменные с одним и тем же термином, у вас есть две одинаковые вещи, и невозможно отделить их друг от друга.

Применительно к вашему примеру: как только вы объедините каждую переменную вершины с соответствующим списком соседей, все переменные исчезнут: у вас останется просто вложенная (и, скорее всего, круговая) структура данных, состоящая из списка списки списков ...

Но, как вы предлагаете, вложенная структура является привлекательной идеей, поскольку она дает вам прямой доступ к смежным узлам. И хотя система Prolog несколько отличается в том, насколько хорошо они поддерживают циклические структуры данных, это не должно мешать вам использовать эту идею.

Единственная проблема с вашим дизайном состоит в том, что узел идентифицируется исключительно (потенциально глубоко вложенной и циклической) структурой данных, которая описывает подграф, который доступен из него. Это приводит к тому, что

  • два узла с одинаковыми потомками неразличимы
  • может быть очень дорого проверить, идентичны ли два «похожих» подграфа или нет

Простой способ обойти это - включить уникальный идентификатор узла (например, имя или номер) в вашу структуру данных. Чтобы использовать ваш пример (немного измененный, чтобы сделать его более интересным):

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

Затем вы можете использовать этот идентификатор для проверки соответствия узлов, например, в обход глубины:

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

Пример прогона:

?- 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)
0 голосов
/ 19 июня 2019

Спасибо, @jschimpf, за ответ.Это многое прояснило для меня.Я только что вернулся к некоторым проблемам с графами в Prolog и подумал, что еще раз попробую эту рекурсивную структуру данных, и предложил следующие предикаты для построения этой структуры данных из списка ребер:

«Руководство»создание структуры данных, предложенной @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.

, где edgeTo(Weight, VertexVar) представляет ребро некоторой вершины с весом, связанным с ней.Вес только для того, чтобы показать, что это можно настроить для любой дополнительной информации.node(Vertex, [edgeTo(Weight, VertexVar), ...]) представляет вершину со своими соседями.

Более «удобный» формат ввода:

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

С необязательным списком вершин:

[Vertex, ...]

Для приведенного выше примера:

[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)]

Этот список можно преобразовать в рекурсивную структуру данных со следующими предикатами:

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

В двоичных версиях этих предикатов предполагается, что каждая вершина можетбыть полученным только из списка ребер - в графе нет вершин без ребер.Тройные версии принимают дополнительный список вершин именно для этих случаев.

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

Чтобы получить все вершины из списка ребер:

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

Чтобы создать неинициализированные переменные для каждой вершины:

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

Для сопряжениявершины и переменные вершин:

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

Чтобы построить рекурсивные узлы:

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

Сначала инициализируется список пустых узлов для каждой вершины:

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

Затем края вставляются один за другим:

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

Чтобы получить переменную вершины для данной вершины: (Это на самом деле работает в обоих направлениях.)

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

Где неинициализированпеременная OrderingVar может быть «назначена» (инициализирована) в постоянное время, например, с информацией о положении вершины в частичном упорядочении графа.Так что это может быть использовано в качестве вывода.(Как иногда обозначается +- в комментариях Пролога - неинициализированная переменная как часть входного термина, которая еще не инициализирована использованным предикатом и обеспечивает вывод.)

...