Prácticas de Inteligencia Artificial
e Ingeniería del Conocimiento I
Cuarto Curso de Informática. Curso 1998-1999
 
José Angel Bañares Bañares
Pedro Muro Medrano
Javier Zarazaga Soria
 
Area de Lenguajes y Sistemas Informáticos
 
 
Departamento de Informática e Ingeniería de Sistemas
Universidad de Zaragoza
 
 

0.Introducción
    0.1Presentación
    0.2.Entorno de Trabajo
      A continuación se describe brevemente el editor emacs y el entorno de trabajo Allegro Common Lisp.

      0.2.1.Emacs
      Vamos a crear un fichero de texto utilizando el editor Emacs. Para invocar al editor teclea lemacs. Si todo ha ido bien estás dentro del editor emacs. Puedes teclear el texto que desees. Existen además una serie de comandos básicos que permiten moverse por el texto, hacer búsquedas, buscar una determinada línea, etc.

      Para dar un comando al editor (por ejemplo, guardar el fichero con los últimos cambios que hayamos realizado) es necesario hacerlo por teclado. Para distinguirlos del texto que tecleamos normalmente para que se añada a nuestro fichero de texto, los comandos se dan al editor pulsando a la vez una tecla especial y otra normal de texto. Estas teclas especiales son la tecla de Control (en vuestro teclado Ctrl o Ctl) y la tecla Meta (etiquetada Meta, Alt o Edit). Cuando representemos una de estas pulsaciones combinadas de teclas, en vez de escribir CONTROL o META cada vez que queramos prefijar un carácter lo denotaremos con Ctrl- <chr> o Alt-<chr>:
       

              Una vez acabes de escribir tu programa puedes utilizar los siguientes comandos para desplazarte por el texto:

                                      Línea previa Ctrl-P
      Retrocede carácter, Ctrl-B     Posición del cursor     Avanza carácter, Ctrl-F
                                  Línea siguiente, Ctrl-N

      Una vez que sepas moverte serás capaz de situarte en los sitios donde existan errores. Puedes borrar el carácter anterior al cursor con la tecla RUBOUT o DELETE.

      A continuación debes guardar el programa en un fichero. Los comandos para el manejo de ficheros constan de Ctrl-X seguido por otro carácter. En este momento tienes tu programa en el buffer del emacs (en memoria, no guardado en el disco), es decir, si salieras de Emacs sin guardar los cambios perderías el trabajo realizado. (Tranquilo, antes de salir del editor, emacs te preguntará si quieres que se guarden los cambios). Teclea Ctrl-X Ctrl-S cuando quieras guardar el buffer en un fichero. Aparecerá en la parte inferior de la ventana un mensaje como File to save in:~ /practica1/; a continuación teclea el nombre del fichero File to save in:~/practica1/prueba1.lisp y pulsa RETURN).

      Si el buffer ya tiene asignado un fichero cada vez que teclees Ctrl-X Ctrl-S guardará los cambios en ese fichero. Si quieres guardar el buffer en otro fichero teclea Ctrl-X Ctrl-W, de esta forma te preguntará siempre en que fichero quieres guardar el buffer.

      Una vez que tengas el texto correctamente escrito y guardado en un fichero teclea Ctrl-X Ctrl-C para salir del editor. Comprueba que el fichero prueba1.lisp se encuentra en el directorio mediante la orden ls que listará los nombres de tus ficheros y subdirectorios.

      0.2.1.1.Otras ordenes útiles

        Nota: puedes saltarte esta parte en una primera lectura.

        Búsquedas

          Ctrl-S permite realizar búsquedas en tu texto. Al teclear Ctrl-S aparece en la parte inferior de la pantalla el mensaje I-search. Según vas pulsando teclas Emacs realiza una búsqueda con los caracteres que vas introduciendo a partir de donde esté situado el cursor. Una vez que has escrito la palabra buscada, si quieres repetir la búsqueda pulsa de nuevo Ctrl-S.
           
        Movimiento por el texto
          Alt-F Avanza una palabra Alt-B Retrocede una palabra

          Alt-E Avanza una sentencia Alt-A Retrocede una sentencia

          Ctrl-A Mueve al principio de línea Ctrl-E Mueve al final de la línea

          Alt-< Va al principio del fichero Alt-> Va al final del fichero
           

        Borrar o recuperar líneas borradas
          Ctrl-D Borra el carácter siguiente al cursor

          Ctrl-K Borra desde la posición del cursor hasta el final de línea

          Alt-K Borra desde el final de la sentencia actual

          Cuando borras algo mayor que un carácter Emacs lo guarda para tí. La forma de recuperarlo es mediante Ctrl-Y. Esto es muy útil para desplazar un conjunto de líneas. Borra las líneas que quieras desplazar repitiendo Ctrl-K. Sitúa el cursor donde quieres pegar todo lo borrado y teclea Ctrl-Y. Además puedes deshacer tu última acción tecleando Ctrl-X u (undo o deshacer).
           

        Repetición de una orden
          Puedes repetir una orden un número determinado de veces tecleando Ctrl-U, el número de veces que quieres repetir la orden y a continuación la orden deseada. Por ejemplo, si queremos ir a la línea 10 podemos teclear Alt-< para colocarnos en el principio del fichero y teclear Ctrl-U 9 Ctrl-N para ir a la línea 10.
           
        Más sobre ficheros
          Si quieres cargar un fichero en el buffer para leerlo o modificarlo teclea Ctrl-X Ctrl-F. Emacs te preguntará por el nombre del fichero que quieres editar. Cuando pulses RETURN, Emacs colocará el fichero en el buffer. Emacs guarda la versión anterior de tus fichero añadiendo el símbolo ~ al final del nombre del fichero. También puedes teclear el comando emacs seguido del nombre del fichero que quieres editar, de esta forma puedes cargar en el buffer el fichero una vez dentro del editor, o editar directamente: emacs prueba1.lisp.
           
        Utilizando varios buffers y ventanas
          Cuando se está editando con emacs se tiene un único buffer seleccionado. Pero es posible activar otros buffers que contengan otros ficheros, o el interprete lisp.

          Ctrl-X b Buffer Selecciona o crea un buffer llamado buffer. (Cambio de buffer)

          Ctrl-X Ctrl-B Muestra la lista de buffers

          Ctrl-X Ctrl-K Elimina un buffer

          Además puede dividirse la ventana en varias ventanas con un buffer activo cada una.

          Ctrl-X 2 Divide la ventana en dos

          Ctrl-X 1 Solo una ventana

          Ctrl-X o Selecciona otra ventana

      0.2.2.Entorno de desarrollo Allegro Common Lisp
        0.2.2.1.Invocación del Allegro Common Lisp
        0.2.2.2.Interacción con el intérprete y depuración de programas Lisp

        Una vez iniciado el interprete, ACL escribe un mensaje y entra en el bucle"promt-read-eval-print". El prompt por defecto será:

        Nombre del paquete
                  |
                  v
                 USER(1):
                      ^
                      |
                 Linea de instruccion
         

        Cuando se comete un error se entra en el bucle "break", que da facilidades de depuración:

        Nombre del paquete
                         |
                         v
                    [2] USER(1):
                    ^        ^
                   /         |
          Nivel de "break"   |
                         Linea de instruccion
         
        Se puede obtener AYUDA sobre las instrucciones que se pueden introducir en el nivel "break" con :help. Para volver al nivel superior teclea :pop.

        USER(4): (Hola)
         Error: attempt to call `HOLA' which is an undefined function.
           [condition type: UNDEFINED-FUNCTION]

         Restart actions (select using :continue):
          0: Try calling HOLA again.
          1: Return a value instead of calling HOLA.
          2: Try calling a function other than HOLA.
          3: Setf the symbol-function of HOLA and call it again.
         [1] USER(5): :pop
         USER(6):

        Las instrucciones más importantes para depurar un programa lisp son las que permiten visualizar la pila. La pila es la entidad donde los argumentos de las funciones lisp son evaluados. Cuando una función es llamada, la función que llama evalúa y coloca en la cima de la pila los argumentos de la función llamada. Una ventana de la pila es el área de la pila donde residen los argumentos de una función invocada. Si por ejemplo, potencia llama a producto, que a su vez llama a suma, habra por lo menos tres ventanas activas cuando se invoque a suma. Cuando se entra en el nivel "break", la primera ventana de la pila que no está asociada con el mecanismo de manejo del error suele ser el más interesante.

        Las siguientes funciones son las más interesantes para inspeccionar la pila, y así poder comprobar los argumentos que han recibido las funciones:

        • :zoom muestra información de la pila. zoom se puede abreviar con :zo. Puede ir seguida de distintas opciones. Si se utiliza la opción :brief sólo muestra como se han invocado las funciones. Por ejemplo, supongamos que se ha definido la función suma de la siguiente forma:
         
          (defun suma (a b) (+ 'a b))
         
          Podemos visualizar las invocaciones de la siguiente forma:
          USER(29): (suma 1 2)
        Error: EXCL::+_2OP: `A' is not of the expected type `NUMBER'
          [condition type: TYPE-ERROR]
        [1] USER(30): :zo :brief t
        Evaluation stack:

          ERROR <-
        + <-
          SUMA <- EVAL <- TPL:TOP-LEVEL-READ-EVAL-PRINT-LOOP <-
          TPL:START-INTERACTIVE-TOP-LEVEL
         

        Si se utiliza la opción :moderate se visualiza además los argumentos con los que se llamo a las funciones, lo que nos permitirá detectar el error del ejemplo anterior al comprobar que a "+" le llega como primer argumento el símbolo A:
          [1] USER(33): :zo :moderate t
        Evaluation stack:

           (ERROR TYPE-ERROR :DATUM ...)
         ->(+ A 2)
           (SUMA 1 2)
           (EVAL (SUMA 1 2))
           (TPL:TOP-LEVEL-READ-EVAL-PRINT-LOOP)
           (TPL:START-INTERACTIVE-TOP-LEVEL
              #<BIDIRECTIONAL-TERMINAL-STREAM @ #x40126e39>
              #<Function TOP-LEVEL-READ-EVAL-PRINT-LOOP> ...)

        0.2.2.3.Evaluación y compilación de ficheros lisp
          Para evaluar todas las funciones que se encuentran en un fichero utiliza la instrucción load:

          USER(1): (load "/iaic1/salidas/practicas/pract1/practica1.lsp")
          ; Loading /iaic1/salidas/practicas/pract1/practica1.lsp
          T
           
          Una vez evaluadas las funciones de un fichero podrás utilizarlas. También puedes editar el código de una función evaluada mediante la función

          (ed '<nombre_de_la_función>).

          Una vez depurados tus programas puedes compilar los ficheros de forma que se evite el proceso de interpretación cada vez que se invoca una función. La instrucción compile-file compila un fichero, y la función load hace que las funciones compiladas en el fichero estén disponibles. Las funciones compiladas se ejecutan mucho más rápido, pero en caso de error perderás las posibilidades de depuración que ofrece el entorno si las funciones son interpretadas.
           

        0.2.2.4.Acceso a documentación
          El entorno te ofrece acceso al manual de Common Lisp. Para acceder a la documentación de una función teclea Alt-X fi:clman y a continuación escribe el nombre de la función a consultar.
           
        0.2.2.5.Preparación de ficheros a someter y fin sesión
          En algunas prácticas se te pedirá una transcripción de tu interacción con LISP. Dribble permite crear un fichero con dicha transcripción. Para ello ejecuta (dribble <especificación_de_archivo>). A partir de ese momento todo lo que sucede queda registrado en el fichero. Ejecutando de nuevo (dribble) se finaliza la grabación.

          Para salir del entorno teclea :exit. Sal de Lisp antes de salir del editor emacs. ¡SI NO LO HACES ASI, EL PROCESO DE LISP NO TERMINA!

 


 
Práctica 1
    Programación en Common Lisp

    1.1.Objetivo

       
      El objetivo de esta práctica es familiarizarse con el lenguaje Common Lisp y con el entorno de programación. Más concretamente, utilizaremos el entorno Allegro Common Lisp. El entorno de trabajo puede ejecutarse directamente desde la línea de comandos o como un subproceso del editor emacs. Emacs es un editor especialmente eficaz para el desarrollo de programas LISP, y una parte del entorno de programación del Allegro CL es la interfaz con emacs.

      Por otra parte, veremos la utilidad de utilizar funciones recursivas y funciones de alto nivel en Common Lisp. Así comprobarás sus facilidades para hacer ``mapping", transformar, filtrar, contar o encontrar elementos particulares dentro de estructuras de datos como listas y estructuras. Common Lisp cuenta con un amplio repertorio de funciones, por lo que es importante adquirir el hábito de comprobar si el lenguaje ofrece una función antes de proceder a su implementación. Utiliza la ayuda en línea (clman).

      Al finalizar la práctica deberás saber editar, ejecutar y compilar un programa Common Lisp. Además, como objetivo secundario se deben conocer las herramientas de depuración LISP. Busca información de las funciones TRACE, STEP, BREAK, DESCRIBE, DOCUMENTATION, TIME. (En el capítulo 10 del Wiston (LISP) se explican brevemente las primitivas de depuración).
       

    1.2.Parte A. Familizarización con el entorno de programación
      Antes de realizar programas en Lisp realiza la siguientes tarea con objeto de familiarizarte con el entorno de programación lisp.
       
        Tarea
    1.3.Parte B. Uso de un programa de juegos
      Evalúa el fichero /users2/IAIC1/salidas/practicas/pract1/minimax.lsp y el fichero /users2/IAIC1/salidas/practicas/pract1/connect-four.lsp, y escribe las siguientes funciones lisp:

      (dribble ``juego.trz'')
      (play)
      (dribble)

      El computador dibuja un tablero para jugar a cuatro en raya y espera un numero del 0 al 6 indicando tu movimiento. Cuando realices un movimiento aparecerá una X en el último cuadro sin ocupar de la columna que indicaste. A continuación el computador hará un movimiento y esperará tu siguiente movimiento. Gana el que antes haga 4 en raya (horizontal, vertical o diagonalmente).

      Puedes repetir el juego, esta vez sin registrar la traza y evaluando los ficheros compilados. Los ficheros compilados tienen la extensión .fasl. Si evalúas los ficheros compilados comprobarás que la ejecución es mucho más rápida.
       

    1.4.Parte C. Trabajo con Lisp
      El ejemplo que se aborda en esta parte es la organización de una pequeña base de datos para almacenar la información de libros de una Biblioteca. Para ello hemos creado una librería de funciones de utilidad que nos facilitan el tratamiento. El programa fuente practica1.lsp lo encontrarás en el directorio /users2/IAIC1/salidas/practicas/pract1.

      La base de datos contiene información acerca del título, autor, clasificación por palabras claves y año de edición de cada libro. En la versión propuesta la estructura de datos elegida para cada libro consiste en una lista con tres listas internas cuyos car respectivos son las palabras clave titulo, autor, clasificacion y agno. Para abstraer al usuario de la estructura de datos utilizada se ha definido el constructor (los parámetros identifican los valores de cada campo):

      (crea-libro titulo autor clasificacion agno)

       

      1. Piensa como se podrían poner esos parámetros identificados por claves (acuérdate de la opción &key de una lambda expresión).
        Idénticamente se han definido las funciones: (libro-titulo libro)
      (libro-autor libto)
      (libro-clasificacion libro)
       
      Se proporcionan también utilidades para recopilar información de la base de datos:
      Podrás encontrar también diversas versiones de algunas de estas funciones, así podrás comprobar diferentes implementaciones y sus distintas eficiencias.

      Tarea

(ordenar libros #'por-antiguedad)

La función debe recibir como primer parámetro una base de datos de libros y como segundo argumento una función que tome como argumentos dos elementos de la lista y devuelva T si y sólo si el primer argumento es estrictamente menor que el segundo en algún sentido. Por ejemplo la función por-antiguedad devuelve T si el año de edición del primer argumento es menor que el del segundo argumento.

Ordena los libros por antigüedad, y a continuación ordena la lista resultante colocando al principio los que tengan un mayor numero de palabras claves en la lista clasificacion.

    1.5.Información a entregar

      Prepara un fichero con las tareas pedidas en la parte C y la traza registrada para comprobar el funcionamiento correcto de todas las funciones que has definido. Deberás entregar un único archivo P1NXXIII.lsp en el directorio /users2/IAIC1/entradas/P1.

       

      El fichero debe estar preparado para ser evaluado sin dar ningún error. Para ello las partes correspondientes a trazas se colocarán entre los símbolos #| y |#. De esta forma se indica que el código comprendido entre estos símbolos no será evaluada al leer los ficheros.
       

    1.6.Listado de las funciones suministradas
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;   iaaa  iaaa  iaaa  iaaa  iaaa  iaaa  iaaa  iaaa  iaaa  iaaa   ;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;;
;;;    idea:      winston
;;;    creado:      13-2-1991
;;;    modificado : 14-4-94  muro
;;;                 20-20-95 bagnares
;;;
;;;    Departamento de Informatica e Ingenieria de Sistemas
;;;    Centro Politecnico Superior
;;;    Universidad de Zaragoza
;;;
;;;    Titulo: Libros de una Biblioteca
;;;
; Esta practica es la organizacion de una pequena base de datos para
;almacenar la informacion de libros de una biblioteca.
; Para ello hemos creado una libreria de funciones de utilidad que nos
;facilitan el tratamiento.

; La base de datos contiene informacion acerca del titulo, autor y
;clasificacion por palabras claves de cada libro.
; En la version propuesta la estructura de datos elegida para cada libro
;consiste en una lista con tres listas internas  cuyos 'car' respectivos
;son las palabras clave 'titulo', 'autor' y 'clasificacion'.
;Para abstraer al usuario de la estructura de datos utilizada se ha
;definido el constructor (los parametros identifican los valores de
;cada campo):'(crea-libro titulo autor clasificacion)'.

(defun crea-libro (titulo autor clasificacion agno)
   (LIST (LIST 'TITULO TITULO)
         (LIST 'AUTOR AUTOR)
         (LIST 'CLASIFICACION CLASIFICACION)
  (LIST 'AGNO AGNO)))

;Este constructor crea un nuevo libro

(setf libro-ejemplo
      (crea-libro '(Common Lisp)
                  '(Guy steele)
                  '(Tecnico Lisp)
    '1991))

; Para seguir con el constuctor creamos un conjunto de lectores:

(defun libro-titulo (libro)
   (second (assoc 'titulo libro)))

(defun libro-autor (libro)
   (second (assoc 'autor libro)))

(defun libro-clasificacion (libro)
  (second (assoc 'clasificacion libro)))

(defun libro-agno (libro)
   (second (assoc 'agno libro)))

;Creamos ahora un procedimiento para cambiar el autor
(defun libro-cambio-autor (libro autor)
  (if (eql 'autor (first (first libro)))
    (cons (list 'autor autor) (rest libro))
    (cons (first libro)
          (libro-cambio-autor (rest libro) autor))))

;Ahora construimos mas libros en la base de datos:
(setf bdlibros
      (list
       (crea-libro '(Common Lisp)
                   '(Guy L. Steele)
                   '(Tecnico Lisp)
                   '1991)
       (crea-libro '(Artificial Intelligence)
                   '(P. H. Winston)
                   '(Tecnico IA)
                   '1989)
       (crea-libro '(Moby Dick)
                   '(H. Melville)
                   '(Ficcion)
                   '1850)
       (crea-libro '(Tom Sawyer)
                   '(M. Twain)
                   '(Ficcion)
                   '1894)
       (crea-libro '(The Black Orchid)
                   '(Rex Stout)
                   '(Ficcion Misterio)
                   '1710)))

;FILTRADO DE DATOS
; Puede ser interesante sacar listados que contengan informacion parcial de
;la base de datos. Esto es, se requiere un filtrado.

(defun lista-autores (libros)
  (if (null libros)
    nil
    (cons (libro-autor (first libros))
          (lista-autores (rest libros)))))

; Se puede hacer un filtro que acceda a mayor profundidad:
; Primero creamos una funcion booleana que diga si un libro cumple la
;condicion, p.e. que sea de ficcion.

(defun ficcionp (libro)
  (member 'ficcion (libro-clasificacion libro)))

; Ahora la podemos usar para encontrar todos los libros de ficcion:

(defun lista-libros-de-ficcion (libros)
  (cond ((null libros) nil)
        ((ficcionp (first libros))
         (cons (first libros)
               (lista-libros-de-ficcion (rest libros))))
        (t (lista-libros-de-ficcion (rest libros)))))

;;;  TAMBIEN ES POSIBLE CONTAR Y ENCONTRAR

;(length (LISTA-LIBROS-DE-FICCION bdlibros))

; Tambien se puede acceder a uno cualquiera de la lista:
; (first (LISTA-LIBROS-DE-FICCION bdlibros))

;Pero esto es muy poco eficiente porque se necesita crear nuevas listas.
;Pero puede contarse directamente:
(defun cuenta-libros-ficcion (libros)
  (cond ((null libros) 0)
        ((ficcionp (first libros))
         (+ 1 (cuenta-libros-ficcion (rest libros))))
        (t (cuenta-libros-ficcion (rest libros)))))

(defun encuentra-primer-libro-ficcion (libros)
  (cond ((null libros) nil)
        ((ficcionp (first libros))
         (first libros))
        (t (encuentra-primer-libro-ficcion (rest libros)))))

;Observar los patrones que se repiten en las funciones anteriores.
;;; Funciones de MAPPING
;Para facilitar las cosas
;(lista-autores bdlibros)
;(mapcar #'libro-autor bdlibros)

(defun libro-autor-apellido (libro)
  (first (last (libro-autor libro))))

;(mapcar #'libro-autor-apellido bdlibros)
;(mapcar #'(lambda (libro) (first (last (libro-autor libro)))) bdlibros)

;;; Para simplificar las operaciones de filtrado: REMOVE-IF y REMOVE-IF-NOT
;(remove-if-not #'ficcionp bdlibros)  ;elimina si NO se verifica la condicion
;(remove-if #'ficcionp bdlibros)   ;elimina SI se verifica la condicion
;(count-if #'ficcionp bdlibros)   ;cuenta SI se verifica la condicion
;(find-if #'ficcionp bdlibros)   ;encuentra SI se verifica la condicion


Webmaster:José Angel Bañares (banares@posta.unizar.es)
(Última modificación: 6 de Octubre de 1998)
                        © 1998, Grupo de Sistemas de Información Avanzados e Inteligencia Artificial.