2. 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.
3. 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.
4. 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]
5. 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
6. 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).
7. 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).
8. 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).
9. 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).
10. 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).
11. 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).
12. 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.
13. 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.
14. 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']).
15. 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.
16. 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.
17. 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;
18. 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.
20. 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.
21. 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).
22. 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).
23. 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).
24. 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
25. 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.
26. 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
27. 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.
28. 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
29. 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
30. 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.
31. 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].
32. 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.
33. 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.
34. 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
35. 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.
36. 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
37. 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.
39. 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.