Unión prólogo de la AUBUC

Resuelto 5oo asked hace 10 años • 0 respuestas

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 ?

5oo avatar Dec 08 '14 19:12 5oo
Aceptado
union(A, B, C, U) :-
   union(A, B, V),
   union(C, V, U).

Su definición de se union/3puede 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/3tal 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 algunos Xy Ys. Teniendo en cuenta eso, y dado que tenemos un ajuste Yque es diferente al X. Entonces


podemos concluir que eso también memberd(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/2y 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_1puede 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/3siempre 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) ).
false avatar Dec 08 '2014 13:12 false

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/3con 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).
Tudor Berariu avatar Dec 08 '2014 13:12 Tudor Berariu

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/1restricció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/2es 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

 avatar Apr 04 '2016 19:04