Translate

sábado, 6 de septiembre de 2014

Listas de propiedades




Las listas de propiedades son parecidas a los registros en otros lenguajes. Lo que se llama "campos" para otros lenguajes en common lisp son propiedades.

Veamos unos ejemplos:

> (setf pelota '(:color "blanco" :diametro 25 :material "cuero"))

Pelota tiene distintas propiedades como color diámetro y material. Veamos otro:

> (setf mi-moto '(:marca "Zanella" :modelo "Custom Patagonian Eagle" :cilindrada 150 :kilometros 4000 :precio 12000))
(:MARCA "Zanella" :MODELO "Custom Patagonian Eagle" :CILINDRADA 150 :KM 4000 :PRECIO 12000)


Para obtener el valor de una propiedad, usamos la función getf:

> (getf mi-moto :modelo)
"Custom Patagonian Eagle"

La función getf nos devuelve el valor de la propiedad, para ser utilizadas como argumento de otra función o almacenarse en un símbolo (de todas formas, al almacenarse termina usándose como argumento de otra función: "setf")

> (setf modelo (getf mi-moto :modelo))

En modelo queda el valor "Custom Patagonian Eagle".

Para modificar un valor de la lista de propiedades usamos "setf" con "getf" a la vez. Supongamos que queremos modificar el precio de "mi-moto":

> (setf (getf mi-moto :precio) 13000)
13000

Ok! :-P Si consultamos ahora nuestra lista de propiedades, veremos:

> mi-moto
(:MARCA "Zanella" :MODELO "Custom eagle" :CILINDRADA 150 :KM 4000 :PRECIO 13000)

Se puede guardar cada lista de propiedades en una lista, para tener una base de datos sencilla, que podría guardarse en un archivo, y tener funciones para búsqueda y filtros, etc. Las posibilidades son infinitas.

lunes, 11 de agosto de 2014

Juego de Damas

Solicito colaboradores para el proyecto, pues por razones de tiempo se ha estancado hace rato ya :-( . La interfaz usa LTK, el archivo es Damas.lisp. Interesados/as envíen mail a renealejandrocoronel@gmail.com. 

De todos modos se lo puede descargar sin necesidad de colaborar. Ya estaba haciendo las respuestas del ordenador pero habían surgido algunos problemas con eso. Para ejecutarlo simplemente escriba desde su interprete lisp preferido: 

> (load "~/Escritorio/mis-programas/ltk.lisp") 
> (load "~/Escritorio/mis-programas/damas.lisp") 
> (damas:main) 

 Obviamente que debe cambiar el directorio. Escucho sugerencias. Saludos.

sábado, 1 de marzo de 2014

La Completa Guía para Idiotas sobre Paquetes de Common Lisp

Traduje este documento que a pesar de su título, trata sobre algunos de los fundamentos de Common Lisp. El documento explica de forma sencilla y rápida lo que es fundamental conocer sobre paquetes y la utilidad que ofrecen estos. Saludos :-)

Aquí dejo la guía para descargar: paquetes.pdf

jueves, 13 de febrero de 2014

Crear Interfaces Gráficas en Lisp con LTK (sexta parte)


Generar un menú

Como este tema no lo encontré por ninguna parte. Voy a mostrar como generar menúes con LTK en Common-Lisp. La ventana principal es un "toplevel" a la que debe asociarse un menú que será el maestro.

(setf menu-maestro (make-instance 'menu)

(configure *tk* :menu menu-maestro)

Deben generarse los submenues, que son los que contienen los títulos de cada conjunto de botones de menú. Supongamos que tenemos que generar menúes para un juego:

(setf menu-juego (make-instance 'menu
                                            :text "Juego"
                                            :master menu-maestro))

(setf menu-ayuda (make-instance 'menu
                                            :text "Ayuda"
                                            :master menu-maestro))

Ya estan los titulos de cada menú, lo único que falta ahora son los botones para cada menú, vamos a generarlos:
Para el menu "Juego":

(setf boton-juego-nuevo (make-instance 'menubutton
                                           :text "Nuevo Juego"
                                           :master menu-juego
                                           :command (lambda()
                                                                 (do-msg "Aquí va una función para generar un nuevo juego"))))
                                                             
(setf boton-salir (make-intance 'menubutton
                                :text "Salir"
                                :master menu-juego
                                :command (lambda() (setf *exit-mainloop* t))))

Para el menú Ayuda:

(setf boton-about (make-instance 'menubutton
                                  :text "Acerca de...
                                  :master menu-ayuda
                                  :command (lambda() (do-msg "Mi juego - Copyleft Cachito 2014"))))


Vamos a ver el programa completo:

;-------------------------------------------------------------------------

(defpackage :mi-juego
   (:use :common-lisp :ltk)
   (:export #:main))

(in-package :mi-juego)

(defun main ()
   (setf *debug-tk* nil)
   (with-ltk ()
(let* ((menu-maestro (make-instance 'menu))
         (menu-juego (make-instance 'menu :text "Juego" :master menu-maestro))
         (menu-ayuda (make-instance 'menu :text "Ayuda" :master menu-maestro))
         (boton-juego-nuevo (make-instance 'menubutton
    :text "Juego nuevo"
    :master menu-juego
    :command (lambda ()
                                                                 (do-msg "Aquí va una función para generar un nuevo juego"))))
         (boton-salir (make-instance 'menubutton
      :text "Salir"
      :master menu-juego
      :command (lambda () (setf *exit-mainloop* t))))
 
         (boton-about (make-instance 'menubutton
      :text "Acerca de..."
      :master menu-ayuda
      :command (lambda () (do-msg "Mi juego - Copyleft Cachito 2014")))))

           (configure *tk* :menu menu-maestro)
           (wm-title *tk* "Mi juego")
           (minsize *tk* 300 200)
           (maxsize *tk* 300 200))))

;-----------------------------------------------------------------------------------

Guardamos el programa en un archivo .lisp. Cargamos el LTK y luego nuestro programa.

> (load "c:/Descargas/ltk-0.98/ltk.lisp")
> (load "c:/mi-programa.lisp")

Esta dentro del paquete mi-juego, para ejecutarlo hacemos:

> (mi-juego:main)


miércoles, 12 de febrero de 2014

Crear Interfaces Gráficas en Lisp con LTK (Quinta parte)

Antes que nada, me gustaría recordar que para utilizar LTK es necesario tener instalado tcl/tk 8.5 o superior en tu ordenador. Para Windows o Mac OS X puedes descargarlo desde http://www.activestate.com/activetcl/downloads. En las plataformas linux suele venir instalado por defecto, aunque no siempre viene con la última versión disponible, por lo tanto se puede descargar desde la dirección antes citada o bien instalarlo desde el gestor de paquetes Synaptic o desde terminal usando comandos. En Debian/Ubuntu es:

> sudo apt-get install tcl8.5

Y luego:

> sudo apt-get install tk8.5

Es fundamental descargarse el LTK - The Lisp Toolkit desde http://www.peter-herth.de/ltk/ltk-0.98.tgz. Descomprimir el archivo y cargar el ltk.lisp desde nuestro interprete lisp:

> (load "c:/Descargas/ltk-0.98/ltk.lisp")

Para probar que todo funciona correctamente, tenemos que estar dentro del paquete ltk:

> (in-package :ltk)

> (ltktest)

Y nos muestra una ventana con varios widgets funcionando.


Configurar Aspectos de la Ventana

Se puede cambiar el titulo de la ventana, su tamaño mínimo y máximo por ejemplo, todo esto a través de las funciones de administración de ventanas. Aquí muestro algunas de ellas:


Función
Descripción
(wm-title toplevel titulo) Establece el título de la ventana
(minsize toplevel ancho alto) Establece el tamaño mínimo de la ventana en píxeles
(maxsize toplevel ancho alto) Establece el tamaño máximo de la ventana en píxeles
(on-close toplevel funcion) Establece la función que será llamada al presionar el botón cerrar en la ventana
(on-focus toplevel funcion) Llama a función cuando la ventana adquiere el foco.
"toplevel" es la ventana que puede crearse como una instancia. La ventana por defecto es *tk*, de este modo, si intentas cambiar el título de la ventana puedes usar la función:

(wm-title *tk* "Este es el nuevo título")

El resto de las funciones puede verse en el documento "ltkdoc.pdf" que se encuentra en el archivo ltk-0.98.tgz

Veamos un ejemplo concreto:

;-------------------------------------------------------------------------

(load "ltk.lisp")

(use-package :ltk)

(defun ventana ()
   (with-ltk()
        (let*
             ((boton-1 (make-instance 'button
                                                    :text "Mensaje"
                                                    :command (lambda()
                                                                                (do-msg "Holaaaaaa....")))))

          (wm-title *tk* "Ventanita")
          (minsize *tk* 300 200)
          (maxsize *tk* 300 200)
          (place boton-1 50 50))))

;-------------------------------------------------------------------------------

Podemos guardarlo en un archivo "ventana.lisp" o el nombre que quieras. Lo cargas y lo ejecutas:

> (load "ventana.lisp")
> (ventana)

Nos mostrará la ventana:


domingo, 27 de octubre de 2013

Arboles con múltiples nodos

Desarrollaremos un árbol múltiple y veremos como recorrerlo y guardarlo. El ejemplo que seguiremos es una estructura en forma de árbol que vi en algún libro, alguna vez y que se utilizaba para ejemplificar un árbol de decisiones que trataba sobre si era conveniente o no ir a jugar al tenis. Le aconsejo que vaya guardando las funciones en un archivo de texto con extensión .lisp, por ejemplo: "arbol.lisp". Cada vez que modifica el archivo fuente, cargue en el interprete la nueva versión con su archivo modificado:

> (load "arbol.lisp")



El árbol de decisión establece que alguien va a jugar tenis, según el clima.
En primer lugar debemos crear una función para guardar los nodos (en un archivo de texto con extension .lisp), sería así:

(defun agregar-nodo (nodo)
  (push nodo *arbol*)) ;vamos metiendo cada nodo en *arbol*

Lo guardamos en la lista global *arbol*. Debemos declararla al principio:

(defvar *arbol* nil) ; y le asignamos a nil

Debemos generar el nodo con una función, es muy fácil:

(defun hacer-nodo(nombre &rest valores)
   (list :nombre nombre :valores valores))

"nombre" es el nombre del nodo, al que se hará referencia. En valores se guarda una lista con los valores del árbol. El primer elemento va a ser la raíz y los demás serán sus ramas. Veamos como cargamos el árbol generando los nodos, a través del interprete:

> (agregar-nodo (hacer-nodo 'clima '("clima" . nil) '("soleado" . humedad) '("nublado" . T) '("lluvioso" . viento)))
(("clima") ("soleado" . HUMEDAD) ("nublado" . T) ("lluvioso" . VIENTO))

CLIMA es el nombre del nodo. Los símbolos HUMEDAD y VIENTO son símbolos a los que daremos los valores correspondientes. En el caso de generar el primer elemento "raíz" del árbol, el primer valor de la lista de valores es la cadena "clima" que es el dato asignado al nodo principal, los valores que siguen son los nodos múltiples.
Ahora creamos el resto de los nodos:

> (agregar-nodo (hacer-nodo 'humedad '("humedad" .  nil) '("normal" . T) '("alta" . nil)))
(("humedad") ("normal" . T) ("alta"))

> (agregar-nodo (hacer-nodo 'viento '("viento" . nil) '("fuerte" . nil) '("debil" . T)))
(("viento") ("fuerte") ("debil" . T))

En este caso va el símbolo y luego los nodos múltiples.
¿Como hacemos para asignar o enlazar a HUMEDAD y VIENTO, los símbolos, con sus respectivos valores...?

Si consultamos estos símbolos veremos que aun no contienen nada y nos dará un error. Incluso debemos tener un símbolo que apunte a la raíz y a sus valores. Para eso les dimos los nombres en :NOMBRE a cada nodo. Debemos crear una función para asignar o enlazar a cada símbolo ":NOMBRE" sus respectivos valores, tanto el de la raíz como sus nodos. De esta manera cada nodo quedará en memoria "en forma de árbol", es decir el símbolo del mismo nombre, por ejemplo HUMEDAD, dentro de la lista de valores, tendrá los nodos asignados como una lista, ya que se le da el mismo nombre ":NOMBRE" cuando se crea el nodo. Parece complicado, pero es fácil, por ejemplo el símbolo VIENTO que se crea en el nodo de nombre CLIMA va a ser el mismo que el símbolo VIENTO cuando se crea el nodo del mismo nombre. Solo que aun estos símbolos no contienen valores. Entonces, creamos la función que realiza dicha asignación:

(defun transforma-nodo-x(nodo) ;Función que transforma un registro de nodo en un nodo en memoria
  (set (getf nodo :nombre) (getf nodo :valores)))

Esta función asigna a cada símbolo en :nombre su lista de valores o nodos múltiples. Ahora bien, tenemos varios nodos, y por lo tanto necesitamos cargar a memoria toda la lista de registros nodos que guardamos en *arbol*, para ello, barrilete cósmico, usamos la siguiente función:

(defun transforma-nodos() ; Función que transforma todos los nodos del árbol y los carga en memoria
  (dolist (nodo *arbol*)
    (transforma-nodo-x nodo)))

Hacemos un (transforma-nodos), y ya podemos constatar que los valores se han cargado:

> clima
(("clima") ("soleado" . HUMEDAD) ("nublado" . T) ("lluvioso" . VIENTO))

> humedad
(("humedad") ("normal" . T) ("alta"))

Debemos modificar la función agregar-registro agregándole la función para cargar un nodo, para que cada vez que creamos un registro nodo, lo cargue en memoria, es decir que le asigne a su nombre los valores correspondientes:

(defun agregar-nodo(nodo) ; Función que agrega un nodo a la lista *arbol*; y a la memoria "nodo-x"
   (push nodo *arbol*)
   (transforma-nodo-x nodo))

Para recorrer el árbol, podemos usar recursividad. Primero accedemos al dato del primer nodo con (first (car ...)), luego aplicando la función recursivamente accedemos al primer elemento de la lista con un first "la raíz", luego con otra llamada recursiva al resto, "los valores", usando un rest. Bueno, menos palabras y mas acción:

(defun mostrar (lista) ;función que muestra todos los nodos
  (when (not (null lista))
    (when (not (equal T lista))
    (format t "~%~5t~s" (car (first lista)))
    (mostrar (eval (cdr (first lista))))
    (mostrar (rest lista)))))

El argumento lista no trata sobre una señorita inteligente, mas bien trata sobre la lista de valores que contiene cada símbolo :NOMBRE, como vimos anteriormente, por ejemplo los símbolos CLIMA, HUMEDAD y VIENTO, los que ya cuentan con valores asignados.
Entonces llamamos a nuestra función para ver los nodos:

> (mostrar clima)

     "clima"
     "soleado"
     "humedad"
     "normal"
     "alta"
     "nublado"
     "lluvioso"
     "viento"
     "fuerte"
     "debil"

Ahí están todos los datos de los nodos. Te preguntarás por que se muestra el árbol sin diferenciar la raíz de sus nodos con alguna especie de dentado o tabulación, es verdad, llevaría mas programación.

¡Ah! ...
¿te habrás dado cuenta que si sales del interprete se borran los nodos cargados en *arbol* y las listas de valores que se cargaron en los símbolos...?

Entonces, antes de salir vamos a crear una función para guardar el árbol en un archivo:

(defun guardar-arbol(archivo) ;Función para guardar los registros nodos de *arbol* en un archivo de salida
  (with-open-file (salida archivo
 :direction :output
 :if-exists :supersede)
    (with-standard-io-syntax
      (print *arbol* salida))))

Pasemos a guardar el archivo con los datos cargados a través del interprete:

> (guardar-arbol "arbol.tree") ;en arbol.tree quedan los datos cargados

Ahora queda cargar esos nodos registro cuando iniciamos:

(defun cargar-arbol (archivo) ;Función para recuperar los nodos de *arbol* desde un archivo de entrada
  (with-open-file (entrada archivo)
    (with-standard-io-syntax
      (setf *arbol* (read entrada)))))

Para cargar hacemos:

> (cargar-arbol "arbol.tree")

Entonces en nuestro archivo "arbol.lisp" agregamos al final:

(cargar-arbol "arbol.tree") ;para cargar los nodos registro al inicio
(transforma-nodos) ;para asignar a los símbolos con los nombres correspondientes sus valores respectivos.
A todo esto le estaría faltando una función que recorra el árbol y haga las preguntas para decidir si voy a jugar al tenis. Así que agregamos estas funciones a nuestro archivo "arbol.lisp", que nos ayudarán a decidir:

(defun leer-mensaje(mensaje) ;función que muestra un msj en pantalla y devuelve un valor ingresado
(format *query-io* "~%~%~a: " mensaje)
(force-output *query-io*)
(read-line *query-io*))

(defun pregunta (lista) ;función que recorre el árbol preguntando al usuario
  (when (not (null lista))
    (if (not (equal T lista))
(progn
 (format t "~%¿Cual es la condición de el/la ~s?" (car (first lista)))
       (set 'opcion 1)
 (set 'valores (rest lista))
 (format t "~%")
 (dolist (valor valores)
   (format t "~%~5t~s. ~s" opcion (car valor))
   (set 'opcion (+ 1 opcion)))
 (set 'elegir (- (parse-integer (leer-mensaje "Elija una opcion: ") :junk-allowed t) 1))

 (pregunta (eval (cdr (nth elegir valores))))) ;vuelve a preguntar con recursividad segun opcion

(format t"~%Entonces voy a jugar al tenis"))))

Para hacer la pregunta obvia:

> (pregunta clima)

En este link está el archivo "arbol.lisp" que contiene las funciones descritas arriba. También otro link donde se guardaron en disco los datos del árbol: "arbol.tree".




sábado, 4 de mayo de 2013

Mas tipos de datos lisp



Listas de Propiedades


Las listas de propiedades (‘plistas’) proporcionan una vía simple pero poderosa de manejar pares de palabras-clave/valores. Una plista es simplemente una lista, con un número par de elementos, donde cada pareja de elementos representa un valor con nombre. Este es un ejemplo de plista:

   > '(:nombre "Gloria" :domicilio "Calle Urquiza 1222, Formosa" :celular "111-111111" :edad 35)

En esta plista, las claves son símbolos de palabras clave, y los valores son cadenas. Las claves en una plista son con mucha frecuencia símbolos de palabras clave. Los símbolos de palabras clave son símbolos cuyos nombres van precedidos por un signo de dos puntos (‘:’), y que son usadas generalmente justo para comparar el símbolo en sí (típicamente no se usan por su valor del símbolo o función del símbolo). Para acceder a los miembros de una plista, CL proporciona la función getf, que toma una plista y una clave:

   > (getf '(:nombre "Gloria" :domicilio "Calle Urquiza 1222, Formosa" :celular "111-111111" :edad 35) :nombre)
   "Gloria"

Es mas cómodo guardar la lista de propiedad en una variable:

   > (set 'registro '(:nombre "Gloria" :domicilio "Calle Urquiza 1222, Formosa" :celular "111-111111" :edad 35))

   > (getf registro :edad)
   35


 

 

 

 

 

 

 

 





 "Oh, Lisp me vuelve tan loca..." (Ai Sayama)

 

 

 

Arrays (Arreglos)


Los arreglos son estructuras de datos que pueden albergar "rejillas" simples (o multi-dimensionales) llenas de valores, que son indexadas por uno o más enteros. Los arreglos soportan un acceso muy rápido a los datos basado en índices de enteros. Se crean arreglos empleando la función constructora make-array y los elementos del arreglo se refieren usando la función de acceso aref. Se pueden establecer elementos individuales en el arreglo usando setf:

     > (set 'array (make-array (list 3 3)))
     #2A((NIL NIL NIL) (NIL NIL NIL) (NIL NIL NIL))
    
     > (setf (aref array 0 0) "David")
     "David"
    
     > (setf (aref array 0 1) "José")
     "José"
    
     > (aref array 0 0)
     "David"
    
     > (aref array 0 1)
     "José"

Observe que luego de la función va el argumento requerido de las dimensiones del array, que debe ser una lista. En el ejemplo anterior se trata de un array de 3 * 3. La función make-array también admite varios argumentos de palabras clave optativos para inicializar el arreglo en el momento de su creación. Uno de ellos es :initial-contents que permite establecer los valores iniciales:

    > (make-array '(4 2 3)
            :initial-contents
            '(((a b c) (1 2 3))
              ((d e f) (3 1 2))
              ((g h i) (2 3 1))
              ((j k l) (0 0 0))))
    #3A(((A B C) (1 2 3)) ((D E F) (3 1 2)) ((G H I) (2 3 1)) ((J K L) (0 0 0)))

Se trata de un array de tres dimensiones: 4 * 2 * 3.
Observe la respuesta del intérprete al crear el array: Comienza con las dimensiones #3A (A es el tipo del array) y luego los elementos como listas anidadas. De esta manera podríamos definir al array sin usar la función make-array:

   > (set 'nuevo-array #3A(((a b c) (1 2 3)) ((d e f) (3 1 2)) ((g h i) (2 3 1)) ((j k l) (0 0 0))))

   > (aref nuevo-array 2 0 1)
   H

   > (setf (aref nuevo-array 3 1 1) "sapo pepe")
   "sapo pepe"

   > nuevo-array
   #3A(((A B C) (1 2 3)) ((D E F) (3 1 2)) ((G H I) (2 3 1)) ((J K L) (0 "sapo pepe" 0)))

Tablas Hash


Una tabla hash es similar a lo que en otros lenguajes es conocido como arreglo asociativo. Es comparable a un array de una dimensión que admite claves que son cualquier objeto CL arbitrario, que serán comparados usando eql. Las tablas hash soportan un rastreo de  datos de tiempo constante, lo que significa que el tiempo que se toma en buscar un elemento en la tabla hash permanecerá estable, incluso si el número de entradas en la tabla se incrementa.
Para trabajar con tablas hash, CL proporciona la función constructora make-hash-table y la función de acceso gethash. Para establecer entradas en una tabla hash se usa el operador setf en conjunción con la función de acceso gethash. setf es una generalización de setq que puede usarse en sitios resultantes de una llamada a función, además de exclusivamente con símbolos como con setq. Este es un ejemplo de creación de una tabla hash, que pone un valor en ella y que recupera un valor de ella:

   > (setq so-clasif (make-hash-table))
   #S(HASH-TABLE :TEST FASTHASH-EQL)

   > (setf (gethash :windows so-clasif) "sin comentarios")
     "sin comentarios"
    
   > (gethash :windows so-clasif)
     "sin comentarios" ;
     T


Observe que la función gethash devuelve dos valores. El primer valor de retorno es el valor correspondiente a la entrada encontrada en la tabla hash, si existiera. El segundo valor de retorno es un valor booleano (T o NIL), que indica si el valor se se encontró de hecho en la tabla hash. Si el valor no se encuentra, el primer valor de retorno será NIL y el segundo será también NIL. Este segundo valor es necesario para distinguir los casos en los que la entrada sí se encuentra en la tabla hash, pero sucede que su valor es precisamente NIL. Existen varias vías para acceder a múltiples valores de retorno de una función, empleando multiple-value-bind y multiple-value-list.

En proximas entregas veremos como trabajar con tablas hash, eliminar registros, guardarla en un archivo, etc.
Mi mujer me dijo que si no llevo dinero a casa, me echa y que va a salir con el carnicero de la esquina. :-(