Estructuras incompletas. Listas diferencia. 1 / 13 Estructuras incompletas Proporcionan una técnica de implementación muy potente propia de Prolog: Incrementan radicalmente la eficiencia de muchos programas. Simplifican el diseño de dichos programas. Se apoyan en un uso muy hábil de las variables lógicas para representar agujeros o huecos en las estructuras de datos. Estos huecos representan una parte de la estructura que aun no ha sido computada y que se irá computanto incrementalmente a medida que avance la ejecución. Las estructuras incompletas pueden entenderse como una generalización de la idea de acumulador. La estrutura incompleta más utilizada son las listas diferencia 2 / 13 Listas revisitadas El predicado append(?Xs,?Ys,?Zs) para concatenar dos listas se definı́a como: append([],Ys,Ys). append([X|Xs],Ys,[X|Zs]):- append(Xs,Ys,Zs). Desarrollar el árbol de búsqueda para append([a,b,c],[d,e],Zs) ¿Qué complejidad computacional tiene esta operación? ¿Cuántas invocaciones recursivas hace una llamada a append para concatenar dos listas dadas? ¿Cómo podrı́a mejorarse esta complejidad? 3 / 13 Utilizando el append Problema: definir el predicado rotacionSimple(+Xs,-Ys)::= la lista Ys es el resultado de trasladar la cabeza de Xs al final de la misma. Por ejemplo, si Xs=[e1 ,e2 . . . ,en ] entonces será Ys=[e2 . . . ,en ,e1 ]. rotacionSimple([],[]). rotacionSimple([X|Xs],Ys):- append(Xs,[X],Ys). ¿Qué complejidad tiene esta operación? ¿Cómo podrı́a mejorarse? 4 / 13 Utilizando el append II Problema: definir el predicado aplana(+Xs,-Ys)::= dada una lista Xs con un anidamiento arbitrario de listas, Ys será la lista resultante de aplanarla (eliminar el anidamiento de listas y obtener una lista sin anidamiento). Por ejemplo, si Xs=[1,[2],[],[3,[4,[]],[[[5]]]]] será Ys=[1,2,3,4,5]. aplana([],[]). aplana([X|Xs],Ys):aplana(X,X1), !, aplana(Xs,Xs1), append(X1,Xs1,Ys). aplana([X|Xs],[X|Ys]):- aplana(Xs,Ys). ¿Puede mejorarse la eficiencia de esta operación? 5 / 13 Listas diferencia La ineficiencia de los predicados anteriores es debida en gran parte a la ineficiencia de append. Pensando en el modo de uso: append(+Xs,+Ys,-Zs), este predicado recorre la lista Xs completa, elemento a elemento. En programación imperativa utilizando listas enlazadas (mediente punteros al “siguiente”) la correspondiente operación append puede hacerse en tiempo constante: apuntando el último enlace de la primera lista al principio de la segunda lista. → a → b → c →|| → d → e →|| → a → b → c → d → e →|| Pero para ello se necesita guardar la referencia al último (o bien, puntero al último nodo, que a su vez nos da acceso al último puntero). 6 / 13 ¿Punteros en Prolog? En Prolog la situacion es similar: Para concatenar dos listas directamente (en tiempo constante) se necesita una: lista abierta, i.e., una lista con una referencia al final, que pueda apuntarse a otra lista para hacer la concatenación En Prolog no hay punteros... pero hay variables lógicas, que pueden actuar como referencias. Podemos representar la lista [a, b, c] como un par (lista abierta,referencia al resto de la lista): ([a, b, c|R], R) Las listas diferencia: Siempre llevarán una variable como resto de lista. Dicha variable debe guardarse como segundo elemento del par. Nota: habitualmente el par ([a, b, c|R], R) se representa como [a, b, c|R] − R o [a, b, c|R]\R (esto no es crı́tico, podemos elegir la que más nos guste). 7 / 13 Concatenación con listas diferencia La concatenacion de [a, b, c] y [d, e], con la nueva representación serı́a la concatención de [a, b, c|R1] −R1 con [d, e|R2] −R2. | {z } | {z } L1 L2 Se unifica R1 = L2 El nuevo resto será R2. La lista resultante es [a, b, c, d, e|R2] −R2. | {z } L1 append con listas diferencia: appendDif(L1-R1,L2-R2,L3-R3):- R1=L2, L3=L1, R3=R2. O más sintético: appendDif(A-B,B-C,A-C). 8 / 13 Manipulando listas diferencia Importante: cuando trabajamos con listas diferencia tenemos que manipular siempre la representación adecuada L-R en todas los predicados/objetivos implicados. El objetivo appendDif([1,2,3],[4,5],L) no tiene sentido porque appendDif espera listas diferencia (de la forma L-R como argumentos. ¿Cómo se representa la lista vacı́a como lista diferencia? L-L Una lista diferencia puede transformarse automáticamente en una lista estándard. Por ejemplo, dada la lista [1,2,3,4|R]-R, si la unificamos con L-[] (es decir, unificamos R=[]) obtendremos la lista estándard L=[1,2,3,4]. La operación inversa para convertir una lista estándard en una lista diferencia no es inmediata... ¿cómo se harı́a? 9 / 13 Rotación simple de una lista Tenı́amos el problema: definir el predicado rotacionSimple(+Xs,-Ys)::= la lista Ys es el resultado de trasladar la cabeza de Xs al final de la misma. Por ejemplo, si Xs=[e1 ,e2 ,. . . ,en ] entonces serı́a Ys=[e2 ,. . . ,en ,e1 ]. Utilizando listas diferencia (cambiando la representación de las listas), tenemos: rotacionSimple(L-L,L-L):- var(L), !. % lista vacia rotacionSimple([X|Xs]-R,Xs-R1):- R=[X|R1]. O más sintético (haciendo la unificación en cabeza): rotacionSimple(L-L,L-L):- var(L), !. % lista vacia rotacionSimple([X|Xs]-[X|R1],Xs-R1). 10 / 13 Aplana revisitado Tenı́amos: aplana([],[]). aplana([X|Xs],Ys):aplana(X,X1), !, aplana(Xs,Xs1), append(X1,Xs1,Ys). aplana([X|Xs],[X|Ys]):- aplana(Xs,Ys). Podrı́amos utilizar listas diferencia y el append correspondiente... pero de hecho no es necesario el append: aplana2(Xs,Ys):- aplanaAux(Xs,Ys-[]). aplanaAux([], L-L). aplanaAux([X|Xs],A-D):aplanaAux(X,A-B), !, aplanaAux(Xs,C-D), B=C. aplanaAux([X|Xs],[X|A]-B):- aplanaAux(Xs,A-B). 11 / 13 Aplana re-revisitado Una implementación aun mejor para aplana (sin listas diferencia): apl([],[]). apl([[]|Xs],Ys):- !, apl(Xs,Ys). apl([[X|Xs]|Ys],Zs):- !, apl([X,Xs|Ys],Zs). apl([X|Xs],[X|Ys]):- apl(Xs,Ys). 12 / 13 Dos ejercicios Implementar el predicado toDifList(Xs,Ys-Zs)::= dada la lista (plana) Xs obtiene la correspondiente lista diferencia Ys-Zs. Por ejemplo, toDifList([1,2,3],DL) obtendrı́a DL=[1,2,3|R]-R. Implementar el predicado toDifListGen(T,T1)::= dado el término T obtiene el término T1 resultante de transformar todas las listas que tenga T como subtérmino en listas diferencia. Por ejemplo, toDifList([1,[2,3],[4,[5]]],DL) obtendrı́a DL = [1, [2, 3|A]-A, [4, [5|B]-B|C]-C|D]-D 13 / 13