facultad de inform´atica - 4a el lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf ·...

50
Ingenier´ ıa de Requerimientos (IDR) (pr´ acticas) Facultad de Inform´ atica - 4A El lenguaje de programaci´ on Visual Prolog Parte II Javier Ib´ nez (Despacho D-109) Germ´ an Vidal (Despacho D-242) Edificio DSIC Curso 2005/2006

Upload: dodiep

Post on 20-Sep-2018

215 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

Ingenierıa de Requerimientos (IDR)

(practicas)

Facultad de Informatica - 4A

El lenguaje de programacion Visual Prolog

Parte II

Javier Ibanez (Despacho D-109)

German Vidal (Despacho D-242)

Edificio DSIC

Curso 2005/2006

Page 2: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

Parte V

Datos simples y compuestos

1

Page 3: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

1.- Tipos de datos simples

Un objeto simple es una variable o bien una constante, entendiendo por “constante”:un caracter (char), un numero (entero o real), o una tira de caracteres (symbol ostring).

1.1. Variables

Las variables deben comenzar por una letra mayuscula o por subrayado ( ). Unavariable anonima se representa con un sımbolo de subrayado y se interpreta como “noimporta su valor”. Las variables se pueden instanciar a cualquier argumento o datolegal. Las distintas ocurrencias de la variable anonima se pueden instanciar a datosdiferentes, incluso dentro de una misma clausula o atomo.

Las variables en Prolog son locales, es decir, si dos clausulas contienen una mismavariable X, estas variables se consideran distintas. Por supuesto, ambas pueden con-vertirse en la misma variable debido al mecanismo de unificacion, pero en general notienen porque tener ninguna relacion.

1.2. Constantes

Las constantes incluyen numeros, caracteres y tiras de caracteres. Atencion, no lasconfundais con las constantes simbolicas definidas en la seccion constants. Aquı, elvalor de una constante es su nombre. Es decir, el valor de la constante 2 es 2, y el valorde la constante pedro es pedro.

Caracteres

Los caracteres son de tipo char. Los caracteres se escriben entre comillas simples:

