Получение связных компонентов графа на Прологе

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

h(0,1).
h(1,2).
h(3,4).
h(3,5).

Таким образом, есть два отдельных компонента графика. Я хотел бы, чтобы все отдельные компоненты на выходе были представлены в виде списка. Таким образом, если на графике есть три отдельных компонента, будет три списка. Для приведенного выше примера ожидаемый результат равен [[0,1,2],[3,4,5]].


person Darki    schedule 16.01.2016    source источник
comment
Итак, вы ищете предикат, который возвращает [[0,1],[3,4,5]]?   -  person Willem Van Onsem    schedule 16.01.2016
comment
Нет, извините, я должен быть более конкретным. Факты h() приведены только для примера. Могут быть разные ребра и разное количество компонент графа. Дело в том, что программа должна записывать списки компонентов. Так что в данном случае это: '[[0,1,2],[3,4,5]]'   -  person Darki    schedule 16.01.2016
comment
вы ищете базовый алгоритм или он должен быть эффективным?   -  person Willem Van Onsem    schedule 17.01.2016
comment
Он просто должен служить своей цели. Нет необходимости в эффективности.   -  person Darki    schedule 17.01.2016


Ответы (2)


Используя iwhen/2, мы можем определить binrel_connected/2 следующим образом:

:- use_module(library(ugraphs)).
:- use_module(library(lists)).

binrel_connected(R_2, CCs) :-
   findall(X-Y, call(R_2,X,Y), Es),
   iwhen(ground(Es), ( vertices_edges_to_ugraph([],Es,G0),
                       reduce(G0,G),
                       keys_and_values(G,CCs,_) )).

Пример запроса в SICStus Prolog 4.5.0 с symm/2 для симметричное закрытие:

| ?- binrel_connected(symm(h), CCs).
CCs = [[0,1,2],[3,4,5]] ? ;
no
person repeat    schedule 16.01.2016
comment
ensure_ground/3 не только гарантирует, что есть только решения (основные ответы), но и отсекает дальнейшие ответы. На самом деле, это детерминировано для ensure_ground(p,X,X)! с p(a,1,1). p(a,_,_). - person false; 07.02.2016
comment
Хуже того, это удается даже p(a,_,_). p(a,1,1).. - person false; 08.02.2016
comment
@ЛОЖЬ. Правильный! Спасибо! - person repeat; 08.02.2016
comment
Что означает имя iwhen? - person Mostowski Collapse; 26.10.2019

Компоненты сильной связи вычисляются этим модулем. Я взял его с сайта Маркуса Триски.

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Strongly connected components of a graph.
   Written by Markus Triska ([email protected]), 2011, 2015
   Public domain code.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

:- module(scc, [nodes_arcs_sccs/3]).

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

   Usage:

   nodes_arcs_sccs(+Ns, +As, -SCCs)

   where:

   Ns is a list of nodes. Each node must be a ground term.
   As is a list of arc(From,To) terms where From and To are nodes.
   SCCs is a list of lists of nodes that are in the same strongly
        connected component.

   Running time is O(|V| + log(|V|)*|E|).

   Example:

   %?- nodes_arcs_sccs([a,b,c,d], [arc(a,b),arc(b,a),arc(b,c)], SCCs).
   %@ SCCs = [[a,b],[c],[d]].

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

:- use_module(library(assoc)).

nodes_arcs_sccs(Ns, As, Ss) :-
        must_be(list(ground), Ns),
        must_be(list(ground), As),
        catch((maplist(node_var_pair, Ns, Vs, Ps),
               list_to_assoc(Ps, Assoc),
               maplist(attach_arc(Assoc), As),
               scc(Vs, successors),
               maplist(v_with_lowlink, Vs, Ls0),
               keysort(Ls0, Ls1),
               group_pairs_by_key(Ls1, Ss0),
               pairs_values(Ss0, Ss),
               % reset all attributes
               throw(scc(Ss))),
              scc(Ss),
              true).

% Associate a fresh variable with each node, so that attributes can be
% attached to variables that correspond to nodes.

node_var_pair(N, V, N-V) :- put_attr(V, node, N).

v_with_lowlink(V, L-N) :-
        get_attr(V, lowlink, L),
        get_attr(V, node, N).

successors(V, Vs) :-
        (   get_attr(V, successors, Vs) -> true
        ;   Vs = []
        ).

attach_arc(Assoc, arc(X,Y)) :-
        get_assoc(X, Assoc, VX),
        get_assoc(Y, Assoc, VY),
        successors(VX, Vs),
        put_attr(VX, successors, [VY|Vs]).

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Tarjan's strongly connected components algorithm.

   DCGs are used to implicitly pass around the global index, stack
   and the predicate relating a vertex to its successors.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

scc(Vs, Succ) :- phrase(scc(Vs), [s(0,[],Succ)], _).

scc([])     --> [].
scc([V|Vs]) -->
        (   vindex_defined(V) -> scc(Vs)
        ;   scc_(V), scc(Vs)
        ).

scc_(V) -->
        vindex_is_index(V),
        vlowlink_is_index(V),
        index_plus_one,
        s_push(V),
        successors(V, Tos),
        each_edge(Tos, V),
        (   { get_attr(V, index, VI),
              get_attr(V, lowlink, VI) } -> pop_stack_to(V, VI)
        ;   []
        ).

