Unión prólogo de la AUBUC
Empecé a aprender Prolog recientemente y no puedo resolver cómo hacer la unión de tres listas.
Pude hacer la unión de 2 listas:
%element
element(X,[X|_]).
element(X,[_|Y]):-
element(X,Y).
%union
union([],M,M).
union([X|Y],L,S) :- element(X,L),union(Y,L,S).
union([X|Y],L,[X|S]) :- (not(element(X,L))),union(Y,L,S).
puede alguien ayudarme por favor ?
union(A, B, C, U) :-
union(A, B, V),
union(C, V, U).
Su definición de se union/3
puede mejorar reemplazando
... not(element(X,L)), ...
por
... maplist(dif(X),L), ...
o
... non_member(X, L), ....
non_member(_X, []).
non_member(X, [E|Es]) :-
dif(X, E),
non_member(X, Es).
Aquí hay un caso donde se muestra la diferencia:
?- union([A],[B],[C,D]).
A = C, B = D, dif(C, D).
¿Cómo debe
[A]
verse[B]
tal que su unión contenga 2 elementos?
La respuesta es: deben ser diferentes.
Su versión original falla para esta consulta, sin embargo, tiene éxito para una instancia especializada como:
?- A = 1, B = 2, union([A],[B],[C,D]).
Así que tiene éxito en esto, pero falla en su generalización. Por tanto, no es una relación pura y lógica.
Entonces, ¿está todo bien y perfecto dif/2
? Lamentablemente no. @TudorBerariu tiene buenas razones para optar por un recorte, ya que refleja parte de la intención que tenemos sobre la relación. El recorte refleja efectivamente dos intenciones clave
que la alternativa de no ser miembro ahora está excluida, lo cual es cierto para ciertos modos, como que Arg1 y Arg2 son términos suficientemente instanciados. Una aproximación segura serían los términos básicos.
que no hay necesidad de mirar más elementos en la lista Arg2, lo cual nuevamente solo es cierto si Arg1 y Arg2 están suficientemente instanciados.
Los problemas solo aparecen cuando los términos no están suficientemente instanciados.
El inconveniente de la definición de OP y la anterior es que ambas son innecesariamente demasiado generales, lo que se puede observar con elementos repetidos en Arg2:
?- union([a,a],[a,a],Zs).
Zs = [a, a]
; Zs = [a, a]
; Zs = [a, a]
; Zs = [a, a]
; false.
De hecho, obtenemos |Arg2| |Arg1| -1 respuestas redundantes. Así que el corte tenía una buena razón para estar ahí.
Otra razón por la que union/3
tal como está no es muy eficiente es que para el caso básico (previsto) deja abiertos puntos de elección innecesarios. Nuevamente, la solución de @TudorBerariu no tiene este problema:
?- union([a],[a],Zs).
Zs = [a]
; false. % <--- Prolog does not know that there is nothing left
Eliminando la redundancia
La verdadera culpable de tantas respuestas redundantes es la primera regla. element(a,[a,a])
(comúnmente llamado member/2
) tendrá éxito dos veces.
union([X|Y],L,S) :- element(X,L), union(Y,L,S).
^^^^^^^^^^^^
Aquí hay una definición mejorada:
memberd(X, [X|_Ys]).
memberd(X, [Y|Ys]) :-
dif(X,Y), % new!
memberd(X, Ys).
La regla recursiva, leída de derecha a izquierda, dice lo siguiente:
Supongamos
memberd(X, Ys)
que ya es cierto para algunosX
yYs
. Teniendo en cuenta eso, y dado que tenemos un ajusteY
que es diferente alX
. Entonces
podemos concluir que eso tambiénmemberd(X, [Y|Ys])
es cierto.
Esto ha eliminado las soluciones redundantes. Pero nuestra definición todavía no es muy eficiente: todavía tiene que visitar Arg2 dos veces para cada elemento y luego no puede concluir que no quedan alternativas. En cualquier caso: resistirse a colocar un corte para eliminar esto.
Introducir el determinismo a través de la cosificación.
Compara las definiciones de memberd/2
y non_member/2
. Aunque describen "lo opuesto" entre sí, se ven muy similares:
non_member(_X, []).
non_member(X, [Y|Ys]) :-
dif(X,Y),
non_member(X, Ys).
memberd(X, [X|_Ys]).
memberd(X, [Y|Ys]) :-
dif(X,Y),
memberd(X, Ys).
¡La regla recursiva es la misma! Sólo que el hecho es diferente. Fusionémoslos en una definición, con un argumento adicional que indique si queremos decir memberd
( true
) o non_member
( false
):
memberd_t(_X, [], false).
memberd_t(X, [X|_Ys], true).
memberd_t(X, [Y|Ys], Truth) :-
dif(X, Y),
memberd_t(X, Ys, Truth).
Ahora, nuestra definición se vuelve un poco más compacta:
unionp([], Ys, Ys).
unionp([X|Xs], Ys, Zs0) :-
if_( memberd_t(X, Ys), Zs0 = Zs, Zs0 = [X|Zs] ),
unionp(Xs, Ys, Zs).
memberd_t(_X, [], false). % see below
memberd_t(X, [Y|Ys], Truth) :-
if_( X = Y, Truth=true, memberd_t(X, Ys, Truth) ).
Tenga en cuenta la diferencia entre if_(If_1, Then_0, Else_0)
y el constructo de control if-then-else ( If_0 -> Then_0 ; Else_0 )
. Si bien If_1
puede tener éxito varias veces con diferentes valores de verdad (es decir, puede ser tanto verdadero como falso), el constructo de control tiene If_0
éxito solo una vez por ser verdadero únicamente.
if_(If_1, Then_0, Else_0) :-
call(If_1, T),
( T == true -> call(Then_0)
; T == false -> call(Else_0)
; nonvar(T) -> throw(error(type_error(boolean,T),_))
; /* var(T) */ throw(error(instantiation_error,_))
).
=(X, Y, T) :-
( X == Y -> T = true
; X \= Y -> T = false
; T = true, X = Y
; T = false,
dif(X, Y) % ISO extension
% throw(error(instantiation_error,_)) % ISO strict
).
equal_t(X, Y, T) :-
=(X, Y, T).
Para garantizar que memberd_t/3
siempre se beneficie de la indexación del primer argumento, utilice la siguiente definición (gracias a @WillNess):
memberd_t(E, Xs, T) :-
i_memberd_t(Xs, E, T).
i_memberd_t([], _E, false).
i_memberd_t([X|Xs], E, T) :-
if_( X = E, T = true, i_memberd_t(Xs, E, T) ).
Puedes hacer la unión de las dos primeras listas y luego la unión entre ese resultado y la tercera:
union(L1, L2, L3, U):-union(L1, L2, U12), union(U12, L3, U).
Puedes mejorar union/3
con un operador de corte:
union([],M,M).
union([X|Y],L,S) :- element(X,L), !, union(Y,L,S).
union([X|Y],L,[X|S]) :- union(Y,L,S).
Usar solo predicados con un argumento adicional como memberd_t/3 conduce solo a una cosificación débil. Para una cosificación fuerte también necesitamos generar restricciones. Una fuerte cosificación es otro enfoque para eliminar el no determinismo.
Pero una cosificación fuerte es difícil; una forma posible de archivar esto es utilizar una CLP(*)
instancia que también haya cosificado operadores lógicos. Aquí hay un ejemplo si se usa CLP(FD)
para el problema de unión. Lamentablemente esto cubre sólo el dominio Z
:
Código de cosificación fuerte:
member(_, [], 0).
member(X, [Y|Z], B) :-
(X #= Y) #\/ C #<==> B,
member(X, Z, C).
union([], X, X).
union([X|Y], Z, T) :-
freeze(B, (B==1 -> T=R; T=[X|R])),
member(X, Z, B),
union(Y, Z, R).
Lo anterior no adolece de puntos de elección innecesarios. Aquí hay algunos ejemplos que muestran que esto ya no sucede:
Ejemplo de ejecución de un terreno:
?- union([1,2],[2,3],X).
X = [1, 2, 3].
Además, el ejemplo anterior ni siquiera crea puntos de elección, si usamos variables en alguna parte. Pero es posible que veamos muchas limitaciones:
Ejecución de un ejemplo no terrestre:
?- union([1,X],[X,3],Y).
X#=3#<==>_G316,
1#=X#<==>_G322,
_G316 in 0..1,
freeze(_G322, (_G322==1->Y=[X, 3];Y=[1, X, 3])),
_G322 in 0..1.
?- union([1,X],[X,3],Y), X=2.
X = 2,
Y = [1, 2, 3].
Como no formulamos algunas invariantes de entrada, el intérprete no puede ver que producir restricciones en el caso anterior no tiene ningún sentido. Podemos usar la all_different/1
restricción para ayudar un poco al intérprete:
Proporcionar invariantes:
?- all_different([1,X]), all_different([X,3]), union([1,X],[X,3],Y).
Y = [1, X, 3],
X in inf..0\/2\/4..sup,
all_different([X, 3]),
all_different([1, X]).
Pero no deberíamos esperar demasiado de este singular ejemplo. Dado que CLP(FD)
y the freeze/2
es sólo un procedimiento de decisión incompleto para proposiciones y ecuaciones Z, es posible que el enfoque no funcione tan fácilmente como aquí en todas las situaciones.
Adiós