’a’ ’*’ ’{’ ’3’ ’A’

Si queremos usar el caracter \ o la comilla simple, lo escribiremos ası: ’\\’, ’\’’.Disponemos tambien de una serie de caracteres que realizan funciones especiales (cuan-do se preceden por el caracter de escape):

’\n’ Newline’\r’ Return’\t’ Tab (horizontal)

Los caracteres se pueden escribir tambien usando el caracter de escape, seguido delnumero ASCII del caracter (por ejemplo, ’\65’).

Numeros

Los numeros permitidos son enteros (ver la tabla de dominios enteros en la ParteIII) o reales (dominio real).

2

Page 4: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

Tiras de caracteres

Pueden ser de tipo symbol o string. La diferencia entre ambos es mas bien unacuestion de implementacion (tal como se comento en la Parte III).

Visual Prolog realiza una conversion automatica entre datos del dominio symboly datos del dominio string. En principio, la sintaxis para cada tipo es la siguiente:

symbol: nombres que comiencen por un caracter en minuscula y que contengansolo letras (minusculas o mayusculas) y dıgitos;

string: entre comillas dobles y pueden contener cualquier cosa.

Aquı podeis ver algunos ejemplos:

symbol stringjesseJames "Jesse James"a "a"pdc Prolog "Visual Prolog, by PDC"

Dado que ambos tipos son intercambiables, la distincion no resulta importante. Sinembargo, cosas tales como los nombres de predicado y los functores para los tipos dedatos compuestos, deben seguir obligatoriamente la sintaxis para symbol.

2.- Tipos de datos compuestos

Los datos compuestos nos permiten tratar varios “bloques” de informacion comoun objeto unico. Por ejemplo, la fecha “2 de Abril de 1988” consta de tres partes (dıa,mes y ano), pero a menudo resulta util tratarla como un objeto unico:

2 Abril 1988

���������

���

aaaaaaaa

fecha

Podemos hacer esto declarando un dominio:

domainsfecha = fecha(unsigned,string,unsigned)

y entonces escribir simplemente:

..., D = fecha(2, "Abril", 1988), ...

Fijaos en que tiene el mismo aspecto que un hecho Prolog, pero se trata en realidadde un objeto compuesto de datos, que podemos manejar como si fuera un entero o unacadena de caracteres. Los datos compuestos comienzan por un nombre, usualmentellamado functor, seguidos por sus argumentos entre parentesis.

Es importante destacar que, aunque un functor de Prolog es comparable a unafuncion en otros lenguajes, los functores no tienen asociada una interpretacion. Esdecir, dado un objetivo:

3

Page 5: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

write(suma(3,2))

aparecera por pantalla suma(3,2) y, en ningun caso, 5.Los argumentos de un dato compuesto pueden ser, a su vez, datos compuestos. Por

ejemplo, la informacion sobre el cumpleanos de una persona se puede representar conuna estructura como esta:

Pepe Perez

�����

ZZZZ

persona

14 Abril 1960

���������

���

aaaaaaaa

fecha nac

������������

PPPPPPPPP

cumplea~nos

En Prolog, escribimos esta estructura ası:

cumplea~nos(persona("Pepe", "Perez"), fecha(14, .Abril", 1960))

2.1. Unificacion de datos compuestos

Un dato compuesto puede unificar con una variable, o bien con otro dato compuesto(que contenga posiblemente variables como argumentos). Esto signifca que podemosusar datos compuestos para pasar varios datos a la vez como un dato unico, usando launificacion para descomponerlo. Por ejemplo,

fecha(2, "Abril", 1988)

unifica con una variable X, instanciando la variable X a fecha(2, .Abril", 1988). Porotro lado,

fecha(2, "Abril", 1988)

tambien unifica con fecha(D, M, A), instanciando D a 2, M a Abril y A a 1988.

Uso del sımbolo = para unificar datos compuestos

Visual Prolog realiza unificacion en dos situaciones: (1) cuando un subobjetivo seresuelve contra la cabeza de una clausula, y (2) cuando aparece el sımbolo ‘=’ en unsubobjetivo (‘=’ es un predicado predefinido en Prolog, con la particularidad de que seescribe en notacion infija).

Ante un objetivo de la forma izq = der , Prolog lo resuelve realizando la unifi-cacion entre izq y der. Podeis encontrar un ejemplo que usa el predicado ‘=’ pararealizar comparaciones entre dos datos compuestos en el programa ch05e01.pro.

4

Page 6: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

2.2. Agrupando datos simples para formar datos compuestos

Los datos compuestos se pueden considerar y tratar en Prolog como si fueran undato simple, lo que simplifica bastante la programacion. Por ejemplo, el hecho:

tiene(juan, libro("De aqui a la eternidad", "James Jones")).

establece que Juan tiene el libro “De aquı a la eternidad”, escrito por James Jones. Dela misma forma, podrıamos escribir el hecho:

tiene(juan, perro(toby)).

que se podrıa interpretar como “Juan tiene un perro llamado Toby”. En estos ejemploshemos usado datos compuestos, que son:

libro("De aqui a la eternidad", "James Jones")

y

perro(toby)

Sin embargo, si hubieramos escrito (usando datos simples):

tiene(juan, "De aqui a la eternidad").tiene(juan, toby).

no serıamos capaces de decidir si toby es el nombre de un libro o el nombre de un perro.Ası, podemos usar el functor de un dato compuesto para distinguir entre distintos tiposde datos (en este caso libro y perro).

En resumen, los datos compuestos tienen siempre la forma:

functor(dato1, dato2, ..., datoN)

donde dato1, . . . , datoN pueden ser datos simples o bien datos compuestos.

Un ejemplo del uso de datos compuestos

Como hemos comentado, una de las ventajas del uso de datos compuestos es quenos permite pasar un conjunto de datos simples como un solo argumento. Supongamosque queremos mantener un directorio de telefonos con las fechas de nacimiento de lagente (para recordar su fecha de cumpleanos). De entrada, podrıamos usar algo comoesto:

predicatesphone_list(symbol, symbol, symbol, symbol, integer, integer)

/* (First , Last , Phone , Month , Day , Year )

clausesphone_list(ed, willis, "422-0208", aug, 3, 1955).phone_list(chris, grahm, "433-9906", may, 12, 1962).

5

Page 7: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

Si examinamos los 6 argumentos de phone list, parece mas adecuado agrupar 5 dedichos argumentos ası:

First name Last name

������

HHHHHH

person

Month Day Year

!!!!!!!!

EEE

PPPPPPPPP

birthday

De esta forma, reescribimos el fragmento de programa anterior como sigue:

domainsname = person(symbol,symbol) /* (First, Last) */birthday = b_date(symbol,integer,integer) /* (Month, Day, Year) */ph_num = symbol /* Phone_number */

predicatesphone_list(name,symbol,birthday)

clausesphone_list(person(ed, willis), "422-0208", b_date(aug, 3, 1955)).phone_list(person(chris, grahm), "433-9906", b_date(may, 12, 1962)).

Ahora el predicado phone list solo tiene 3 argumentos, lo que hace el programamas legible y facil de usar.

Supongamos ahora que queremos generar una lista de personas cuyo cumpleanossea en el mes actual. El programa ch05e03.pro muestra como se puede hacer. Cargadel programa y ejecutarlo. Fijaos en el uso del predicado predefinido date, que nos de-vuelve el ano, mes y dıa del reloj del sistema. Podeis encontrar informacion sobre lospredicados predefinidos en la Parte IX.

Ejercicio. Modificad el programa para que tambien liste las fechas de nacimiento de lagente y sus numeros de telefono.

2.3. Declaracion de dominios para datos compuestos

Tal como vimos anteriormente, podemos definir en un programa las clausulas:

tiene(juan, libro("De aqui a la eternidad", "James Jones")).tiene(juan, perro(toby)).

y realizar consultas del tipo:

tiene(juan, X).

La variable X se podra instanciar a distintos tipos de datos: un libro, un perro, ocualquier otra cosa mas que definamos. Ası, una declaracion del tipo:

domainstiene(symbol, symbol)

6

Page 8: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

ya no es valida, puesto que el segundo argumento debe ser un dato compuesto. Ladeclaracion correcta serıa:

domainsarticulos = libro(titulo, autor); perro(nombre)titulo, autor, nombre = symbol

El punto y coma (‘;’) en la declaracion de articulos se lee “o”, es decir, los articulospueden ser libros o perros. En el programa ch05e04.pro podeis ver un ejemplo mascompleto del uso de datos compuestos.

Resumiendo, los datos compuestos se declaran ası:

dominio = alternativa1(D, D, ...);alternativa2(D, D, ...);...

donde alternativa1, alternativa2, etc, son functores arbitrarios (pero diferentes).La notacion (D, D, ...) representa una secuencia de nombres de dominios, que puedenser estandar o bien estar declarados en algun otro sitio. Cuando una de las alternati-vas sea un functor sin argumentos, podemos escribir tanto functor como functor(),ambas opciones son validas.

Es importante destacar que los functores deben seguir la sintaxis que hemos dadopara los datos simples de tipo symbol. Por ejemplo, una declaracion del tipo:

domainsnum_natural = 0 ; succ(num_natural)

para disponer de un dominio que represente los numeros naturales (usando la notaciondel sucesor), no es valida, ya que el numero 0 no es un functor valido. La formacorrecta de declararlo es:

domainsnum_natural = cero ; succ(num_natural)

Datos compuestos “multi-nivel”

Visual Prolog permite construir datos compuestos con varios niveles. Por ejemplo,en:

libro("El patito feo", "Andersen")

en lugar de usar el apellido del autor como segundo argumento, podemos construir unanueva estructura que describa al autor con mas detalle:

libro("El patito feo", autor("Hans Christian", "Andersen"))

De esta forma, la declaracion que tenıamos antes:

7

Page 9: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

domainsarticulos = libro(titulo, autor); perro(nombre)titulo, autor, nombre = symbol

se convierte ahora en:

domainsarticulos = libro(titulo, autor); perro(nombre)autor = autor(nombre_autor, apellido_autor)titulo, nombre_autor, apellido_autor, nombre = symbol

A menudo resulta mas claro representar los distintos niveles de un objeto compuestocon un arbol:

titulo

nombre autor apellido autor

!!!!!!!!

HHHHHHH

autor

bbbbb

libro

Sin embargo, hay que tener en cuenta que en una declaracion de dominios solo se puededescribir un nivel cada vez. Es decir, una declaracion como esta:

articulo = libro(titulo, autor(nombre_autor, apellido_autor))

en la que hay functores anidados, es incorrecta.

2.4. Declaracion de dominios mixtos

En este ultimo punto, vamos a ver como declarar dominios de forma que podamosusar predicados:

1. con un argumento de varios tipos posibles,

2. con un numero indeterminado de argumentos, cada uno de un tipo especıfico, y

3. con un numero indeterminado de argumentos, alguno de los cuales puede tenervarios tipos posibles.

Argumentos de tipos multiples

Para permitir que un predicado acepte argumentos de varios tipos distintos, debe-mos anadir un functor a cada posibilidad. Por ejemplo, dado el programa:

8

Page 10: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

domainsedad = i(integer); r(real); s(string)

predicatestu_edad(edad)

clausestu_edad(i(Edad)) :- write(Edad).tu_edad(r(Edad)) :- write(Edad).tu_edad(s(Edad)) :- write(Edad).

tenemos un procedimiento tu edad que acepta como argumento un valor entero, realo un string. Fijaos en que la presencia del functor para las distintas alternativas esnecesaria. Es decir, una declaracion como esta:

domainsedad = integer; real; string

no es valida en Visual Prolog.

Listas

Supongamos que queremos almacenar las asignaturas que debe impartir cada pro-fesor. En principio, podrıamos generar el siguiente codigo:

predicatesprofesor(symbol, symbol, symbol)/* (nombre, apellido, asig) */

clausesprofesor(juan, perez, matematicas)profesor(juan, perez, fisica)profesor(juan, perez, algebra)profesor(ana, alonso, historia)profesor(ana, alonso, quimica)

En este ejemplo, tenemos que repetir el nombre del profesor por cada asignatura queimparte. Si tuvieramos un volumen mas grande de asignaturas, la tarea serıa realmentecostosa. En esta situacion, resulta util disponer de una declaracion que nos permitaasignar un numero indeterminado de argumentos. Esto se puede conseguir con el usode listas. En la siguiente version del codigo anterior, introducimos un nuevo argumentoasignatura que es del tipo lista:

domainsasignatura = symbol*

predicatesprofesor(symbol, symbol, asignatura)

9

Page 11: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

clausesprofesor(juan, perez, [matematicas, fisica, algebra])profesor(ana, alonso, [historia, quimica])

En esta version el codigo resulta mas compacto y legible. En la declaracion:

asignatura = symbol*

le estamos diciendo a Prolog que el tipo asignatura estara compuesto por una lista deelementos del tipo symbol. Por ejemplo, si queremos declarar un dominio que consistaen una lista de numeros enteros, lo harıamos ası:

lista_enteros = integer*

Podeis ver el uso de listas con mas detalle en la Parte VII.

10

Page 12: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

Parte VI

Repeticion y recursion

11

Page 13: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

Una buena parte de la utilidad de los ordenadores consiste en que son capaces de re-alizar un mismo proceso una y otra vez. Prolog puede expresar repeticion tanto a nivelde procedimientos como de estructuras de datos. La idea de una estructura de datosrecursiva puede parecer extrana, pero en Prolog se usa de forma generalizada cuandoel tamano definitivo de la estructura no es conocido en el momento de su definicion(estructuras dinamicas). En esta parte, presentamos primero los procedimientos repet-itivos (iteracion y recursion), y despues abordamos las estructuras de datos recursivas.

1.- Procesos repetitivos

A primera vista puede parecer extrano que Prolog no disponga de intrucciones delestilo de FOR, WHILE o REPEAT, lo que significa que no hay una forma directa de ex-presar la iteracion. Sin embargo, es perfectamente posible implementar procedimientosrepetitivos usando backtracking y recursion.

Es importante destacar que esta ausencia no disminuye en absoluto la potenciaexpresiva del lenguaje. De hecho, Prolog reconoce un tipo especial de recursion, llamadotail-recursion, que se compila a codigo maquina exactamente igual que un bucle iterativo(consiguiendo ası la misma eficiencia que un bucle de Pascal o C). Por otro lado,la recursion es, en muchos casos, la forma mas clara y natural de expresar procesosrepetitivos.

1.1. Backtracking

El mecanismo de backtracking nos permite buscar soluciones alternativas para unsubobjetivo. Concretamente, un paso de backtracking consiste en reconsiderar el ultimopunto de la computacion en el que disponıamos de mas de una alternativa para resolverun subobjetivo (es decir, este unificaba con mas de una clausula del programa), eligien-do a continuacion la primera de las alternativas aun no consideradas y continuandola computacion de la forma usual. El siguiente ejemplo nos muestra como explotar elbacktracking para realizar procesos repetitivos.

Cargad el programa ch06e01.pro:

PREDICATESnondeterm country(symbol)print_countries

CLAUSEScountry("England").country("France").country("Germany").country("Denmark").

print_countries :-

12

Page 14: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

country(X),write(X), /* write the value of X */nl, /* start a new line */fail.

print_countries.

GOALprint_countries.

El predicado country simplemente lista los nombres de varios paıses, de forma que unobjetivo como:

country(X).

tiene multiples soluciones. El predicado print countries se encarga de recoger todaslas soluciones e imprimirlas por pantalla. Su definicion es como sigue:

print_countries :-country(X), write(X), nl, fail.

print_countries.

La primera clausula de print countries dice:

“Para imprimir los paıses, debemos encontrar una solucion a country(X),imprimir X, hacer un salto de lınea y provocar un fallo.”

En este caso, “provocar un fallo” significa:

“realizar un paso de backtracking, para buscar una alternativa a country(X).”

El predicado predefinido fail siempre falla (y provoca el backtracking), pero podrıamosconseguir el mismo efecto escribiendo un subobjetivo como 2 = 3.

El efecto de la definicion de print countries consiste, por tanto, en:

1. obtener la primera solucion para country(X) (instanciando X a "England"),

2. escribir England por pantalla,

3. hacer un salto de lınea,

4. desinstanciar la variable X (debido al backtracking provocado por fail), y

5. repetir el proceso anterior mientras existan mas alternativas para country(X).

De esta forma, al ejecutar el objetivo, aparece por pantalla:

EnglandFranceGermanyDenmarkyes

13

Page 15: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

Si no incluyesemos la segunda clausula para print countries, la unica diferencia serıaque el objetivo terminarıa con fallo (despues de encontrar e imprimir todos los paıses),mostrando por pantalla:

EnglandFranceGermanyDenmarkno

Ejercicio. Modifica el programa ch06e01.pro de manera que el predicado country tengados argumentos: nombre y poblacion. Modifica despues los hechos para que se ajustenal nuevo formato, asignando poblaciones entre 5 y 15 millones a cada paıs. Finalmente,modifica print countries para que solo imprima los paıses con mas de 10 millones dehabitantes.

Pre- y post-procesos

En general, un programa que compute toda las soluciones a un objetivo, requerira re-alizar alguna accion extra antes y despues. Por ejemplo, nuestro programa podrıa:

1. imprimir primero “Algunos paises del mundo son:”,

2. imprimir despues todas las soluciones a country(X), e

3. imprimir finalmente “y pueden haber mas”.

En este momento, la primera clausula de print countries realiza el paso (2) y, ademas,podemos modificar facilmente la segunda clausula para que realice el paso (3). Conc-retamente, podemos redefinirla ası:

print_countries :- write("y pueden haber mas"), nl.

¿Y respecto al paso (1)? Bastarıa con anadir una clausula mas al principio, quedandola definicion completa como sigue:

print_countries :-write("Algunos paises del mundo son:"),nl, fail.

print_countries :-country(X),write(X),nl, fail.

print_countries :-write("y pueden haber mas"), nl.

14

Page 16: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

Fijaos en que el fail en la primera clausula es importante ya que nos asegura que,tras ejecutar dicha clausula, el backtracking nos llevara a ejecutar la segunda clausula.

Sin embargo, algun programador avispado podrıa pensar que no son necesarias lastres clausulas, y escribir el programa anterior de esta forma:

new_print_countries :-write("Algunos paises del mundo son:"),nl,print_countries,write("y pueden haber mas"),nl.

print_countries :-country(X),write(X),nl, fail.

En principio, puede parecer que no hay ningun error, y que ante un objetivo de laforma:

new_print_countries.

el programa funcionara correctamente. Sin embargo, no es ası!

Ejercicio. Averiguar por que funciona mal el programa anterior y resolverlo.

1.2. Implementando el backtracking con bucles

El backtracking es una buena forma de conseguir todas las soluciones alternativaspara un objetivo. Ademas, incluso aunque un objetivo no tenga soluciones alternativas,aun es posible usar backtracking para introducir repeticion. Para ello, basta con definirel siguiente predicado:

repeat.repeat :- repeat.

Este procedimiento sirve para “enganar” al control de Prolog, haciendole pensar queexiste un numero infinito de soluciones. Es decir, nos permite introducir un numero in-finito de pasos de backtracking. Podemos ver un ejemplo en el programa ch06e02.pro:

PREDICATESnondeterm repeatnondeterm typewriter

CLAUSESrepeat.repeat :- repeat.

15

Page 17: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

typewriter :-repeat,readchar(C), /* Leer un caracter y asignarlo a C */write(C),C = ’\r’. /* Es un CR? Si no, fallo */

GOALtypewriter,nl.

El procedimiento typewriter funciona de la siguiente forma:

1. Ejecuta repeat (que no hace nada).

2. Lee un caracter y se lo asigna a la variable C.

3. Escribe el valor de C.

4. Comprueba si el valor de C es un retorno de carro.

5. Si lo es, el programa termina. Si no, hace backtracking en busca de alternativas.Dado que ni write, ni readchar generan soluciones alternativas, llegamos hastael repeat (desinstanciando la C), quien siempre tiene soluciones alternativas (versu definicion).

6. Ahora el proceso vuelve a comenzar, leyendo otro caracter, imprimiendolo y ver-ificando si es un retorno de carro.

Notad que la desinstanciacion de la variable C al realizar el backtracking resulta fun-damental para el buen funcionamiento del ejemplo anterior. En general:

Todas las variables pierden sus valores cuando la ejecucion realiza back-tracking mas alla del paso en el que dichas variables tomaron sus valores.

Desgraciadamente, esto significa que no es posible “recordar” nada de una iteracion ala siguiente, lo que impide hacer uso de un contador o cualquier otro registro sobre elprogreso de las iteraciones. En la siguiente seccion veremos como resolver este problema.

1.3. Procedimientos recursivos

En Visual Prolog la recursion resulta el mecanismo mas natural de expresar proce-sos repetitivos y, ademas, sı permite el uso de contadores (o cualquier otro resultadointermedio). Por ejemplo, el procedimiento para calcular el factorial de un numero sepuede expresar ası:

Para encontrar el factorial de un numero N:- Si N es 1, el factorial es 1.- Si no, encontrar el factorial de N-1 y multiplicarlo por N.

16

Page 18: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

En Prolog, podemos expresarlo mediante dos clausulas:

factorial(1,1) :- !.factorial(N, FactN) :-

M = N-1,factorial(M, FactM),FactN = N*FactM.

Como funciona la recursion internamente

Como en cualquier otro lenguaje, las sucesivas llamadas recursivas a un mismoprocedimiento se tratan como si fueran llamadas a procedimientos distintos. Es decir,los argumentos y las variables internas del procedimiento son locales a cada llamada.

Esta informacion se almacena en un “entorno” de activacion de procedimiento en elarea de memoria llamada STACK. Con cada nueva llamada recursiva, se crea un nuevoentorno que contiene las variables locales del procedimiento invocado. Cada vez que laejecucion de una regla termina, el entorno es eliminado del STACK (excepto que existansoluciones alternativas pendientes).

Fijaos en que esto significa que un procedimiento recursivo puede tener, en principio,un coste espacial muy superior al de un procedimiento iterativo equivalente! Pese atodo, en el siguiente punto veremos que existe un tipo especial de recursion, cuyo costeasociado es equivalente al de una iteracion.

Ademas, no debemos olvidar que los procedimientos recursivos nos permiten expre-sar facilmente algoritmos cuya version iterativa puede dar lugar a algoritmos muchomas complejos (por ejemplo, el caso de las torres de Hanoi). En general, la recursiones la forma mas apropiada de describir un problema que contiene otro problema delmismo tipo en su definicion.

1.4. Optimizacion de ultima llamada (LCO)

Como ya hemos comentado, la recursion plantea un problema: consume muchamemoria. Cada vez que un procedimiento llama a otro (sea o no el mismo), debe sal-varse el estado del procedimiento actual en un entorno del STACK, de forma que suejecucion pueda continuar al terminar la llamada. Esto significa que, si un proced-imiento recursivo se llama a si mismo 100 veces, debemos almacenar en el STACK 100entornos de activacion de procedimiento. . .

¿Como se puede evitar este uso excesivo de memoria? La solucion pasa por consider-ar lo siguiente: si el procedimiento que realiza la llamada no debe realizar ninguna otraaccion despues de dicha llamada, no es necesario almacenar su estado en el STACK!

Por ejemplo, supongamos que tenemos un procedimiento procA que llame a unprocedimiento procB, de forma que, a su vez, procB llame a procC en el ultimo paso.Es decir, tenemos una situacion como esta:

procC :- ...

17

Page 19: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

procB :- ..., procC.

procA :- ..., procB, ...

Cuando comienza la ejecucion de procA y llegamos a la ejecucion de la llamada aprocB, se almacena el estado del procedimiento procA en el STACK y se pasa a ejecutarprocB. Ahora, procB comienza su ejecucion y se encuentra una llamada a procC, conlo que debe salvar su estado en el STACK y pasar a ejecutar procC. La optimizacion queplanteamos consiste en lo siguiente: no hace falta almacenar el estado del pro-cedimiento procB ya que, al terminar la ejecucion de procC, debe seguir ejecutandoprocA (y no procB), puesto que procB ya habıa terminado.

Consideremos una situacion ligeramente distinta: tenemos ahora un procedimientorecursivo que, como ultima accion, realiza una llamada a sı mismo:

proc :- ..., proc.

En este caso, como hemos visto arriba, no es necesario almacenar el estado de procen el STACK, ya que la llamada aparece en ultimo lugar. Es decir, una llamada a procprocede a ejecutar el cuerpo del procedimiento y luego realiza la llamada recursiva,la cual simplemente repite el mismo proceso, sin almacenarse en ningun momento elestado de proc en el STACK. ¿Que hemos conseguido? Un procedimiento recursivo quese comporta como un procedimiento iterativo!

Este tipo de recursion se conoce como recursion de cola (“tail-recursion”) y laoptimizacion que hemos descrito (es decir, no almacenar en el STACK el entorno delprocedimiento con este tipo de recursion) se conoce como “optimizacion de ultimallamada” (last call optimization, LCO).

Uso de la recursion de cola

Hemos dicho que para aplicar la optimizacion LCO, el procedimiento debe realizar lallamada recursiva como ultima accion de su definicion. ¿Que significa esto exactamenteen Prolog? Significa que debe cumplirse que:

1. la llamada aparece como ultimo subobjetivo en el cuerpo de la clausula, y que

2. no hay puntos de backtracking previos pendientes.

Aquı podemos ver un ejemplo que cumple las dos condiciones:

contar(N) :-write(N), nl,NewN = N+1,contar(NewN).

Este procedimiento puede ejecutarse con un objetivo de la forma:

contar(0).

18

Page 20: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

lo que producira que se escriba una secuencia infinita de numeros naturales por pantalla,sin agotar nunca la memoria (este ejemplo se encuentra en el programa ch06e04.pro).

Ejercicio. Prueba a modificar el ejemplo anterior, de forma que la ejecucion se abortedebido a que se agota la memoria del STACK. (Nota: solo funcionara en plataformas de16 bits.)

Uso erroneo de la recursion de cola

Cargad el programa ch06e05.pro. En el se muestran distintas formas de imple-mentar de forma erronea la recursion de cola.

1. Cuando la llamada no es realmente la ultima accion del procedimiento:

badcount1(X) :-write(’\r’,X),NewX = X+1,badcount1(NewX),nl.

Aquı, con cada llamada recursiva sı se debe salvar el estado del procedimiento,ya que al terminar la llamada aun quedara pendiente de ejecutar nl.

2. Otra forma de perder la recursion de cola consiste en que existan alternativaspendientes en el momento de la llamada. Por ejemplo,

badcount2(X) :-write(’\r’,X),NewX = X+1,badcount2(NewX).

badcount2(X) :-X < 0,write("X is negative.").

Aquı, la llamada recursiva se encuentra al final del cuerpo de la primera clausula.Sin embargo, en el momento de la llamada aun esta pendiente de ejecutar lasegunda clausula, lo que obliga a salvar el estado del procedimiento.

3. De forma similar al caso anterior, puede ocurrir que hayan soluciones alternativasincluso aunque no sean para el propio procedimiento recursivo. Por ejemplo,

badcount3(X) :-write(’\r’,X),NewX = X+1,

19

Page 21: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

check(NewX),badcount3(NewX).

check(Z) :- Z >= 0.check(Z) :- Z < 0.

Supongamos que X es positivo. Entonces, en el momento de la llamada recursiva abadcount3(NewX), la primera clausula de check se ha ejecutado con exito, pero lasegunda alternativa esta aun pendiente. Al igual que antes, no tenemos recursionde cola y es necesario almacenar el estado del procedimiento.

Recuperando la recursion de cola mediante cortes

En este momento, puede parecer que es realmente difıcil garantizar que un proced-imiento cumpla las condiciones de la recursion de cola. Por un lado, resulta sencilloescribir la llamada recursiva al final de la ultima clausula del procedimiento. Pero,¿como podemos asegurarnos de que no hayan quedado alternativas pendientes?

La forma mas sencilla consiste en emplear el corte. Por ejemplo, el procedimientobadcount3 que veıamos, se podrıa escribir ası:

cutcount3(X) :-write(’\r’,X),NewX = X+1,check(NewX),!,cutcount3(NewX).

(dejando check como estaba). Con esto conseguimos que, justo antes de ejecutar lallamada recursiva cutcount3(NewX), todas las alternativas pendientes para cutcount3(si las hay) sean eliminadas. Esto es justamente lo que necesitabamos. Ahora el pro-cedimiento cumple las condiciones de la recursion de cola, con lo que no sera necesarioalmacenar el estado del procedimiento y la recursion se comportara como una iteracion,sin consumir espacio del STACK.

De forma similar, podemos usar el corte para evitar el problema de badcount2.Ahora, tendrıamos esto:

cutcount2(X) :-X >= 0, !,write(’\r’,X),NewX = X+1,cutcount2(NewX).

cutcount2(X) :-write("X is negative.").

20

Page 22: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

Hemos movido el test X <0 de la segunda clausula a la primera. Ahora, si se cumpleX >= 0, el corte elimina la posibilidad de hacer backtracking a la segunda clausula,con lo que la llamada recursiva a cutcount2(NewX) es realmente la ultima llamada delprocedimiento. Si el test no se cumple, se ejecuta la segunda clausula (no hace faltacomprobar que X <0, porque la unica forma de llegar a la segunda clausula es que eltest X >= 0 falle) y el procedimiento termina.

Fijaos en que, en ocasiones, no resulta tan simple conseguir un procedimiento conrecursion de cola mediante el uso de cortes. Por ejemplo, el procedimiento badcount1no puede arreglarse introduciendo un corte. La unica posibilidad serıa modificar lacomputacion para que la llamada recursiva estuviera al final.

1.5. Bucles y contadores

Vamos a ver ahora como implementar un programa recursivo que se comporte comoun bucle con contadores. Para ello, veremos de forma intuitiva como se traduce unprograma Pascal a Prolog. Partimos del siguiente programa en estilo Pascal:

P := 1;FOR I := 1 TO N DO P := P*I;FactN := P;

Este fragmento de codigo calcula (en FactN) el valor del factorial de N. En primer lugar,debemos escribirlo con un bucle WHILE, de forma que quede patente que operacionesestamos haciendo sobre las variables:

P := 1;I := 1;WHILE I <= N DO

beginP := P*I;I := I+1

end;FactN := P;

A partir de este bucle, tenemos que obtener su version recursiva (recordad que enProlog es la forma natural de expresar procedimientos repetitivos):

factorial(N, FactN);begin

factorial_aux(N, FactN, 1, 1)end;

factorial_aux(N, FactN, I, P);begin

IF I <= N THENbegin

21

Page 23: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

P := P*I;I := I+1;factorial_aux(N, FactN, I, P)

end;ELSE

FactN := Pend;

Fijaos en la necesidad de introducir una funcion auxiliar con dos argumentos extra parapoder pasarle los valores iniciales de las variables P e I. Ahora, el programa equivalenteen Prolog resulta muy sencillo de generar:

factorial(N, FactN) :-factorial_aux(N, FactN, 1, 1).

factorial_aux(N, FactN, I, P) :-I <= N, !,NewP = P*I,NewI = I+1,factorial_aux(N, FactN, NewI, NewP).

factorial_aux(N, FactN, I, P) :-FactN = P.

Observad que en Prolog no es posible escribir algo de la forma:

I = I+1

ya que, desde el punto de vista logico, un numero I nunca puede ser igual a I+1. Porello, usamos una variable auxiliar NewI, y escribimos NewI = I+1.

Podeis encontrar una version algo mas compacta del ejemplo anterior en el progra-ma ch06e08.pro.

Ejercicio. Escribir un programa con recursion de cola que imprima una tabla con las 10primeras potencias de 2:

22

Page 24: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

N 2^N--- -----1 22 43 8... ...10 1024

2.- Estructuras de datos recursivas

A diferencia de los lenguajes imperativos, Prolog permite la definicion de estructurasde datos recursivas. Por estructura de datos recursiva entendemos una estructura dedatos que contiene como argumentos estructuras de su mismo tipo.

La estructura de datos recursiva mas habitual en Prolog es la lista. Dada la im-portancia que las listas tienen en Prolog, estas se ven con detalle en la Parte VII.Ahora vamos a definir una estructura recursiva que represente un arbol. La recursivi-dad aparece porque un arbol (binario) se puede ver como formado por un elementoraız, un subarbol izquierdo y un subarbol derecho, de manera que cada subarbol es asu vez una estructura de tipo arbol.

2.1. El tipo de datos arbol

Pese a que la idea de los tipos de datos recursivos la introdujo Niklaus Wirth en el li-bro “Algoritmos + Estructuras de datos = Programas”, estos no fueron implementadosen Pascal. De haber existido, podrıamos definir un arbol de esta forma:

arbol = record /* Pascal incorrecto! */raiz: string[80];izq, der: arbol

end;

Sin embargo, la unica aproximacion para una estructura de este tipo en Pascal consisteen utilizar punteros:

arbolptr = ^arbol;

arbol = recordraiz: string[80];izq, der: arbolptr

end;

Notad que existe una diferencia sutil: este codigo maneja la representacion en memoriade un arbol, y no la propia estructura arbol. Es decir, vemos un arbol como formadopor celdas de memoria, cada una conteniendo un dato y un puntero a otras celdas.

En Visual Prolog, por el contrario, sı que es posible definir autenticos tipos de datosrecursivos. Por ejemplo, podemos definir la estructura arbol simplemente como sigue:

23

Page 25: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

domainstipoarbol = arbol(string, tipoarbol, tipoarbol)

Con esta declaracion establecemos que el tipo arbol esta compuesto por un functorarbol, cuyos argumentos son: un string (la raız) y dos estructuras del mismo tipoarbol.

Sin embargo, esto no es aun correcto. Tal como lo hemos definido, un arbol serıasiempre una estructura infinita. Para que puedan existir arboles finitos, es necesariopermitir que en algun momento los argumentos no sean a su vez arboles. Para ello,introducimos el functor vacio para denotar un arbol vacıo, y modificamos la declaracioncomo sigue:

domainstipoarbol = arbol(string, tipoarbol, tipoarbol) ; vacio

Por ejemplo, el siguiente arbol:

Carlos Mabel

�����

bbbbb

Miguel

Juan Elena

�����

ZZZZ

Carmen

���������

PPPPPPPPP

Ana

se representa mediante la estructura Prolog:

arbol("Ana",arbol("Miguel",

arbol("Carlos", vacio, vacio),arbol("Mabel", vacio, vacio)),

arbol("Carmen",arbol("Juan", vacio, vacio),arbol("Elena", vacio, vacio)))

Tened en cuenta dos cosas: (1) el indentado no es necesario, lo escribimos ası porlegibilidad, y (2) la estructura no es una clausula de Prolog, es solo un dato complejo(que se podra usar como argumento de un predicado).

Recorrido de un arbol

Antes de abordar el tema de como crear arboles, vamos a ver primero que se puedehacer con un arbol ya creado. Una de las operaciones mas habituales consiste en recorrertodos los nodos del arbol, realizando algun proceso con cada nodo. El algoritmo basicopara realizar este recorrido es:

1. Si el arbol esta vacio, no hacer nada.

24

Page 26: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

2. En otro caso, procesar el nodo actual, recorrer el subarbol izquierdo y recorrer elsubarbol derecho.

Al igual que la estructura arbol, el algoritmo es recursivo. En Prolog se puede imple-mentar ası:

recorrer(vacio).

recorrer(arbol(Raiz, Izq, Der)) :-procesar(Raiz),recorrer(Izq),recorrer(Der).

anadiendo la definicion adecuada para procesar. Concretamente, el algoritmo realizaun recorrido en profundidad del arbol. La siguiente numeracion nos indica el orden delrecorrido para el arbol anterior:

Carlos (3) Mabel (4)

������

HHHHHH

Miguel (2)

Juan (6) Elena (7)

������

bbbbbb

Carmen (5)

������������

XXXXXXXXXXXX

Ana (1)

Notad que la forma en que se recorre el arbol es la misma en que Prolog recorre un arbolde busqueda mediante backtracking. En el programa ch06e09.pro podeis encontrar unejemplo que imprime por pantalla el valor de todos los nodos del arbol, usando elalgoritmo que hemos visto. Por supuesto, podeis modificar facilmente el programa paraque realice algun proceso mas complejo que la simple impresion de los nodos.

Creacion de un arbol

En primer lugar, la forma mas directa de crear un arbol consiste en escribir laestructura donde sea necesaria, tal y como hemos hecho en el punto anterior. Sinembargo, a menudo resulta util poder construir la estructura en tiempo de ejecucion.Para ello, el metodo consiste en crear inicialmente un primer arbol conteniendo un solonodo, y cuyos subarboles izquierdo y derecho esten vacıos. Este simple hecho:

crear_arbol(N, arbol(N, vacio, vacio)).

nos permite crear un nuevo arbol con un solo nodo N en la raız. Por ejemplo, la ejecuciondel subobjetivo:

crear_arbol("Ana", Arbol).

25

Page 27: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

usando el hecho anterior, tiene el efecto de instanciar la variable Arbol a la estructuraarbol(.Ana", vacio, vacio).

De forma similar, podemos definir procedimientos que se encarguen de insertar unnuevo subarbol izquierdo, o bien un nuevo subarbol derecho:

insertar_izq(Izq, arbol(Raiz,_,Der), arbol(Raiz,Izq,Der)).insertar_der(Der, arbol(Raiz,Izq,_), arbol(Raiz,Izq,Der)).

Haciendo uso de las tres clausulas crear arbol, insertar izq e insertar der resultasencillo construir una estructura de arbol paso a paso en tiempo de ejecucion. En elprograma ch06e10.pro podeis ver un ejemplo concreto.

2.2. Arboles de busqueda binarios

Una de las principales aplicaciones de los arboles consiste en almacenar datos enellos, de forma que luego la busqueda de un determinado elemento resulte muy eficiente.En general, para buscar un elemento en un arbol con N nodos, debemos recorrerlostodos (en el peor de los casos), con lo que tendrıamos un algoritmo de busqueda cuyocoste es de orden N.

Cuando se usan arboles binarios (ordenados), la busqueda se puede realizar de formamucho mas eficiente, ya que cada vez que se alcanza un determinado nodo, solamentees necesario inspeccionar uno de los subarboles. Por ejemplo, consideremos el siguientearbol binario (ordenado alfabeticamente):

Antonio Cesar

�����

bbbbb

Beatriz

Laura

Ramon

Olga

�����

QQQQQ

Mauricio Tadeo

�����

PPPPPPPPP

Sara

PPPPPPPPPP

Gracia

Pese a que el arbol tiene 10 nodos, nunca es necesario inspeccionar mas de 5 nodos paraencontrar cualquier elemento del arbol (concretamente, en el peor de los casos hay queinspeccionar tantos nodos como niveles de profundidad tenga el arbol). En general, siun arbol binario con N nodos esta equilibrado, el coste de buscar un elemento es delorden de log2N .

Por supuesto, ya que ahora necesitamos que los elementos del arbol esten ordenados,los procedimientos del punto anterior para la creacion de un arbol no son adecuados.

26

Page 28: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

El procedimiento para la insercion de un nuevo elemento en un arbol binario ordenadoes:

insert(NewItem, vacio, arbol(NewItem, vacio, vacio)) :-!.

insert(NewItem, arbol(Raiz,Izq,Der), arbol(Raiz,NewIzq,Der)) :-NewItem < Raiz,!,insert(NewItem, Izq, NewIzq).

insert(NewItem, arbol(Raiz,Izq,Der), arbol(Raiz,Izq,NewDer)) :-insert(NewItem, Der, NewDer).

Ordenacion de arboles

Una vez que hemos construido un arbol binario ordenado, podemos recorrer suselementos en orden mediante este sencillo programa:

recorrer_todo(vacio).

recorrer_todo(arbol(Raiz, Izq, Der)) :-recorrer_todo(Izq),procesar(Raiz),recorer_todo(Der).

De esta forma, dada una secuencia de N elementos, podemos ordenarla a base deinsertar todos los elementos en un arbol binario (ordenado), y despues recorrer todo elarbol con el procedimiento anterior. De esta forma, tenemos un algoritmo de ordenacioncuyo coste es Nlog2N (no existen algoritmos de ordenacion mas eficientes!).

En el programa ch06e11.pro podeis encontrar un ejemplo completo de manejo dearboles (declaracion, creacion e impresion) para ordenar alfabeticamente una secuenciade caracteres.

En el programa ch06e12.pro teneis una version algo mas compleja del mismoejemplo (toma como entrada un fichero). Su ejecucion es unas 5 veces mas eficiente queel programa SORT.EXE que proporciona el sistema DOS (aunque ligeramente menor queel sort optimizado de UNIX).

Observad que en estos dos ultimos ejemplos aparecen algunos predicados pre-definidos que no hemos introducido aun (openread, writedevice, etc.). Podeis con-sultar la Parte IX para encontrar mas informacion sobre ellos.

Ejercicio. Usar estructuras de datos recursivas para implementar hipertexto. El hiper-texto consiste basicamente en que alguna de las palabras de un texto pueden conteneruna referencia a un nuevo texto, el cual, a su vez, puede contener palabras que llevenasociadas nuevamente referencias a otro texto, etc. Para simplificarlo, podemos consid-erar unicamente strings que llevan asociados un unico string (conteniendo posiblementeuna nueva referencia). Es decir, partimos de una declaracion como esta:

27

Page 29: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

domainslink = vacio; entrada(string, link)

Definid ahora un pequeno hipertexto como este:

entrada("Prolog es un lenguaje de programacion...",entrada("Prolog: Lenguaje de programacion declarativo...",

entrada("Declarativo: ...", vacio).

y realizar un programa que, dada esta estructura, muestre el primer string por pantallay espere a que se pulse Enter, muestre el segundo string y espere de nuevo a que sepulse Enter, y ası hasta alcanzar el fin de la estructura, momento en el cual mostrara elmensaje "No hay mas informacion".

28

Page 30: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

Parte VII

Listas y recursion

29

Page 31: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

El objetivo de esta parte es introducir el procesamiento de listas en Prolog. Concreta-mente, veremos como declarar listas, algunos ejemplos de su uso, y la definicion de lostıpicos predicados de Prolog member y append. Por ultimo, se introduce el predicadopredefinido findall, que nos permitira recoger todas las soluciones para un objetivodado.

1.- ¿Que es una lista?

Una lista es una estructura que contiene un numero indeterminado de objetos. Secorresponde basicamente con los vectores de otros lenguajes, pero no requiere que seconozca el numero maximo de elementos de antemano. Usando estructuras recursivastambien es posible definir estructuras dinamicas, pero a menudo resulta mas sencillo eluso de listas.Una lista que contenga los elementos 1, 2 y 3 se escribe como:

[ 1, 2, 3 ]

Los elementos de la lista se separan por comas y aparecen encerrados entre corchetes.Algunos ejemplos mas son:

[ ana, pepe, juan][ "Ana Perez", "Juan Garcia"]

1.1. Declaracion de listas

Para declarar un tipo lista enteros que consista de una lista de numeros enteros,escribimos:

domainslista_enteros = integer*

Los elementos de una lista pueden ser a su vez listas (dando lugar a algo similar a losvectores multidimensionales de Pascal). La unica restriccion es que todos los elementosde la lista deben ser del mismo tipo. Por ejemplo, esta declaracion:

domainsmatriz = lista_enteros*lista_enteros = integer*

es perfectamente valida, y sirve para disponer de un tipo matriz que consiste de unalista de listas de enteros (una matriz de enteros). Sin embargo, esta declaracion:

domainslista_mixta = integer* ; symbol*

no es valida, incluso aunque la escribamos ası:

30

Page 32: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

lista_mixta = elemento*elemento = integer ; symbol

(ahora el problema estarıa en la declaracion de elemento, que ya vimos en la Parte Vque no era valida). La forma correcta de declarar una lista que pueda contener numerosenteros y secuencias de caracteres es:

domainslista_mixta = elemento*elemento = i(integer); s(symbol)

Una lista de este tipo podrıa ser esta: [i(3), s(pepe), i(6)].

Cabeza y cola

La forma habitual de entender las listas en Prolog consiste en verlas como un oper-ador binario, cuyo primer argumento es la cabeza de la lista, y cuyo segundo argumentoes la cola de la lista. Es decir, dada una lista:

[ a, b, c, d ]

decimos que a es la cabeza de la lista, y [b, c, d] es la cola. Este proceso se puederepetir, de manera que la lista [b, c, d] se descompone a su vez en cabeza (b) ycola ([c, d]), y ası hasta llegar a la lista vacıa ([ ]). Podemos representarla con unaestructura de arbol como sigue:

a

b

c

d [ ]

���

TTT

lista

����

TTT

lista

������

TTT

lista

!!!!!!!!

TTT

lista

De hecho, incluso una lista conteniendo un solo elemento [a] se puede ver como unaestructura compuesta de la forma:

a [ ]

���

TTT

lista

En este caso, a es la cabeza y [ ] es la cola de la lista.

31

Page 33: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

1.2. Procesamiento de listas

Prolog dispone de una forma para hacer explıcita la separacion entre la cabeza y lacola de una lista. Concretamente, usamos una barra vertical (|) en lugar de la coma.Por ejemplo, la lista:

[ a, b, c, d ]

es equivalente a:

[ a | [ b, c, d ] ]

y, continuando el proceso, tenemos:

[ a | [ b | [ c | [ d | [] ] ] ] ]

Por otra parte, se puede combinar el operador “,” con “|”, de forma que la lista [a,b, c, d] se escriba:

[ a, b | [ c, d ] ]

Veamos algunos ejemplos de como se puede partir una lista en cabeza y cola:

Lista Cabeza Cola[’a’, ’b’, ’c’] ’a’ [’b’, ’c’][ ’a’ ] ’a’ [ ][ ] indefinido indefinido[[1, 2, 3], [2, 1], [ ]] [1, 2, 3] [[2, 1], [ ]]

La siguiente tabla muestra algunos ejemplos de como se unifican las listas:

Lista 1 Lista 2 Instaciacion de variables[X, Y, Z] [pepe, juan, ana] X=pepe, Y=juan, Z=ana[7] [X | Y] X=7, Y=[ ][1, 2, 3, 4] [X, Y | Z] X=1, Y=2, Z=[3,4][1, 2] [2 | X] fallo

2.- Uso de listas

Puesto que las listas son estructuras de datos recursivas, vamos a necesitar algorit-mos recursivos para procesarlas. Este tipo de algoritmos suelen contener dos clausulas:una para procesar una lista ordinaria (es decir, que pueda subdividirse en cabeza ycola) y otra para procesar la lista vacıa.

2.1. Escritura de listas

El procedimiento para escribir una lista es muy simple:

32

Page 34: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

escribir_lista([]).

escribir_lista([H|T]) :-write(H), nl,escribir_lista(T).

Ahora, dado un objetivo:

escribir_lista([1, 2, 3]).

aparecera por pantalla:

123yes

Ejercicio. ¿El programa para escribir listas cumple las condiciones de la recursion decola? ¿Las cumplirıa si invirtiesemos el orden de las clausulas?

2.2. Contando los elementos de una lista

Para contar los elementos de la lista, podemos definir un algoritmo recursivo que:

si la lista esta vacıa, devuelve 0;

en otro caso, el resultado es 1 mas el numero de elementos de la cola (llamadarecursiva).

Concretamente, es suficiente con las dos clausulas siguientes:

nelem([], 0).nelem([_|T], L) :-

nelem(T, TL),L = TL+1.

Por ejemplo, el objetivo:

nelem([a, b, c, d], L).

tiene exito mostrando por pantalla:

L = 41 Solution

Ejercicio. ¿Cual es el resultado del siguiente objetivo?

nelem(X,3), !.

33

Page 35: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

Ejercicio. Escribir un programa sumlist que sume los elementos de una lista. (Nota: esbasicamente similar al procedimiento nelem.)

Ejercicio. ¿Que ocurre si lanzamos el siguiente objetivo al programa anterior?

sumlist(Lista, 10).

¿Por que ha ocurrido esto?

2.3. Listas y recursion de cola

Como resulta sencillo de comprobar, la definicion del procedimiento nelem paracalcular el numero de elementos de una lista no cumple las condiciones de la recursionde cola. Convertirlo en uno que sı las cumpla no es una tarea sencilla, aunque es posiblehacerlo.

Para ello, hay que crear una version recursiva que use un contador, similar al pro-grama factorial que vimos en la parte anterior:

nelem(L, N) :- nelem_aux(L, N, 0).

nelem_aux([], N, N).nelem_aux([_|T], N, Contador) :-

NuevoContador = Contador+1,nelem_aux(T, N, NuevoContador).

Como podeis ver, esta version es algo mas compleja y menos legible que la versionanterior. Solo la hemos presentado para mostrar que, aunque el proceso no suele sersencillo, es posible obtener una version con recursion de cola a partir de practicamentecualquier algoritmo recursivo.

Ejercicio. Reescribe el programa sumlist para que cumpla las condiciones de la recur-sion de cola.

Veamos algunos ejemplos mas. El siguiente fragmento de programa sirve para sumar 1a todos los elementos de una lista:

sum1([], []). /* caso base */sum1([H | T], [NewH | NewT]) :- /* caso recursivo */

NewH = H+1, /* sumar 1 al primer elemento */sum1(T, NewT). /* sumar 1 al resto de elementos */

(La version completa del programa esta en ch07e04.pro.) Fijaos en que esta versionya cumple las condiciones de la ‘recursion de cola.

El siguiente ejemplo muestra como podemos obtener una sublista con los numerospositivos de una lista:

34

Page 36: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

eliminar_negativos([], [])

eliminar_negativos([H|T], ListaPos) :-H < 0, /* si H es negativo, no se considera */!,eliminar_negativos(T, ListaPos).

eliminar_negativos([H|T], [H|ListaPos]) :-eliminar_negativos(T, ListaPos).

(La version completa del programa esta en ch07e05.pro.)

Por ultimo, el siguiente programa duplica los elementos de una lista:

duplicar([], []).duplicar([H | T], [H, H | DobleT]) :-

duplicar(T, DobleT).

2.4. El predicado member

Supongamos que tenemos una lista con nombres: Juan, Pedro, Ana, etc. y queremossaber si un determinado nombre se encuentra en la lista. Para ello, podemos usar elsiguiente programa (ch07e06.pro):

DOMAINSnamelist = name*name = symbol

PREDICATESnondeterm member(name, namelist)

CLAUSESmember(Name, [Name|_]).member(Name, [_|Tail]) :- member(Name,Tail).

Podemos probar con el objetivo:

member(juan, [pedro, susana, juan]).

y Prolog contestara yes.

Ejercicio. Supongamos que cambiamos de orden las dos clausulas de member. ¿Hayalguna diferencia con la version anterior? (Nota: probad el objetivo member(X, [ana,juan, pedro]) con las dos versiones.)

35

Page 37: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

2.5. Concatenacion de listas: el predicado append

En primer lugar, si revisamos la definicion del predicado member:

member(Name, [Name|_]).member(Name, [_|Tail]) :- member(Name, Tail).

esta se puede interpretar de dos formas:

Desde el punto de vista declarativo: “Name es miembro de una lista si la cabezade la lista es igual a Name; si no, Name es miembro de la lista si es miembro de lacola”.

Desde un punto de vista procedural: “Para obtener un elemento que sea miembrode una lista, podemos devolver la cabeza de la lista, o bien cualquier elementoque sea miembro de la cola de la lista”.

Estos dos puntos de vista se corresponden, respectivamente, con los objetivos:

member(2, [1, 2, 3]). member(X, [1, 2, 3]).

Como podeis ver, la definicion de member es la misma, pero sirve para dos propositosdistintos: (1) comprobar si un determinado elemento es miembro de una lista (porejemplo, member(2,[1,2,3])), y (2) obtener todos los elementos de una lista (porejemplo, member(X,[1,2,3])).

Diferentes usos del mismo procedimiento

Una de las ventajas de Prolog es que, a menudo, uno puede implementar un proced-imiento teniendo en mente solo una de las posibles interpretaciones del mismo, y queeste sea igualmente util para el resto de interpretaciones. Por ejemplo, si nos planteamosimplementar un procedimiento append(List1, List2, List3) que, dadas las listasList1 y List2, nos devuelva en List3 la concatenacion de List1 y List2, podrıamosescribir esto:

append([], List2, List2).append([H|L1], List2, [H|L3]) :- append(L1, List2, L3).

cuya interpretacion es:

1. La concatenacion de una lista vacıa [ ] con List2 es List2.

2. La concatenacion de una lista no vacıa [H|L1] con List2 es una lista cuyo el-emento en cabeza es H y cuya cola es la concatenacion de la cola de la primeralista (L1) y List2.

Por ejemplo, dado el objetivo:

append([1, 2, 3], [8, 9], L).

36

Page 38: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

obtenemos la siguiente solucion:

L = [1, 2, 3, 8, 9]1 Solution

(En ch07e07.pro teneis una version completa del programa.)Sin embargo, desde el punto de vista declarativo, hemos definido (sin habernoslo

propuesto!) una relacion que tiene exito siempre que la concatenacion de las dosprimeras listas sea igual a la tercera. Estos nos permite lanzar objetivos como este:

“encontrar una sublista L1 tal que la concatenacion de L1 con [2,3] sea lalista [1,2,3]”

(es decir, queremos calcular la diferencia de [1,2,3] menos [2,3]). En Prolog, es-cribirıamos el objetivo:

append(L1, [2,3], [1,2,3]).

cuya solucion es:

L1 = [1]1 Solution

De forma similar, podemos lanzar un objetivo:

append([1], L2, [1,2,3]).

y averiguar que lista hay que concatenar a [1] para obtener la lista [1,2,3]. Lasolucion de Prolog es:

L2 = [2, 3]1 Solution

Mas aun, podrıamos usar el predicado append para generar todas las posibles sublistascuya concatenacion es [1,2,3]. Concretamente, dado el objetivo:

append(L1, L2, [1,2,3]).

Prolog nos devuelve:

L1 = [], L2 = [1,2,3]L1 = [1], L2 = [2,3]L1 = [1,2], L2 = [3]L1 = [1,2,3], L2 = []4 Solutions

En resumen, disponemos de un procedimiento en el que los patrones de flujo de infor-macion no son fijos. Podemos preguntar al sistema cual es la salida del procedimientodada una cierta entrada de datos, o bien preguntar cual debe ser la entrada para obteneruna cierta salida. Ambas formas de uso son validas con una misma definicion.

Como ya comentamos en la Parte II, el comportamiento en la entrada/salida dedatos a un procedimiento se establece mediante sus patrones de flujo de datos. Porejemplo, cuando usamos append para obtener la concatenacion de dos listas, como enel objetivo:

37

Page 39: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

append([1,2], [3], L):

tenemos un patron de flujo de datos (i,i,o), es decir, los dos primeros argumentosson de entrada (i) y el tercero de salida (o). La definicion del predicado append tienela particularidad de aceptar cualquier patron de flujo de datos:

(i,i,i) (i,i,o) (i,o,i) (o,i,i)(i,o,o) (o,i,o) (o,o,i) (o,o,o)

Ejercicio. Escribir la definicion de un predicado miembro par(E, L) que tenga exitosi el elemento E es un miembro de la lista L y, ademas, es par. Por ejemplo, dado elobjetivo:

miembro_par(2, [1,2,3,4,5,6]).

la respuesta debe ser:

yes

mientras que, dado el objetivo:

miembro_par(X, [1,2,3,4,5,6]).

la respuesta debe ser:

X = 2X = 4X = 63 Solutions

3.- Encontrando todas las soluciones

En la Parte VI discutimos las dos formas de realizar procesos repetitivos: backtrack-ing y recursion. Quedaron claras las ventajas de la recursion frente al backtracking, yaque con un procedimiento recursivo es posible pasar informacion (a traves de los ar-gumentos) de una iteracion a la siguiente. Sin embargo, hay cosas que la recursion nopuede hacer: generar todas las soluciones a un objetivo.

Supongamos ahora que necesitamos generar todas las soluciones para un objetivodado y, ademas, disponer de ellas agrupadas dentro de una estructura. Para esto, Prologdispone del predicado predefinido findall. Dado un objetivo de la forma:

findall(Var, Goal, ListVar).

Prolog nos devuelve en ListVar una lista con todos los valores de Var tales que Goaltiene exito.

Por ejemplo, dada la definicion de append:

append([], List2, List2).append([H|L1], List2, [H|L3]) :- append(L1, List2, L3).

38

Page 40: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

podemos lanzar el objetivo:

findall(L1, append(L1, L2, [1,2,3]), ListL1).

cuya solucion sera:

ListL1 = [ [], [1], [1,2], [1,2,3] ]1 Solution

Es decir, nos ha construıdo una lista ListL1 conteniendo todos los valores de L1 talesque el objetivo append(L1,L2,[1,2,3]) tiene exito.

En el programa ch07e08.pro podeis ver otro ejemplo del uso de findall para cal-cular la media de las edades de varias personas:

DOMAINSname,address = stringage = integerlist = age*

PREDICATESnondeterm person(name, address, age)sumlist(list, age, integer)run

CLAUSESsumlist([],0,0).sumlist([H|T],Sum,N) :-

sumlist(T,S1,N1),Sum=H+S1, N=1+N1.

person("Sherlock Holmes", "22B Baker Street", 42).person("Pete Spiers", "Apt. 22, 21st Street", 36).person("Mary Darrow", "Suite 2, Omega Home", 51).

GOALfindall(Age,person(_, _, Age),L),sumlist(L,Sum,N),Ave = Sum/N,write("Average=", Ave),nl.

Por otro lado, si quisieramos obtener un listado de las personas que tienen 42 anos,podrıamos lanzar simplemente el objetivo:

findall(Who, person(Who,_,42), List).

Sin embargo, fijaos en que nos da un error de tipos. El problema consiste en que lalista List que debe construir findall debe estar declarada. Es decir, debemos anadirla declaracion:

slist = string*

(el nombre de la lista no importa). Aunque pueda parecer un inconveniente, este tipode situaciones se da rara vez en aplicaciones reales.

39

Page 41: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

Parte VIII

La base de datos interna

40

Page 42: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

Como ya comentamos brevemente en la Parte III, Visual Prolog nos permite definiruna base de datos interna compuesta de hechos. Los predicados de estos hechos debendeclararse en la seccion database, y se pueden actualizar en tiempo de ejecucion me-diante ciertos predicados predefinidos.

La secuencia de hechos para un predicado dado se comporta como una tabla de unabase de datos y, en realidad, Visual Prolog los trata en compilacion como si se tratasede una base de datos real.

1.- Declaracion de la base de datos interna

La definicion de un programa que contenga una base de datos interna tiene unaspecto como este:

domainsnombre, direccion = stringedad = integergenero = hombre ; mujer

databasepersona(nombre, direccion, edad, genero)

predicateshombre(nombre, direccion, edad)mujer(nombre, direccion, edad)

clausespersona("Rosa", "Madrid", 35, hombre).persona("Miguel", "Valencia", 24, mujer).

hombre(Nombre, Direccion, Edad) :-persona(Nombre, Direccion, Edad, hombre).

mujer(Nombre, Direccion, Edad) :-persona(Nombre, Direccion, Edad, mujer).

En este ejemplo usamos el predicado persona de la misma forma que el resto depredicados (hombre y mujer). La unica diferencia es que, en tiempo de ejecucion, sepueden insertar y borrar hechos del tipo persona(...).

Existen dos restricciones al uso de la base de datos:

1. solo se pueden anadir hechos, no reglas;

2. los hechos no pueden contener variables desinstanciadas.

Es posible, sin embargo, definir mas de una base de datos. Para hacer esto, debemosdarle un nombre explıcito a cada una:

41

Page 43: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

database - personaspersona(nombre, direccion, edad, genero)

database - estudiantesestudiante(nombre, direccion, edad)

Esta declaracion crea dos bases de datos con los nombres personas y estudiantes. Sisolo usamos una base de datos, no es necesario darle un nombre, aunque internamentese le asignara el nombre dbasedom.

2.- Uso de la base de datos interna

En primer lugar, fijaos en que mediante una secuencia de hechos Prolog resultainmediato definir una base de datos relacional haciendo uso de la base de datos interna.Ası, los objetivos Prolog se pueden utilizar para lanzar consultas a la base de datos,la unificacion se encarga de computar los valores para las variables de la consulta, y elbacktracking se encarga de obtener todas las soluciones para dichas variables.

2.1. Acceso a la base de datos interna

El acceso a los predicatos pertenecientes a la base de datos interna es exactamenteel mismo que al resto de los predicados. Es decir, dado el siguiente programa:

domainsnombre = stringsexo = char

databasepersona(nombre, sexo)

clausespersona("Elena", ’M’).persona("Maria", ’M’).persona("Juan", ’H’).

podemos lanzar un objetivo del tipo:

persona(Nombre, ’M’).

para encontrar los nombres de todas las mujeres de la base de datos, o bien:

persona("Elena", _).

para comprobar si Elena pertenece a nuestra base de datos.

42

Page 44: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

2.2. Actualizacion de la base de datos interna

Los hechos de la base de datos interna se pueden escribir directamente en el pro-grama, tal como hemos hecho en los ejemplos anteriores, o bien se pueden insertar oeliminar en tiempo de ejecucion.

Disponemos de los predicados predefinidos: assert, asserta, assertz, retract, re-tractall, consult y save, cuya definicion veremos en los siguientes apartados. Estospredicados pueden tomar uno o dos argumentos. Indicaremos entre comentarios el pa-tron de flujo de datos asociado a cada predicado.

Insercion de hechos

Disponemos de 3 predicados predefinidos (de uno o dos argumentos cada uno):

asserta(hecho) /* (i) */asserta(hecho, nombre_database) /* (i,i) */

assertz(hecho) /* (i) */assertz(hecho, nombre_database) /* (i,i) */

assert(hecho) /* (i) */assert(hecho, nombre_database) /* (i,i) */

El primero, asserta, anade el nuevo hecho al principio de los hechos ya existentes parael predicado dado; assertz lo anade al final (y assert se comporta igual).

El segundo argumento es siempre opcional, ya que los nombres de los predicadosdeben ser unicos (aunque existan varias bases de datos definidas en el programa), conlo que Prolog sabe perfectamente donde debe insertarlo. Sin embargo, si quereis com-probar que lo estais insertando en el lugar correcto, podeis usar las versiones de dosargumentos.

Por ejemplo, si tenemos un programa que contiene los hechos:

persona("Rosa", "Madrid", 35).persona("Miguel", "Valencia", 24).

y lanzamos el objetivo:

assertz(persona("Pepe", "Madrid", 36)),asserta(persona("Ana", "Valencia", 22)).

el nuevo conjunto de hechos del programa es:

persona("Ana", "Valencia", 22).persona("Rosa", "Madrid", 35).persona("Miguel", "Valencia", 24).persona("Pepe", "Madrid", 36).

43

Page 45: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

Prolog no comprueba si un hecho existe o no en la base de datos antes de insertarlo.Si deseamos hacer dicha comprobacion, podemos definir un procedimiento del tipo:

mi_assert(persona(Nombre,Direccion)) :-persona(Nombre, Direcccion), ! ; /* disyuncion */assert(persona(Nombre, Direccion)).

Este procedimiento comprueba primero si existe y, en caso negativo, lo inserta.

Consulta de un fichero de hechos

El predicado predefinido consult se utiliza para cargar en la base de datos internauna coleccion de hechos almacenada en un fichero. consult puede tener uno o dosargumentos:

consult(nombreFichero) /* (i) */consult(nombreFichero, nombreDatabase) /* (i,i) */

consult siempre almacena los nuevos hechos al final de la base de datos. Sin embargo,a diferencia de assertz, si no se le especifica un nombre de base de datos, unicamentelee los hechos de la base de datos por defecto (dbasedom).

En general, si llamamos a consult con un nombre concreto de base de datos, sololeera los hechos declarados en dicha base de datos. Si el fichero contiene algun hechono declarado en la base de datos especificada, se producira un error. Sin embargo,tened en cuenta que la lectura se realiza de forma secuencial. Es decir, si el fichero queconsultamos contiene 10 hechos, y el septimo que aparece en el fichero contiene algunerror de sintaxis (o no pertenece a la base de datos especificada), los seis primeroshechos sı se leeran y despues mostrara el mensaje de error.

Para que consult pueda leer un fichero sin problemas, este debe tener el mismoformato que los ficheros generados por save, es decir, no debe contener:

caracteres en mayuscula (excepto en strings entre dobles comillas)

espacios en blanco (excepto en strings entre dobles comillas)

comentarios

lıneas en blanco

Eliminacion de hechos

El predicado predefinido retract elimina hechos de la base de datos interna. Suformato es:

retract(hecho) /* (i) */retract(hecho, nombreDatabase) /* (i,i) */

44

Page 46: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

retract(hecho) elimina de la base de datos interna el primer hecho que unifique conhecho, instanciado en el proceso las variables que aparezcan en hecho. El predicadoretract es indeterminista y, mediante backtracking, puede eliminar no solo el primero,sino todos los hechos de la base de datos que unifiquen con su argumento. Por ejemplo,dado el programa:

databasepersona(string, string, integer)

database - mibasededatostiene(string, string)no_tiene(string, string)

clausespersona("Ana", "Valencia", 22).persona("Rosa", "Madrid", 35).persona("Miguel", "Valencia", 24).persona("Pepe", "Madrid", 36).

tiene("Miguel", "casa").tiene("Ana", "coche").tiene("Miguel", "perro").

no_tiene("Ana", "coche").no_tiene("Pepe", "perro").

El objetivo:

retract(persona(_, "Madrid", _)).

eliminara de la base de datos dbasedom (puesto que no le hemos dado un nombreexplıcito) el hecho:

persona("Rosa", "Madrid", 35).

Por el contrario, si hubieramos lanzado el objetivo:

retract(persona(Nombre, "Madrid", _)),write(Nombre), nl,fail.

entonces se hubieran eliminado los hechos:

persona("Rosa", "Madrid", 35).persona("Pepe", "Madrid", 36).

(gracias al backtracking provocado por fail), mostrando por pantalla lo siguiente:

Nombre = "Rosa"Nombre = "Pepe"no

45

Page 47: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

El motivo de que el ultimo mensaje sea no es que, tras el ultimo fail, intenta buscarmas hechos que unifiquen con persona(Nombre,"Madrid", ) y, al no encontrarlos, seproduce un fallo que aborta la computacion.

Como ocurrıa en el caso de assert, el segundo argumento de retract es opcional.Es decir, el objetivo:

retract(tiene("Ana",_)).

y el objetivo:

retract(tiene("Ana",_), mibasededatos).

tienen exactamente el mismo efecto, es decir, eliminan el hecho tiene(.Ana",coche").Sin embargo, si lanzamos el objetivo:

retract(persona("Ana",_,_), mibasededatos).

obtendremos un mensaje de error, ya que el predicado persona no esta declarado enla seccion database - mibasededatos.

Por ultimo, tened en cuenta lo siguiente. Si no usamos el nombre explıcito de labase de datos, Prolog no aceptara un objetivo del tipo:

retract(X).

Sin embargo, si indicamos el nombre de la base de datos, es posible lanzar un objetivode la forma:

retract(X, mibasededatos),write(X),fail.

cuyo efecto serıa eliminar todos los hechos (con cualquier predicado) que aparezcan enla base de datos interna mibasededatos (y escribirlos por pantalla).

Eliminacion de varios hechos a la vez

Disponemos tambien de una variante de retract que nos permite eliminar varioshechos a la vez, sin necesidad de usar el backtracking: retractall. Su sintaxis es:

retractall(hecho) /* (i) */retractall(hecho, nombreDatabase) /* (i,i) */

Dado un objetivo retractall(hecho), su efecto es eliminar todos los hechos de la basede datos que unifiquen con hecho. Es decir, se comporta como si lo hubieramos definidoası:

retractall(X) :- retract(X), fail.retractall(_).

46

Page 48: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

aunque es considerablemente mas rapido. Como podeis imaginar, retractall solo puedetener exito una vez y, por tanto, no es posible obtener valores de salida para sus variablesdesinstanciadas. Por ello, al igual que con not, las variables desinstanciadas debenaparecer como variables anonimas (“ ”). Por ejemplo, el objetivo:

retractall(persona(_,_,36)).

elimina todas las personas de la base de datos cuya edad sea 36, mientras que el objetivo:

retractall(_,mibasededatos).

elimina todos los hechos (con cualquier predicado) que aparezcan en la base de datosmibasededatos.

2.3. Como salvar la base de datos interna en un fichero

El predicado predefinido save nos permite salvar el estado de una base de datos enun fichero. Su sintaxis es:

save(nombreFichero) /* (i) */save(nombreFichero, nombreDatabase) /* (i,i) */

Si no indicamos el nombre de la base de datos, en el fichero nombreFichero se alma-cenaran los hechos de la base de datos por defecto (dbasedom).

3.- Un ejemplo

Para terminar esta parte, podeis cargar el programa ch08e01.pro que contiene unpequeno sistema experto que hace uso de la base de datos interna. Lanzad el objetivo:

run(tool).

y contestad a las preguntas como si desearais encontrar una herramienta para comuni-caros con un ordenador. Probad luego con el objetivo:

update, run(tool).

y contestad a las preguntas del mismo modo. Observad el codigo del programa e inten-tad compreder su funcionamiento.

47

Page 49: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

Parte IX

Predicados predefinidos

48

Page 50: Facultad de Inform´atica - 4A El lenguaje de …users.dsic.upv.es/~jsilva/fin/idr/practica3.pdf · Las variables deben comenzar por una letra mayusc´ ula o por subrayado ( ). Una

Visual Prolog dispone de un gran numero de predicados predefinidos, es decir, predica-dos cuya definicion es ya conocida por el sistema y que, por tanto, podemos usar sinincluir su definicion en los programas.

A continuacion podeis ver un listado de los principales grupos de predicados pre-definidos en Visual Prolog, ası como donde podeis encontrar mas informacion sobreellos:

Aritmetica y comparacion. Consultar:

Help/Visual Prolog Language Fundamentals/Arithmetic and Comparison

Manejo de errores y control de ejecucion. Consultar:

Help/Contents/Predefined Predicates/Function Groups/Error and Break control

Entrada, salida y manejo de ficheros. Consultar:

Help/Visual Prolog Language Fundamentals/Writing, Reading, and Files

Manejo de strings. Consultar:

Help/Visual Prolog Language Fundamentals/String Handling

Llamadas al sistema. Consultar:

Help/Visual Prolog Language Fundamentals/System Level Programming

Ademas, podeis consultar una serie de ejemplos sencillos en Visual Prolog. Estosejemplos presentan diversas aplicaciones tıpicas de Prolog y, aunque son versiones sim-ples, se pueden extender facilmente. Los ejemplos son:

1. ch15e01.pro: un pequeno sistema experto para encontrar un animal a partir desus caracterısticas.

2. ch15e02.pro: un programa que permite averiguar si existe una ruta posible entredos ciudades.

3. ch15e03.pro: un pequeno ejemplo sobre como encontrar la salida a un laberintodefinido mediante galerıas.

4. ch15e04.pro: la implementacion de un circuito logico mediante un programaProlog.

5. ch15e05.pro: las torres de Hanoi.

6. ch15e06.pro: un programa que divide las palabras en sılabas.

49