graph - Definición de un camino/sendero/caminata
prolog transitive-closure (3)
¿Qué tal definir
path/4
como esta?
path(R_2, Xs, A,Z) :- % A path `Xs` from `A` to `Z` is ...
walk(R_2, Xs, A,Z), % ... a walk `Xs` from `A` to `Z` ...
all_dif(Xs). % ... with no duplicates in `Xs`.
Para ayudar a la terminación universal, intercambiamos los dos objetivos en la conjunción anterior ...
path(R_2, Xs, A,Z) :-
all_dif(Xs), % enforce disequality ASAP
walk(R_2, Xs, A,Z).
... y use la siguiente implementación diferida de
all_dif/1
:
all_dif(Xs) :- % enforce pairwise term inequality freeze(Xs, all_dif_aux(Xs,[])). % (may be delayed) all_dif_aux([], _). all_dif_aux([E|Es], Vs) :- maplist/2(dif(E), Vs), % is never delayed freeze(Es, all_dif_aux(Es,[E|Vs])). % (may be delayed)
walk/4
se define como
path/4
y
path/5
dado por el OP:
:- meta_predicate walk(2, ?, ?, ?).
walk(R_2, [X0|Xs], X0,X) :-
walk_from_to_step(Xs, X0,X, R_2).
:- meta_predicate walk_from_to_step(?, ?, ?, 2).
walk_from_to_step([], X,X, _).
walk_from_to_step([X1|Xs], X0,X, R_2) :-
call(R_2, X0,X1),
walk_from_to_step(Xs, X1,X, R_2).
La
path/4
anterior de IMO
path/4
es más simple y más accesible, especialmente para los principiantes.
¿Estarías de acuerdo?
Muchos predicados definen algún tipo de camino acíclico construido a partir de bordes definidos a través de una relación binaria, de manera muy similar a la definición del cierre transitivo . Por lo tanto, se requiere una definición genérica.
Tenga en cuenta que las nociones definidas en la teoría de grafos no coinciden fácilmente con lo que comúnmente se espera. En particular, no estamos interesados en los nombres de los bordes.
Peor aún, también la teoría de gráficos ha cambiado un poco, introduciendo la noción de walk , observando
Tradicionalmente, un camino referido a lo que ahora se conoce generalmente como un paseo abierto. Hoy en día, cuando se establece sin ninguna calificación, generalmente se entiende que una ruta es simple, lo que significa que no se repiten vértices (y, por lo tanto, no hay bordes). (El término cadena también se ha utilizado para referirse a una caminata en la que todos los vértices y bordes son distintos).
Entonces mi pregunta es: ¿Cómo nombrar y definir esta funcionalidad?
Lo que he hecho hasta ahora es definir:
path(Rel_2, Path, X0,X)
El primer argumento tiene que ser la continuación de la relación.
Luego viene el
Path
o el par de vértices.
Ejemplo de uso
n(a, b).
n(b, c).
n(b, a).
?- path(n,Xs, a,X).
Xs = [a], X = a ;
Xs = [a, b], X = b ;
Xs = [a, b, c], X = c ;
false.
Implementación
:- meta_predicate path(2,?,?,?).
:- meta_predicate path(2,?,?,?,+).
path(R_2, [X0|Ys], X0,X) :-
path(R_2, Ys, X0,X, [X0]).
path(_R_2, [], X,X, _).
path(R_2, [X1|Ys], X0,X, Xs) :-
call(R_2, X0,X1),
non_member(X1, Xs),
path(R_2, Ys, X1,X, [X1|Xs]).
non_member(_E, []).
non_member(E, [X|Xs]) :-
dif(E,X),
non_member(E, Xs).
No veo la razón para definir en la ruta / 4 los argumentos "nodo inicial" y "nodo final". Parece que una simple ruta / 2 con la regla y la lista de nodos debe ser suficiente.
Si el usuario desea una lista que comience con algún nodo (por ejemplo, ''a''), puede consultar la declaración como: ruta (some_rule, [''a'' | Q]).
Un usuario podría, por ejemplo, solicitar una ruta que tenga una longitud de 10 en la forma: longitud (P, 10), ruta (some_rule, P).
* Anexo 1 *
Algunos objetivos de utilidad se pueden agregar fácilmente, pero no son el tema principal. Ejemplo, ruta / 3 con nodo de inicio es:
path( some_rule, [start|Q], start ) :-
path ( some_rule, [start|Q ] ).
* Anexo 2 *
La adición del último nodo como argumento podría dar la falsa idea de que este argumento impulsa el algoritmo, pero no lo hace. Supongamos por ejemplo:
n(a, b).
n(a, c).
n(a, d).
y la ejecución del algoritmo de rastreo para la consulta:
[trace] ?- path( n, P, X, d ).
Call: (6) path(n, _G1025, _G1026, d) ? creep
Call: (7) path(n, _G1107, _G1026, d, [_G1026]) ? creep
Exit: (7) path(n, [], d, d, [d]) ? creep
Exit: (6) path(n, [d], d, d) ? creep
P = [d],
X = d ;
Redo: (7) path(n, _G1107, _G1026, d, [_G1026]) ? creep
Call: (8) n(_G1026, _G1112) ? creep
Exit: (8) n(a, b) ? creep
Call: (8) non_member(b, [a]) ? creep
Call: (9) dif:dif(b, a) ? creep
Exit: (9) dif:dif(b, a) ? creep
Call: (9) non_member(b, []) ? creep
Exit: (9) non_member(b, []) ? creep
Exit: (8) non_member(b, [a]) ? creep
Call: (8) path(n, _G1113, b, d, [b, a]) ? creep
Call: (9) n(b, _G1118) ? creep
Fail: (9) n(b, _G1118) ? creep
Fail: (8) path(n, _G1113, b, d, [b, a]) ? creep
Redo: (9) non_member(b, []) ? creep
Fail: (9) non_member(b, []) ? creep
Fail: (8) non_member(b, [a]) ? creep
Redo: (8) n(_G1026, _G1112) ? creep
Exit: (8) n(a, c) ? creep
Call: (8) non_member(c, [a]) ? creep
Call: (9) dif:dif(c, a) ? creep
Exit: (9) dif:dif(c, a) ? creep
Call: (9) non_member(c, []) ? creep
Exit: (9) non_member(c, []) ? creep
Exit: (8) non_member(c, [a]) ? creep
Call: (8) path(n, _G1113, c, d, [c, a]) ? creep
Call: (9) n(c, _G1118) ? creep
Fail: (9) n(c, _G1118) ? creep
Fail: (8) path(n, _G1113, c, d, [c, a]) ? creep
Redo: (9) non_member(c, []) ? creep
Fail: (9) non_member(c, []) ? creep
Fail: (8) non_member(c, [a]) ? creep
Redo: (8) n(_G1026, _G1112) ? creep
Exit: (8) n(a, d) ? creep
Call: (8) non_member(d, [a]) ? creep
Call: (9) dif:dif(d, a) ? creep
Exit: (9) dif:dif(d, a) ? creep
Call: (9) non_member(d, []) ? creep
Exit: (9) non_member(d, []) ? creep
Exit: (8) non_member(d, [a]) ? creep
Call: (8) path(n, _G1113, d, d, [d, a]) ? creep
Exit: (8) path(n, [], d, d, [d, a]) ? creep
Exit: (7) path(n, [d], a, d, [a]) ? creep
Exit: (6) path(n, [a, d], a, d) ? creep
P = [a, d],
X = a .
Como puede ver, en este caso el algoritmo no logra la fuerza bruta. Por esta razón, si el algoritmo no mejora, sugiero que no agregue "nodo final" como argumento de "ruta".
Quiero centrarme en nombrar el predicado.
-
A diferencia de
maplist/2
, el orden de los argumentos no es de importancia primordial aquí. -
El nombre del predicado debe aclarar el significado de los argumentos respectivos.
Hasta ahora, me gusta más
path_from_to_edges
, pero también tiene sus pros y sus contras.
path_from_to_edges(Path,From,To,Edges_2) :-
path(Edges_2,Path,From,To).
Vamos a separarlo:
-
pro:
path
es un sustantivo, no se puede leer mal un verbo. Para mí, una lista de vértices está implícita. -
pro:
from
representa un vértice, y también lo haceto
. -
con:
edges
es algo vago, pero usar lambdas aquí es la opción más versátil. -
con: Según Wikipedia , un camino es un camino en el que todos los vértices ( excepto posiblemente el primero y el último ) son distintos. Entonces eso debería aclararse en la descripción.
Usando lambdas para una lista de vértices vecinos
Ess
:
?- Ess = [a-[b],b-[c,a]],
From = a,
path_from_to_edges(Path,From,To,/X^Y^(member(X-X_neibs,Ess),member(Y,X_neibs))).
Ess = [a-[b],b-[c,a]], From = a, To = a, Path = [a] ;
Ess = [a-[b],b-[c,a]], From = a, To = b, Path = [a,b] ;
Ess = [a-[b],b-[c,a]], From = a, To = c, Path = [a,b,c] ;
false.
Editar 2015-06-02
¡Otra oportunidad para nombrar mejor!
Esto se inclina más al lado de
maplist/2
...
graph_path_from_to(P_2,Path,From,To) :-
path(P_2,Path,From,To).
Aquí,
graph
, por supuesto, es un sustantivo, no un verbo.
Con respecto al significado de "ruta": las rutas definitivamente deberían permitir
From=To
y no excluir eso por defecto (con desigualdades de términos por pares).
Es fácil excluir esto con un objetivo adicional
dif(From,To)
, pero no al revés.