420 likes | 679 Views
PROLOG Programación Lógica (III). Mar Mejía Murillo. Listas. Usos Listas Composición de Programas Recursivos Iteración
E N D
PROLOG Programación Lógica (III) Mar Mejía Murillo
Listas • Usos • Listas • Composición de Programas Recursivos • Iteración • Las listas son la estructura básica usada en la programación lógica. Son una estructura de datos recursivas, de modo que la recursión ocurre de manera natural en la definición de operaciones de varias listas.
Listas Cuando definimos operaciones en estructuras de datos recursivas, la definición con mucha frecuencia sigue de manera natural la definición recursiva de la estructura de datos. En el caso de las listas, la lista vacía es el caso base. Así que las operaciones sobre listas deben considerar el caso de la lista vacía. Los otros casos implican una lista que se compone de un elemento y una lista.
Listas Aquí tenemos una definición recursiva de la estructura de datos 'Lista' como la encontramos en prolog: Lista --> [ ] Lista --> [Elemento|Lista] Aquí hay algunos ejemplos de representaciones de listas, el primero es la lista vacía: Pair Syntax Element Syntax [ ] [ ] [a|[ ]] [a] [a|b|[ ]] [a,b] [a|X] [a|X] [a|b|X] [a,b|X]
Listas Los predicados en la lista comúnmente se escriben usando múltiples reglas. Una regla para la lista vacía (caso base) y una segunda regla para listas no vacías. Por ejemplo, aquí está la definición del predicado para la longitud de una lista. % length(List,Number) <- Number is lenght of List length([],0). length([H|T],N) :- length(T,M), N is M+1
Listas elemento de una lista % member(Element,List) <- Element is an element of the list List member(X,[X|List). member(X,[Element|List]) :- member(X,List).
Listas Prefijo de una lista % prefix(Prefix,List) <- Prefix is a prefix of list List prefix([],List). prefix([X|Prefix],[X|List]) :- prefix(Prefix,List).
Listas Sufijo de una lista % suffix(Suffix,List) <- Suffix is a suffix of list List suffix(Suffix,Suffix). prefix(Suffix,[X|List]) :- suffix(Suffix,List).
Listas Append (concatenar) dos listas. % append(List1,List2,List1List2) <- % List1List2 is the result of concatenating List1 and List2. append([],List,List). append([Element|List1],List2,[Element|List1List2]) :- append(List1,List2,List1List2).
Listas Iteración en listas Versión Iterativa de my_length % my_length(List,Number) <- Number is lenght of List % Iterative version. my_length(List,LenghtofList) :- my_length(List,0,LengthofList). % my_length(SufixList,LengthofPrefix,LengthofList) <- % LengthofList is LengthofPrefix + length of SufixList my_length([],LenghtofPrefix,LengthofPrefix). my_length([Element|List],LengthofPrefix,LengthofList) :- PrefixPlus1 is LengthofPrefix + 1, my_length(List,PrefixPlus1,LengthofList).
Listas Versión iterativa de Reversa % reverse(List,ReversedList) <- ReversedList is List reversed. % Iterative version. reverse(List,RList) :- reverse(List,[],RList). % length(SufixList,LengthofPrefix,LengthofList) <- % LengthofList is LengthofPrefix + length of SufixList reverse([],RL,RL). reverse([Element|List],RevPrefix,RL) :- reverse(List,[Element|RevPrefix],RL).
Listas Aquí tenemos algunos ejemplos simples de operaciones comunes sobre listas definidas por comparación de patrones. La primera suma los elementos de una lista y la seguna forma el producto de los elementos de una lista. sum([ ],0). sum([X|L],Sum) :- sum(L,SL), Sum is X + SL. product([ ],1). product([X|L],Prod) :- product(L,PL), Prod is X * PL.
Listas La relación de append es muy flexible. Puede usarse para determinar si un objeto es un elemento de una lista, si una lista es prefijo de una lista y si una lista es sufijo de una lista. member(X,L) :- append(_,[X|_],L). prefix(Pre,L) :- append(Prefix,_,L). suffix(L,Suf) :- append(_,Suf,L). El subguión '_' en las definiciones denota una variable anónima (o 'no importa') cuyo valor es irrelevante para la definición.
Listas La relación member puede ser usado para derivar otras relaciones útiles. vowel(X) :- member(X,[a,e,i,o,u]). digit(D) :- member(D,['0','1','2','3','4','5','6','7','8','9']).
Iteración La recursión es el único método iterativo disponible en Prolog. Sin embargo, la recursión de cola puede ser con frecuencia implementada como iteración. La siguiente definición de la función factorial es una definición iteratica porque es "recursiva de cola". Corresponde a una implementación usando un ciclo-while en un lenguaje de programación imperativo.
Iteración fac(0,1). fac(N,F) :- N > 0, fac(N,1,F). fac(1,F,F). fac(N,PP,F) :- N > 1, NPp is N*PP, M is N-1, fac(M,NPp,F). Note que el segundo argumento funciona como acumulador. El acumulador se usa para almacenar productos parciales tal y como se haría en un lenguaje procedural.
Iteración Por ejemplo, en Pascal, una función factorial iterativa se podría escribir asi: function fac(N:integer) : integer; var i : integer; begin if N >= 0 then begin fac := 1 for I := 1 to N do fac := fac * I end end;
Iteración En la solución Pascal 'fac' actua como una acumulador para almacenar el producto parcial. La solución Prolog también ilustra el hecho de que Prolog permite diferentes relaciones definidas bajo el mismo nombre si el número de argumentos es diferente. En este ejemplo las relaciones son fac/2 y fac/3. Como ejemplo adicional del uso de acumuladores aquí tenemos la versión iterativa de la función Fibonacci.
Iteración ffib(0,1). fib(1,1). fib(N,F) :- N > 1, fib(N,1,1,F) fib(2,F1,F2,F) :- F is F1 + F2. fib(N,F1,F2,F) :- N > 2, N1 is N - 1, NF1 is F1 + F2, fib(N1,NF1,F1,F).
Iteradores, Generadores y Backtracking Los siguientes hecho y regla se pueden usar para generar números naturales nat(0). nat(N) :- nat(M), N is M + 1. La sucesión de números se genera por 'backtracking'. Por ejemplo, cuando la siguiente consulta se ejecuta, los números naturales sucesivos se imprimen. ?- nat(N), write(N), nl, fail.
Iteradores, Generadores y Backtracking El primer número natural se genera e imprime. Entonces, 'fail' forza a que suceda el rastreo de retorno y la segunda regla se usa para generar la sucesión de números naturales. El siguiente código genera prefijos sucesivos de una lista infinita empezando con N. natlist(N,[N]). natlist(N,[N|L]) :- N1 is N+1, natlist(N1,L).
Iteradores, Generadores y Backtracking Como ejemplo final, aquí tenemos un código para la generación de prefijos sucesivos de la lista de números primos. primes(PL) :- natlist(2,L2), sieve(L2,PL). sieve([ ],[ ]). sieve([P|L],[P|IDL]) :- sieveP(P,L,PL), sieve(PL,IDL). sieveP(P,[ ],[ ]). sieveP(P,[N|L],[N|IDL]) :- N mod P > 0, sieveP(P,L,IDL). sieveP(P,[N|L], IDL) :- N mod P =:= 0, sieveP(P,L,IDL).
Iteradores, Generadores y Backtracking Ocasionalmente el backtracking y las múltiples respuestas son incómodas. Prolog nos permite usar el símbolo de corte (!) para controlar el backtracking. El codigo siguiente define un predicado donde el tercer argumento es el máximo de los primeros dos. max(A,B,M) :- A < B, M = B. max(A,B,M) :- A >= B, M = A. El código se puede simplificar quitando las condiciones de la segunda condición. max(A,B,B) :- A < B. max(A,B,A).
Iteradores, Generadores y Backtracking Sin embargo, durante el backtracking, respuestas no correctas se pueden sucitar, como a continuación se muestra: ?- max(3,4,M). M = 4; M = 3
Iteradores, Generadores y Backtracking Para impedir el rastreo de retorno en la segunda regla, el símbolo de corte se pone en la primera regla. max(A,B,B) :- A < B.!. max(A,B,A). Ahora la respuesta incorrecta no se va a generar. Un consejo: Los cortes son similares a los 'goto's, en el hecho de que tienden a incrementar la complejidad del código en lugar de simplificarla. En general el uso de cortes debería ser evitado, y sería mejor replantear las condiciones para que no se generen resultados incorrectos.
Entrada y Salida En archivos: see(File) La entrada actual ahora es File. seeing(File) File se unifica con el nombre del archivo de entrada actual seen Cierra el archivo de entrada actual tell(File) El archivo de salida actual es File telling(File) File se unifica con el nombre del archivo de salida actual told Cierra el archivo de salida actual
Entrada y Salida Términos de entrada y salida read(Term) Lee el término hasta el delimitador punto del flujo de entrada actual, si encuentra eof regresa el átomo eof. write(Term) Escribe un término al fluje de salida actual. print(Term) Escribe un término al flujo de salida actual. Usa un predicado portray/1 definido por el usuario para la escritura, o de no existir, usa write. writeq(Term) Escribe un término al flujo de salida actual en una forma estándar de entrada para lectura.
Entrada y Salida Caracter de entrada y salida get(N) N es el código ASCII del siguiente caracter no nulo imprimible en el flujo de entrada actual. Si es fin de archivo, se retorna un -1 put(N) Pone el caracter correspondiente al código ASCII de N en el flujo de salida actual. nl Escribe una nueva línea tab(N) N espacios se ponen en el flujo de salida
Acceso y manipulación de programas y sistema clause(Head,Body) assert(Clause) agrega la cláusula al final de la base de datos asserta(Clause) retract(Clause_Head) consult(File_Name) system(Command) Ejecuta Command en el sistema operativo
Aplicaciones Gramáticas Independientes de Contexto y Gramáticas de Cláusulas Definidas Prolog se originó de intentos de usar la lógica para expresar reglas gramaticales y formalizar los procesos de parseo. Prolog tiene reglas de sintáxis especiales llamadas Gramática de Cláusulas Definidas, que son una generalización de las gramáticas libres de contexto.
Aplicaciones Los no terminales se expresan como átomos Prolog, los elementos en el cuerpo se separan con comas y secuencias de símbolos terminales se escriben como listas de átomos. Para cada no terminal, S, una gramática define un lenguaje que se obtiene por la aplicación no determinista de reglas gramaticales iniciando en S. s --> [a],[b]. s --> [a],s,[b].
Aplicaciones Una ilustración de como se usan estas DCGs, la cadena [a,a,b,b] se le proporciona a la gramática para su parsing. ?- s([a,a,b,b],[]). yes Vea los ejemplos anexos para la formación de oraciones, sin y con control de número.
Aplicaciones Estructuras de Datos incompletas. Una estructura de datos incompleta es una estructura de datos que contiene una variable. Tal estructura se dice estar 'parcialmente instanciada' o 'incompleta'. Aquí ilustramos la programación con estructuras de datos incompletas modificando el código para un árbol de búsqueda binaria. El código resultante permite la relación inserted_in_is para definir tanto la relación de inserción como la de membresía.
Aplicaciones Programación de Meta Nivel Los meta programas tratan otros programas como datos. Analizan, transforman y simulan otros programas. Las cláusulas Prolog pueden pasarse como argumentos, ser agregadas y eliminadas de la base de datos, y ser construidas y ejecutadas por un programa prolog. Las implementaciones pueden requerir que el functor y aridad de la cláusula esté previamente declarado como tipo dinámico
Aplicaciones Assert/Retract El siguiente ejemplo muestra como las cláusulas pueden ser agregadas y removidas de la base de datos Prolog. Muestra como simular una sentencia de asignación usando assert y retract para modificar la asociación entre una variable y un valor.
Aplicaciones :- dynamic x/1 .% this may be required in some Prologs x(0). % An initial value is required in this example assign(X,V) :- Old =..[X,_], retract(Old), New =..[X,V], assert(New). Aquí hay un ejemplo del uso del predicado assign ?- x(N). N = 0 yes ?- assign(x,5). yes ?- x(N). N = 5
Aplicaciones Sistemas expertos Los sistemas expertos se pueden programar en uno de dos modos en Prolog. Uno es construir una base de conocimiento usando hechos y reglas Prolog y usar la máquina de inferencia empotrada para responder consultas. La otra es construir una máquina de inferencia más poderosa en prolog y usarla para implementar un sistema experto.
Aplicaciones Comparación de patrones: Diferenciación simbólica d(X,X,1) :- !. d(C,X,0) :- atomic(C). d(-U,X,-A) :- d(U,X,A). d(U+V,X,A+B) :- d(U,X,A), d(V,X,B). d(U-V,X,A-B) :- d(U,X,A), d(V,X,B). d(C*U,X,C*A) :- atomic(C), CX, d(U,X,A),!. d(U*V,X,B*U+A*V) :- d(U,X,A), D(V,X,B). d(U/V,X,A) :- d(U*V^-1,X,A) d(U^C,X,C*U^(C-1)*W) :- atomic(C), CX, d(U,X,W). d(log(U),X,A*U^(-1)) :- d(U,X,A).
Aplicaciones Programación Orientada a Objetos. Otra de las aplicaciones de prolog es la evaluación de estructuras de datos de tipo objeto. Como sabemos un tipo objeto tiene inherentes métodos y datos que se instancían en el momento de su declaración. En el ejemplo de prolog podemos ver una propuesta de la definición de estas relaciones.