vindex_defined(V) --> { get_attr(V, index, _) }.

vindex_is_index(V) -->
        state(s(Index,_,_)),
        { put_attr(V, index, Index) }.

vlowlink_is_index(V) -->
        state(s(Index,_,_)),
        { put_attr(V, lowlink, Index) }.

index_plus_one -->
        state(s(I,Stack,Succ), s(I1,Stack,Succ)),
        { I1 is I+1 }.

s_push(V)  -->
        state(s(I,Stack,Succ), s(I,[V|Stack],Succ)),
        { put_attr(V, in_stack, true) }.

vlowlink_min_lowlink(V, VP) -->
        { get_attr(V, lowlink, VL),
          get_attr(VP, lowlink, VPL),
          VL1 is min(VL, VPL),
          put_attr(V, lowlink, VL1) }.

successors(V, Tos) --> state(s(_,_,Succ)), { call(Succ, V, Tos) }.

pop_stack_to(V, N) -->
        state(s(I,[First|Stack],Succ), s(I,Stack,Succ)),
        { del_attr(First, in_stack) },
        (   { First == V } -> []
        ;   { put_attr(First, lowlink, N) },
            pop_stack_to(V, N)
        ).

each_edge([], _) --> [].
each_edge([VP|VPs], V) -->
        (   vindex_defined(VP) ->
            (   v_in_stack(VP) ->
                vlowlink_min_lowlink(V, VP)
            ;   []
            )
        ;   scc_(VP),
            vlowlink_min_lowlink(V, VP)
        ),
        each_edge(VPs, V).

v_in_stack(V) --> { get_attr(V, in_stack, true) }.

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   DCG rules to access the state, using semicontext notation.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

state(S), [S] --> [S].

state(S0, S), [S] --> [S0].

Теперь нам нужно связать его с вашим форматом. Сначала констатируйте факты:

?- [user].
h(0,1).
h(1,2).
h(3,4).
h(3,5).
|: (^D here)

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

?- setof(N, X^(h(N,X);h(X,N)), Ns), findall(arc(X,Y), (h(X,Y);h(Y,X)), As), nodes_arcs_sccs(Ns,As,SCCs).
Ns = [0, 1, 2, 3, 4, 5],
As = [arc(0, 1), arc(1, 2), arc(3, 4), arc(3, 5), arc(1, 0), arc(2, 1), arc(4, 3), arc(5, 3)],
SCCs = [[0, 1, 2], [3, 4, 5]].

Возможно, стоит определить сервисный предикат connected(X,Y) :- h(X,Y) ; h(Y,X)....

изменить

Конечно, в случае, если высокооптимизированная реализация, найденная в модуле (scc), считается излишней, мы можем сократить - с изобретательностью - код до пары строк, вычислив фиксированную точку, особенно учитывая функции высокого уровня, разрешенные современным Прологом - SWI -Пролог с библиотекой(yall), в данном случае:

gr(Gc) :- h(X,Y), gr([X,Y], Gc).
gr(Gp, Gc) :-
    maplist([N,Ms]>>setof(M,(h(N,M);h(M,N)),Ms), Gp, Cs),
    append(Cs, UnSorted),
    sort(UnSorted, Sorted),
    ( Sorted \= Gp -> gr(Sorted, Gc) ; Gc = Sorted ).

называться как

?- setof(G,gr(G),L).
L = [[0, 1, 2], [3, 4, 5]].
person CapelliC    schedule 17.01.2016
comment
Вот это да. Я делаю некоторые базовые упражнения на Прологе, поэтому я ожидал что-то вроде 2-3 основных предикатов, которые должны выполнять свою задачу. Но большое спасибо. Я тоже изучу этот код. - person Darki; 17.01.2016
comment
Какой API вы бы хотели использовать для универсального метапредиката, реализующего идиому с фиксированной точкой? Некоторое время назад я реализовал несколько слишком упрощенный fixedpoint/3 (см. stackoverflow.com/a/30454790/4609915). ... то, как я вижу вещи сейчас, я бы предпочел иметь/использовать более общую реализацию, например fixedpoint(Not_Done_3, P_2, X0,X) :- call(P_2,X0,X1), if_(call(Not_Done_3,X0,X1), fixedpoint(Not_Done_3,P_2,X1,X), X1=X). Что вы думаете? - person repeat; 17.01.2016
comment
Есть ли решение, которое не использует переменные с атрибутами? - person Mostowski Collapse; 26.10.2019
comment
@TransfiniteNumbers: кажется, что gr/2 должен подойти, он не использует атрибуты. - person CapelliC; 26.10.2019
comment
Да, я видел это. Я больше имел в виду алгоритм SCC от Маркуса Триски. - person Mostowski Collapse; 26.10.2019
comment
@TransfiniteNumbers: извините, не знаю... алгоритм довольно сложный. - person CapelliC; 27.10.2019
comment
Мне нужно всего 5 предложений для вычисления SCC: stackoverflow.com/a/58575258/502187 . Это исходный код ProQuel 1991 года. - person Mostowski Collapse; 27.10.2019