P1: LISP

Anuncio
PRACTICA 1. PROGRAMACIÓN EN LISP
Fecha de publicación: 29 de septiembre de 2011.
Duración: 3 sesiones de laboratorio.
Versión: 2011/09/29
Forma de entrega: Según lo dispuesto en las normas de entrega generales, accesibles en la página
http://arantxa.ii.uam.es/~ia/practicas/iap.htm
• El código debe estar en un único fichero.
• La evaluación del código en el fichero no debe dar errores (recuerda CTRL+A CTRL+E debe evaluar
todo el código sin errores).
Material recomendado:
• En cuanto a estilo de programación LISP:
• http://www.cs.cmu.edu/Groups/AI/html/faqs/lang/lisp/part1/faq.html
• Como referencia de funciones LISP: http://www.lispworks.com/documentation/commonlisp.html
Ayuda:
• Se recomienda leer atentamente el documento Errores más frecuentes. Se penalizarán
especialmente los errores que se cometan en la práctica aquí recogidos.
• En las prácticas usaremos la versión w IDE, ANSI ya que incluye el entorno de desarrollo con
editor de textos y, por ser ANSI, no es sensible a mayúsculas o minúsculas.
• No utilizaremos las ventanas "Form" e "Inspect"
• Te será útil familiarizarte con las herramientas de histórico de evaluaciones que incluye la
ventana "Debug Window".
• Si te pones en una línea que ya evaluaste en la "Debug Window" y pulsas Enter, se copiará
automáticamente en la línea de prompt actual.
• Usa Ctrl+E para evaluar la línea sobre la que está el cursor en una ventana de edición.
• Usa Ctrl+. para autocompletar la función que estás escribiendo.
• Con Ctrl+Shift+^; (también en el menú "Edit") puedes poner en comentario (o descomentar) la
fracción de código seleccionada en el editor.
• Con Ctrl+Shift+p, puedes tabular automáticamente el código seleccionado en formato estándar.
• Si escribes una función que esté definida (aunque sea tuya) y añades un espacio, en la esquina
inferior izquierda de la ventana principal (en la que están Nuevo, Guardar, Cargar,...) podrás ver
el prototipo y los parámetros que recibe.
• Si seleccionas un nombre de función y pulsas F1 se abrirá automáticamente la ayuda del entorno
acerca de esa función.
• Si vas al menú "Run" verás que puedes trazar o poner un breakpoint en una función cuyo nombre
tienes seleccionado. La traza la escribe el entorno en la "Debug Window" mientras que los
breakpoint lanzan una ventana de mensaje y paran la evaluación momentáneamente.
OBJETIVO: El objetivo de esta práctica es la resolución de problemas sencillos mediante el lenguaje
de programación LISP. Aparte de la corrección de los programas se valorará la utilización de un enfoque
de programación funcional.
NOTA: Las funciones que se muestren por primera vez en el enunciado aparecerán en negrita.
Asegúrate de parar ante cada una de ellas y repasar su funcionamiento en detalle mediante el enlace del
manual de referencia de funciones LISP (indicado arriba).
1
REGLAS DE ESTILO LISP
Codifica utilizando estas reglas. Si tu código no las sigue, la valoración podría ser inferior a la máxima.
Adaptado de [http://www.cs.cmu.edu/Groups/AI/html/faqs/lang/lisp/part1/faq-doc-4.html]
•
•
•
No escribas código C en LISP.
Funciones
o Escribe funciones cortas que realicen una única operación bien definida.
o Utiliza nombres de funciones y variables que sean descriptivos. Es decir, que ilustren para qué
son utilizadas y sirvan así de documentación.
Formato
o Utiliza adecuadamente la sangría.
o No escribas líneas de más de 80 caracteres.
o Utiliza los espacios en blanco para separar segmentos de código, pero no abuses de ellos
ERRÓNEO 1:
(defun foo(x y)(let((z(+ x y 10)))(* z z)))
ERRÓNEO 2:
(defun foo ( x y )
(let ( ( z (+ x y 10) ) )
( * z z )
)
)
CORRECTO:
(defun foo (x y)
(let ((z (+ x y 10)))
(* z z)))
• Recomendaciones para codificar
o No utilices EVAL.
o Utiliza APPLY y FUNCALL sólo cuando sea necesario.
o Utiliza recursión.
o Aprende a usar funciones propias de LISP:
ƒ MAPCAR y MAPCAN (operaciones en paralelo)
ƒ REDUCE (recursión sobre los elementos de una lista)
o No utilices (en este curso) operaciones destructivas: NCONC, NREVERSE, DELETE. Usa
las alternativas no destructivas: APPEND, REVERSE, REMOVE. MAPCAN y la ordenación
SORT (destructiva) puede ser utilizada con precaución.
o No utilices funciones del tipo C{A,D}R con más de dos letras entre la C y la R. Son muy
difíciles de leer.
o Cuando se trata de una lista, es preferible utilizar FIRST/REST/SECOND/THIRD … en
lugar de CAR/CDR/CADR/CADDR …
• Condicionales
o Utiliza WHEN y UNLESS cuando la decisión tenga sólo una rama. En concreto WHEN y
UNLESS se deben utilizar en lugar de un IF con 2 argumentos o un IF con 3 argumentos con
algún argumento NIL o T.
o Utiliza IF cuando la decisión tenga 2 ramas.
o Utiliza COND cuando la decisión tenga una o más ramas.
o Utiliza COND en lugar de IF y PROGN. En general, no utilices PROGN si se puede escribir el
código de otra forma.
MAL:
(IF (FOO X)
(PROGN (PRINT "hi there") 23)
34)
o Utiliza COND en lugar de un conjunto de IFs anidados.
• Expresiones Booleanas
2
En caso de que debas escribir una expresión Booleana, utiliza operadores Booleanos: AND,
OR, NOT, etc.
o Evita utilizar condicionales para escribir expresiones Booleanas.
Constantes y variables
o Los nombres de variables globales deben estar en mayúsculas entre asteriscos.
Ejemplo: (DEFVAR *GLOBAL-VARIABLE*)
o Los nombres de constantes globales deben estar entre (+)
Ejemplo: (DEFCONSTANT +DOS+ 2)
En caso de que haya varias alternativas, utiliza la más específica.
o No utilices EQUAL si EQL o EQ es suficiente.
o No utilices LET* si es suficiente con LET.
o Funciones
ƒ Si una función es un predicado, debe evaluar a T/NIL.
ƒ Si una función no es un predicado, debe evaluar a un valor útil.
ƒ Si una función no debe devolver ningún valor, por convención evaluará a T.
o No utilices DO en los casos en los que se puede utilizar DOTIMES o DOLIST.
Comenta el código.
o Usa ;;; para explicaciones generales sobre bloques de código.
o Usa ;; para explicaciones en línea aparte antes de una línea de código.
o Usa
; para explicaciones en la misma línea de código.
Evita el uso de APPLY para aplanar listas.
o
•
•
•
•
(apply #'append list-of-lists)
; Erróneo
Utiliza REDUCE o MAPCAN en su lugar
(reduce #'append list-of-lists :from-end t)
(mapcan #'copy-list list-of-lists)
; Correcto
; Preferible
Ten especial cuidado con llamadas del tipo
(apply f (mapcar ..))
ERRORES DE CODIFICACIÓN a EVITAR
1. Contemplad siempre el caso NIL como posible argumento.
2. Las funciones que codifiquéis pueden fallar, pero en ningún caso deben entrar en recursiones
infinitas.
3. Cuando las funciones de LISP fallan, el intérprete proporciona un diagnóstico que debe ser leído
e interpretado.
Ejemplo:
>>
(rest 'a)
Error: Attempt to take the cdr of A which is not listp.
[condition type: TYPE-ERROR]
4. Haced una descomposición funcional adecuada. Puede que se necesiten funciones adicionales a
las del enunciado.
5. Diseñad casos de prueba completos, no sólo los del enunciado. Describidlos antes de codificar.
6. Si ya existen, utilizad las funciones de LISP, en lugar de codificar las vuestras propias (a menos
que se indique lo contrario).
7. Programad utilizando el sentido común (no intentando adivinar qué quiere el profesor). Preguntad
si hay algo en el diseño de una función que no entendéis o que resulta chocante.
3
1ª Semana: LISP I, EJEMPLOS
En esta primera semana vamos a realizar la evaluación del código que se facilita en cada apartado
y debemos analizar, contrastar y explicar el resultado obtenido en cada caso.
EVALUACION: En los ejercicios de la primera semana sólo se entrega memoria de los ejercicios
en los que se indique [entregar]. Debéis realizar todos y resolver vuestras dudas. Es importante que no
paséis a los ejercicios de la segunda semana sin entender realmente los conceptos básicos de esta
primera. Podréis ser evaluados sobre ejercicios de esta parte aunque no se entreguen ni puntúen.
APARTADO 1.0: ESTRUCTURA DE LOS DATOS LISTA EN LISP
La estructura de datos utilizada para almacenar tanto datos como el propio código fuente en LISP es la
lista (también se pueden guardar datos de otras formas, como arrays o tablas hash, pero no van a ser
utilizados para estas prácticas). Se trata de listas enlazadas. Cada nodo de la lista es una celda cons. Una
celda cons está formada por dos 2 punteros. Uno de ellos, comúnmente llamado car (Contents of
Address Register) apunta al dato que tenemos en esa posición de la lista. El otro, llamado cdr (Contents
of Decrement Register), apunta al siguiente elemento de la lista (nil en caso de que la lista haya
terminado).
A continuación puedes ver la estructura que tiene en memoria la lista (a (b (c d e) f) g).
Evalúa las siguientes líneas en el intérprete de LISP (una por una).
milista
(setf milista nil)
milista
(setf milista '(- 6 2))
milista
(setf milista (- 6 2))
milista
(setf milista '(8 (9 10) 11))
(car milista)
(first milista)
4
(cdr milista)
(rest milista)
(car (cdr milista))
milista
(length milista)
(setf milista (cons 4 '(3 2)))
milista
(setf milista (list '+ 4 3 2))
milista
(setf milista (append '(4 3) '(2 1)))
milista
(reverse milista)
milista
RECUERDA: Debes evaluar el código anterior y poder explicar en detalle qué está pasando.
APARTADO 1.1: PROGRAMACIÓN FUNCIONAL
LISP es un lenguaje orientado hacia la programación funcional, en contraste con la programación
procedural
a
la
que
estamos
acostumbrados
en
otros
lenguajes
como
C.
La base de la programación funcional es el uso del retorno que dan las funciones como parámetro de una
función superior. Antes de evaluarse una función se evalúan todos sus parámetros de entrada de
izquierda a derecha, los cuales pueden ser a su vez otras evaluaciones de funciones.
Las evaluaciones de funciones LISP deben devolver un único valor sin producir modificaciones en
los parámetros recibidos. Cuando una función modifica los parámetros que recibe se dice que es una
función destructiva, por lo cual debe ser utilizada con cuidado (en este curso no se permite definir
funciones destructivas).
En la segunda de las siguientes líneas de código LISP se evalúan una serie de funciones anidadas
produciendo un resultado final en el que han sido necesarias determinadas transformaciones de una lista
inicial sin que haya por ello ninguna modificación en la misma. Nótese que el programador no ha
necesitado la creación de variables auxiliares para almacenar los valores intermedios.
(setf milista '(1 2 3 4 5 6 7 8 9 10))
(list (length milista)
(apply #'+ (cons 100 milista))
(mapcar #'(lambda (x y) (list x y))
milista (reverse milista)))
(print milista)
RECUERDA: Evalúa en el entorno el código anterior. Observar el resultado y traza detalladamente el
orden de evaluación que utiliza el intérprete LISP para obtener el resultado final.
5
APARTADO 1.2: EVALUACIÓN DE PARÁMETROS DE FUNCIÓN
1.2.1) (setf a (+ 2 3))
(eval a)
1.2.2) (setf b '(+ 2 3))
(eval b)
1.2.3) (setf c (quote (+ 2 3)))
(eval c)
1.2.4) (setf d (list 'quote '(+ 2 3)))
(eval d)
(eval (eval d))
1.2.5) (setf d (list 'quote (+ 2 3)))
(eval d)
(eval (eval d))
IMPORTANTE: Evalúa el código anterior y explica qué está pasando en cada caso. La clave está en
entender bien el funcionamiento de las nuevas funciones.
APARTADO 1.3: EFECTOS INDIRECTOS
1.3.1)(setf *lista-amigos* '(jaime maria julio))
(setf *lista-amigos-masculinos* (remove 'maria *lista-amigos*))
(print *lista-amigos*)
(setf *lista-amigos-masculinos* (delete 'maria *lista-amigos*))
(print *lista-amigos*)
1.3.2) (setf *dias-libres* '(domingo))
(cons 'sabado *dias-libres*)
(print *dias-libres*)
(push 'sabado *dias-libres*)
(print *dias-libres*)
(setf *dias-libres* (cons 'viernes *dias-libres*))
(print *dias-libres*)
IMPORTANTE: Evalúa el código anterior y explica qué está pasando. Centra tu atención en la
destructividad o no de cada conjunto de instrucciones
6
APARTADO 1.4: LISTAS OBTENIDAS POR MODIFICACIÓN DE OTRAS LISTAS
1.4.1) (defun multiplica-2 (lista)
(mapcar #'(lambda (x) (* 2 x)) lista))
1.4.2) (defun sublista-superiores-1 (valor lista)
(remove nil (mapcar #'(lambda (elem)
(if (> elem valor) elem))
lista)))
1.4.3)(defun sublista-superiores-2 (valor lista)
(mapcan #'(lambda (elem)
(if (> elem valor) (list elem)))
lista))
MUY IMPORTANTE: Aunque se pueda obtener el mismo resultado por medio de la programación
con enfoque funcional y sin él, no se aceptará programación basada en manipular el “estado” (i.e. los
valores de las variables en la memoria) (es decir traducido directamente de C). No se permite utilizar
bucles (DO, DOLIST, DOTIMES, LOOP, etc). Se debe programar utilizando un estilo funcional
(recursión, procesamiento en paralelo con mapcar). Debemos seguir esta norma de programación
durante TODAS las prácticas de la asignatura, a menos que se indique expresamente lo contrario.
1.4.3) [Implementación NO FUNCIONAL - No utilizar]
(defun sublista-superiores-3 (valor lista)
(let ((retorno nil))
(dolist (elem lista retorno)
(if (> elem valor)
(setf retorno (cons elem retorno))))))
1.4.4) [Implementación FUNCIONAL]
(defun sublista-superiores-4 (valor lista)
(cond
((null lista)
lista)
((> (car lista) valor)
(cons (car lista)
(sublista-superiores-4 valor (cdr lista))))
( T (sublista-superiores-4 valor (cdr lista)))))
1.4.5) (setf *lista-numeros* '(1 2 3 4 5 6 7 8 9 10))
(setf *lista-numeros-pares-superiores-5*
(sublista-superiores-2 5 (multiplica-2 *lista-numeros*)))
(print *lista-numeros*)
IMPORTANTE: Analiza la implementación de las funciones anteriores. ¿Qué ventajas e inconvenientes
tiene cada una? Para el caso del apartado 4.5, analiza también las ventajas de la programación funcional
respecto a una hipotética versión en código C o Java. La clave está en distinguir las distintas
implementaciones y analizar las características y el funcionamiento de cada una.
APARTADO 1.5: VISIBILIDAD DE VARIABLES EN FUNCIONES
1.5.1)
(defun suma1a ()
(setf a (+ a 1)))
(setf a 3)
(suma1a)
(print a)
7
1.5.2)
(defun suma1 (numero)
(setf numero (+ 1 numero)))
(suma1 a)
(print a)
(print (boundp 'numero))
1.5.3)
(defun suma2 (numero)
(setf resultado (+ 2 numero)))
(suma2 a)
(print a)
(print resultado)
MUY IMPORTANTE: no utilizar nunca SETF dentro de una función o dentro de un mapcar. En
caso de que sea necesario evitar repetir código definid un cierre léxico con un let
1.5.4) [Implementación NO FUNCIONAL y mal uso de SETF - No utilizar]
(defun cuenta-elementos-1 (lista)
(let ((contador 0))
(dolist (elem lista contador)
(setf contador (+ 1 contador)))))
(cuenta-elementos-1 '(1 2 3 4))
(print (boundp 'contador))
IMPORTANTE: Evalúa el código anterior y explica qué está pasando. Haz un examen crítico de la
implementación enfocado en la visibilidad de las variables.
1.5.5) Implementación con mapcar
(defun cuenta-elementos-2 (lst)
(apply #'+ (mapcar #'(lambda(x) 1) lst)))
(cuenta-elementos-2 '(1 2 3 4))
1.5.6) Implementación recursiva
(defun cuenta-elementos-3 (lst)
(if (null lst) 0 (+ 1 (cuenta-elementos-3 (rest lst)))))
(cuenta-elementos-3 '(1 2 3 4))
NOTA: Utiliza la instrucción (trace <fn>) para ver la secuencia de evaluaciones del intérprete de
LISP. Para dejar de ver dicha secuencia, utiliza (untrace <fn>)
APARTADO 1.6: ACCESO Y MODIFICACIÓN DE LOS ELEMENTOS DE UNA LISTA
1.6.1) (setf lista '(1 2 3 4 5 6 7))
(defun elimina-segundo (lista)
(cond
((and (consp lista) (> (length lista) 1))
(setf (cdr lista) (cddr lista)))))
(elimina-segundo lista)
(print lista)
1.6.2) (defun modifica-elemento (lista posicion nuevo-valor)
(cond
8
((and (consp lista) (> (length lista) posicion))
(setf (nth posicion lista) nuevo-valor))))
(modifica-elemento lista 2 300)
(print lista)
1.6.3) (defun elimina-primero (lista)
(cond
((consp lista)
(setf lista (cdr lista)))))
(elimina-primero lista)
(print lista)
1. 6.4) (setf lista (cdr lista))
(print lista)
IMPORTANTE: Evalúa el código anterior y explica qué está pasando. Haz un análisis crítico de la
implementación. Comenta las ventajas e inconvenientes que puede tener la programación destructiva.
APARTADO 1.7: CREACIÓN DE LISTAS
1.7.1)
(defun creaLista1 (numElems incremento)
(let (retorno)
(dotimes (i numElems retorno)
(setf retorno (append retorno (list (* i incremento)))))))
1.7.2)
(defun creaLista2 (numElems incremento)
(let (retorno)
(dotimes (i numElems (reverse retorno))
(setf retorno (cons (* i incremento) retorno)))))
1.7.3)
(defun creaLista3 (numElems incremento &optional (desde 0))
(unless (= 0 numElems)
(cons desde (creaLista3 (- numElems 1)
incremento
(+ incremento desde)))))
(time (creaLista1 1000 5))
(time (creaLista2 1000 5))
(time (creaLista3 1000 5))
IMPORTANTE: Evalúa el código anterior y explica qué está pasando. Haz un examen crítico de la
implementación. ¿Qué versión es más eficiente? ¿Por qué?
APARTADO 1.8: RECORRIDO DE UNA LISTA
RECORDATORIO: incf no es una función sino una macro y modifica su argumento. Una macro no es
más que una regla de reescritura, a modo de un #define de "C". El interprete cuando ve (incf ref
delta) automáticamente lo interpreta como (setf ref (+ ref delta)).
Se proponen las siguientes funciones para contar los elementos de una lista:
1.8.1) (defun f1 (lista)
(let ((contador 0) (numElems (length lista)))
(dotimes (i numElems contador)
9
(incf contador i))))
(f1 '(1 2 3 4))
(f1 '(1 2 3 7))
1.8.2)
(defun f2 (lista)
(let ((contador 0) (num-elems (length lista)))
(dotimes (i num-elems contador)
(incf contador (nth i lista)))))
1.8.3)
(defun f3 (lista)
(let ((contador 0))
(dolist (elem lista contador)
(incf contador elem))))
MUY IMPORTANTE: MAPCAR es una función que realiza una transformación en paralelo sobre
cada uno de los elementos de la lista o listas que toma como argumentos. No se debe usar como método
de procesamiento secuencial.
1.8.4) [INCF dentro de MAPCAR - No utilizar. ERROR GRAVE]
(defun f4 (lista)
(let ((contador 0))
(mapcar #'(lambda (x) (incf contador x)) lista)
contador))
1.8.5)
(defun f5 (lista)
(cond
((null lista) 0)
( T (+ (car lista) (f5 (cdr lista))))))
1.8.6)
(defun f6 (lista)
(apply #'+ lista))
1.8.7)
(defun f7 (lista)
(eval (cons '+ lista)))
Ahora vamos a ver tres versiones para crear una lista con los números pares de otra lista de números.
1.8.8) versión RECURSIVA
(defun f9 (lista)
(cond
((null lista) nil)
(t (if (evenp (car lista)) (cons (car lista) (f9 (cdr lista)))
(f9 (cdr lista))))))
1.8.9) versión REMOVE NIL
(defun f8 (lista)
(remove nil (mapcar #'(lambda (x) (if (evenp x) x)) lista)))
1.8.10) versión con MAPCAN [PREFERIBLE]
(defun f10 (lista)
(mapcan #'(lambda (x) (if (evenp x) (list x))) lista))
IMPORTANTE: Evalúa el código anterior y explica qué está pasando. Haz un análisis crítico de las
distintas implementaciones. En las 5 primeras funciones recorremos la lista para sumar sus elementos.
¿Cuál es la más eficiente? En la 6ª y 7ª aplicamos la función + a la lista. ¿Cómo crees que suma los
10
elementos esta función? Las tres últimas versiones nos proporcionan el mismo resultado para la misma
lista de números de entrada. Analiza el enfoque de cada una teniendo en cuenta sus ventajas y
desventajas. Para todas ellas, utiliza las líneas de código que te proponemos más adelante para hacer las
pruebas. Puedes hacer experimentos con distintas longitudes de lista para fundar mejor tu análisis.
Nota: después de un stack overflow es recomendable cerrar el entorno y volverlo a abrir.
Nota: compile crea la versión compilada de una función.
(setf milista (crealista2 10000 5))
(time
(time
(time
(time
(time
(time
(f2
(f3
(f4
(f5
(f6
(f7
milista))
milista))
milista))
milista))
milista))
milista))
(compile 'f3)
(time (f3 milista))
(setf
(time
(time
(time
milista (crealista2 1000 1))
(f8 milista))
(f9 milista))
(f10 milista))
APARTADO 1.9 [entregar, 3 puntos]:
Consideremos las siguientes implementaciones de funciones que comprueban si un determinado objeto
pertenece a una lista
(defun our-member-equal-1 (obj lst)
(unless (null lst)
(or (equal (first lst) obj)
(our-member-equal-1 obj (cdr lst)))))
(defun our-member-equal-2 (obj lst)
(unless (null lst)
(eval (cons 'or (mapcar #'(lambda (x) (equal obj x))
lst)))))
9.1 Escribe el pseudocódigo para la función our-member-equal-1.
9.2 Explica por qué la implementación realizada en our-member-equal-1 es preferible a la
implementación
our-member-equal-2.
9.3 Considera la función
(defun our-member-equal-3 (obj lst)
(or (equal (first lst) obj)
(our-member-equal-3 obj (rest lst))))
Compara las evaluaciones
>> (our-member-equal-3 NIL NIL)
>> (our-member-equal-3 NIL '(NIL))
con
>> (our-member-equal-1 NIL NIL)
>> (our-member-equal-1 NIL '(NIL))
¿Por qué es incorrecta la implementación our-member-equal-3?
11
APARTADO 1.10 [entregar, 3 puntos]: INVERTIR EL ORDEN DE LOS ELEMENTOS DE UNA
LISTA
[Adaptado de “On LISP”, P. Graham, disponible en la red http://www.paulgraham.com/onlisp.html]
Compara la función:
(defun bad-reverse (lst)
(let* ((len (length lst))
(ilimit (truncate (/ len 2))))
(do ((i 0 (1+ i))
(j (1- len) (1- j)))
((>= i ilimit))
(rotatef (nth i lst) (nth j lst)))))
y la función
(defun good-reverse (lst)
(good-reverse-aux lst nil))
(defun good-reverse-aux (lst acc)
(if (null lst)
acc
(good-reverse-aux (cdr lst)
(cons (car lst) acc))))
¿Cuál es la respuesta del intérprete y cuáles son los efectos de las siguientes evaluaciones?
>> (setf lst '(1 2 3 4))
>> (bad-reverse lst)
>> lst
>> (setf lst '(1 2 3 4))
>> (good-reverse lst)
>> lst
Utilizando esta información, enumera las razones (hay varias) por las que good-reverse es correcta y
bad-reverse no es correcta desde el punto de vista de la programación funcional.
APARTADO 1.11 [entregar, 4 puntos] : BÚSQUEDA EN ANCHURA
Esta es la implementación del algoritmo de búsqueda en anchura implementada en
“ANSI Common Lisp”, Paul Graham (Fig. 3.12, pg. 52) [http://www.paulgraham.com/acl.html]
Grafo: Lista cuyos elementos son de la forma (<nodo> <nodos-adyacentes>)
Los nodos se identifican por el símbolo correspondiente a su etiqueta.
12
Ejemplo:
((a c) (b d) (c b d) (d))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; Breadth-first-search in graphs
(defun bfs (end queue net)
(if (null queue)
nil
(let ((path (car queue)))
(let ((node (car path)))
(if (eql node end)
(reverse path)
(bfs end
(append (cdr queue)
(new-paths path node net))
net))))))
(defun new-paths (path node net)
(mapcar #'(lambda(n)
(cons n path))
(cdr (assoc node net))))
;;;
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(i)
Describe el algoritmo de búsqueda en anchura e ilústralo resolviendo a mano algunos ejemplos
ilustrativos de búsqueda en grafos.
a. Casos especiales.
b. Caso típico: grafo ejemplo.
c. Caso típico distinto del grafo ejemplo anterior.
(ii)
Explica por qué esta función LISP resuelve el problema de encontrar el camino más corto entre
dos nodos del grafo.
(defun shortest-path (start end net)
(bfs end (list (list start)) net))
(iii)
(iv)
(v)
Escribe pseudocódigo para implementar el algoritmo de búsqueda en anchura.
Comenta el código propuesto por Graham de forma que se ilustre cómo se ha traducido el
pseudocódigo propuesto en (iii).
Ilustra el funcionamiento del código con los ejemplos que has resuelto a mano en (i). En
concreto, especifica la secuencia de llamadas a las que da lugar la evaluación
(shortest-path 'a 'd '((a c) (b d) (c b d) (d)))
13
2ª semana: LISP II, EJERCICIOS
Evaluación: Para evaluar el trabajo de la segunda semana se tendrá en cuenta el correcto
funcionamiento del código entregado, el estilo de programación, y el contenido de la memoria.
• No se puede utilizar setf o similares.
• Ninguna función debe ser destructiva.
• No se puede utilizar iteración directa (dolist, do, dotimes...).
• Utiliza mapcar, mapcan o recursión.
• No utilices mapcar en caso de que no sea estrictamente necesario recorrer toda la lista.
• Reutiliza código.
2.1 Secuencia de Fibonacci [1.5 puntos]
Los números de Fibonacci se generan mediante la recursión
F(0) = 0
F(1) = 1
F(n)=F(n-1)+F(n-2) si n > 1
2.1.1 Implementa de forma recursiva una función para generar los números de Fibonacci.
Prototipo: (defun numero-fibonacci(n)
...)
Ejemplo:
>> (numero-fibonacci 8)
21
2.1.2 Implementa de forma recursiva una función para generar la sucesión de n números de Fibonacci.
Por razones de eficiencia, esta función NO DEBE UTILIZAR la anterior
•
•
•
•
•
•
(secuencia-fibonacci
(secuencia-fibonacci
(secuencia-fibonacci
(secuencia-fibonacci
(secuencia-fibonacci
(secuencia-fibonacci
0)
1)
2)
3)
4)
9)
evalúa
evalúa
evalúa
evalúa
evalúa
evalúa
a
a
a
a
a
a
()
(0)
(0 1)
(0 1 1)
(0 1 1 2)
(0 1 1 2 3 5 8 13 21)
Prototipo: (defun secuencia-fibonacci(n)
...)
Ejemplo:
>> (secuencia-fibonacci 9)
(0 1 1 2 3 5 8 13 21)
2.2 Desviación estándar [1.5 puntos]
Implementa (1) de forma recursiva y (2) con mapcar sendas funciones que calcule la desviación estándar
de un conjunto de N valores reales almacenados en una lista
( x1 x2 ... xN );
<x>=
1
N
N
∑ xn ;
n =1
stdev =
1
N
N
∑ (x − < x >)
n =1
2
n
14
Nota: Puede que sea necesario definir funciones auxiliares para realizar el cálculo.
Prototipos: (defun stdev-mapcar (lst)...)
(defun stdev-recursive (lst)...)
2.3 recursive-inverse-bubble-sort [1 punto]
Considere el siguiente código:
(defun coloca-primero-lst (lst)
(unless (null lst)
(let ((primero (first lst))
(resto1 (coloca-primero-lst (rest lst))))
(if (null resto1)
(list primero)
(let ((segundo (first resto1))
(resto2 (rest resto1)))
(if (< primero segundo)
(cons primero (cons segundo resto2))
(cons segundo (cons primero resto2))))))))
que coloca el elemento de menor tamaño en la primera posición de la lista.
Se debe implementar de forma recursiva la función recursive-inverse-bubble-sort, utilizando la
función coloca-primero-lst. Observa que el primer elemento de (coloca-primero-lst lst) está
en la posición correcta. Sólo queda ordenar el resto.
Prototipo:
(defun recursive-inverse-bubble-sort (lista) ...)
Ejemplo:
> (recursive-inverse-bubble-sort '(3 1 5 2 4))
(1 2 3 4 5)
2.4 Relaciones [2 puntos]
IMPORTANTE:
• El código de este apartado será de utilidad en sucesivos ejercicios.
• Considera siempre el caso de que las listas puedan ser vacías.
2.4.1 Combinar un elemento con una lista: Definir una función que combine un elemento dado con
todos los elementos de una lista
Prototipo: (defun combina-elt-lst (elt lst) ...)
Ejemplo:
>> (combina-elt-lst 'a '(1 2 3))
((a 1) (a 2) (a 3))
2.4.2 Producto cartesiano de dos listas: Diseñar una función que calcule el producto cartesiano de dos
listas.
Prototipo: (defun combina-lst-lst (lst1 lst2) ...)
Ejemplo:
>> (combina-lst-lst '(a b c) '(1 2))
((A 1) (A 2) (B 1) (B 2) (C 1) (C 2))
15
2.4.3 Producto de lista de listas: Diseñar una función que calcule todas las posibles disposiciones de
elementos pertenecientes a n listas de forma que en cada disposición aparezca únicamente un elemento
de cada lista.
IMPORTANTE: Puede que sea necesario crear funciones distintas a la de los apartados 2.4.1, 2.4.2.
Prototipo: (defun combina-list-of-lsts (lstolsts)...)
Ejemplo:
>> (combina-list-of-lsts
((A + 1) (A + 2) (A +
(B + 1) (B + 2) (B +
(C + 1) (C + 2) (C +
'((a b c) (+ -)
3) (A + 4) (A 3) (B + 4) (B 3) (C + 4) (C -
(1
1)
1)
1)
2 3 4)))
(A - 2) (A - 3) (A - 4)
(B - 2) (B - 3) (B - 4)
(C - 2) (C - 3) (C - 4))
2.5 Operaciones con conjuntos [2 puntos]
Un conjunto es una colección de elementos distintos en la que el orden en que se encuentran los
elementos no es importante. Se puede representar un conjunto mediante una lista, siempre y cuando las
operaciones sobre la lista tengan en cuenta que el orden no es relevante.
En los ejercicios que siguen no se pueden utilizar funciones de librería LISP existentes para el
manejo de conjuntos (ADJOIN, SET-DIFFERENCE, UNION, INTERSECTION y similares). Para
prácticas posteriores sí podremos utilizarlas. Se admite que, según la implementación realizada,
pueda variar el orden de ordenación de los elementos en las listas que representan los conjuntos
resultantes.
2.5.1 Escribe una función que permita determinar si un elemento pertenece a un conjunto
Prototipo: (defun my-member-p (elt cjto) ...)
Ejemplo:
>> (my-member-p '(L 1 5.3) '((L 1 5.3) (Q) P (L 6.2) (A 3 6.8)))
T
>> (my-member-p '() '((L 1 5.3) (Q) P (L 6.2) (A 3 6.8)))
NIL
2.5.2 Escribe una función que determine si una lista es un conjunto, suponiendo que los objetos distintos
se representan mediante un símbolo distinto
Prototipo: (defun my-set-p (lst) ...)
Ejemplos:
>> (my-set-p nil)
T
>> (my-set-p '(L 1 5.3))
T
>> (my-set-p '((L 1 5.3) Q (Q) P (L 6.2) (A 3 6.8)))
T
>> (my-set-p 'a)
NIL
>> (my-set-p '(a b c a))
NIL
2.5.3 Escribe una función que permita eliminar un elemento de un conjunto
Prototipo: (defun my-remove (elt lst) ...)
Ejemplo: >> (my-remove '(L 1 5.3) '( (L 1 5.3) Q (Q) (L 6.2) P (A 3 6.8)))
(Q (Q) P (L 6.2)(A 3 6.8))
2.5.4 Escribe una función que permita incluir un elemento en un conjunto
Prototipo:
Ejemplos:
(defun my-adjoin (elt cjto) ...)
16
>> (my-adjoin '(L 1 5.3) '((Q) (L 6.2) P (A 3 6.8)))
((Q) P (L 6.2) (A 3 6.8) (L 1 5.3))
>> (my-adjoin '((C 8 8.9) A (P Q)) '((Q) (L 6.2) P (A 3 6.8)))
((Q) (L 6.2) P (A 3 6.8) ((C 8 8.9) A (P Q)))
2.5.5 Escribe una función que permita realizar la unión de dos conjuntos
Prototipo: (defun my-union (cjtoa cjtob) ...)
Ejemplos:
>> (my-union '((Q) (L 6.2) P (A 3 6.8)) '())
((A 3 6.8) (L 6.2) P (Q))
>> (my-union '((Q) (L 6.2) P (A 3 6.8)) '(R (B 3 6.8) (L 6.2)))
(R (B 3 6.8) (A 3 6.8) P (L 6.2) (Q)) ;;; en cualquier orden
2.5.6 Utiliza la función reduce para diseñar una función que permita realizar la unión de n conjuntos
La función reduce: (reduce #'fn '(e1 e2 e3 e4)) ≡ (fn (fn (fn e1 e2) e3) e4)
donde fn es función que toma dos argumentos.
Prototipo:
(defun my-multiple-union (set1 set2 ... setn) ...)
2.5.7 Escribe una función que permita realizar la intersección de dos conjuntos
Prototipo: (defun my-intersection (cjtoa cjtob) ...)
Ejemplos:
>> (my-intersection '((Q) (L 6.2) P (A 3 6.8)) '())
NIL
>> (my-intersection '((Q) (L 6.2) P (A 3 6.8)) '( P R (B 3 6.8) (L 6.2)))
(P (L 6.2))
2.5.8 Utiliza la función reduce para diseñar una función que permita realizar la intersección de n
conjuntos
Prototipo:
(defun my-multiple-intersection (set1 set2 ... setn) ...)
2.6 Recursión de cola [1 punto]
2.6.1 Considera las dos implementaciones del factorial
;;; Recursión
;;;
(defun factorial-recursivo (n)
(cond
((< n 0 ) NIL)
((= n 0) 1)
(t (* n (factorial-recursivo (- n 1))))))
;;;
;;; Recursión de cola: Más eficiente
;;;
(defun factorial-recursivo-cola (n)
(unless (< n 0 )
(factorial-recursivo-cola-aux n 1)))
(defun factorial-recursivo-cola-aux (n acumulador)
(if (= n 0)
acumulador
(factorial-recursivo-cola-aux (- n 1) (* n acumulador))))
¿Es más eficiente factorial-recursivo-cola que factorial-recursivo?
17
2.6.2 Utiliza la función reduce para definir la función factorial?
Prototipo:
(defun factorial-reduce (n) ...)
2.6.3 Considera la implementación para la evaluación del número combinatorio C(n,k), que utiliza la
definición recursiva
,0
,
1, ,
1,
1
1
1,
, 0,
0, (defun combinatorio-recursivo (n k)
(cond
((or (< n k) (< k 0)) NIL)
((or (= k 0) (= n k)) 1)
(t (let ((n-minus-1 (- n 1)))
(+ (combinatorio-recursivo n-minus-1 (- k 1))
(combinatorio-recursivo n-minus-1 k))))))
Haz una implementación con recursión de cola de esta función
Prototipo:
(defun combinatorio-recursivo-cola (n k) ...)
2.7 Funciones que toman como argumento otra función [1 punto]
(defun sorter (cost-fn)
#'(lambda (lista)
(sort lista #'< :key cost-fn)))
•
•
Analiza la función sorter, ¿qué hace exactamente?
Compara el funcionamiento de sorter con la siguiente expresión
(setf (symbol-function 'mifun)
#'(lambda (lista)
(sort lista #'< :key cost-fn)))
¿Qué ventajas o inconvenientes ves en el uso de sorter frente al uso de mifun?
Escribe un programa Lisp que, utilizando la función sorter y funcall, ordene una lista de números de
menor a mayor valor de acuerdo con una función de coste dada
Prototipo:
(defun ordena-por (cost-fn lista )
....)
Ejemplo:
> (ordena-por #'cos '(1 -2 20 -20 12 -1 5))
(-2 5 20 -20 1 -1 12)
La función anterior, ¿es destructiva como sort? En caso de que sea no destructiva explica el porqué. En
caso de que sea destructiva construye una versión no destructiva cuyo nombre sea ordena-por-nodestructiva.
18
3ª Semana: LISP III: Fórmulas bien formadas en lógica proposicional
Para representar las proposiciones atómicas se utilizarán símbolos LISP: 'p 'q 'r 'j
No se permite el uso de 't 'NIL o de los conectores para representar proposiciones atómicas
Para representar los conectores utilizará los átomos LISP incluidos en la siguiente tabla:
Conector
Representación
Disyunción
v
La letra v la primera letra de “velero”.
Conjunción
^
El acento circunflejo.
Implicación
=>
El símbolo igual seguido del mayor estricto que.
<=>
El símbolo del menor estricto que seguido del símbolo igual seguido
del mayor estricto que.
Bicondicional
¬
Negación
•
•
•
•
El símbolo de la negación (se obtiene con un teclado estándar pulsando
simultáneamente Alt Gr y la tecla con el 6)
Se utilizará bien notación infijo o notación prefijo (como en las expresiones LISP) para construir
fórmulas bien formadas no atómicas.
No se utilizarán átomos con valor de verdad constante en las fórmulas (True, False)
No se utilizarán reglas implícitas de precedencia para la aplicación de conectores. Se utilizarán
paréntesis para marcar el orden de aplicación de los conectores en las FBFs.
Uso de paréntesis:
o Son aceptables FBFs que asuman la asociatividad de v y ^
1. ((a ^ b) ^ c ^ d)
2. (a v ((¬ b) v c v d) v (a ^ b ^ (¬ q)))
o
No son aceptables fórmulas con paréntesis redundantes:
1. (a)
2. ((a))
•
•
Argumentos para los conectores
o La negación es un operador unario (toma un solo argumento).
o La implicación y bicondicional son operadores binarios (toman dos argumento).
o La disyunción y conjunción son operadores n-arios.
A continuación se le muestran algunos ejemplos en notación prefijo:
o
o
o
o
•
(¬ p)
(=> (¬ p) q)
(v (=> (¬ p) q) (¬ r) (<=> a b) )
(^ p (¬ t) (v p q r s) )
Por convención, se aceptarán como FBFs en formato prefijo (pero no en formato infijo) aquellas
en las que la conjunción o la disyunción estén vacías
o
o
(v ) ; su valor de verdad es False
(^ ) ; su valor de verdad es True
o tengan un único argumento.
Sea w una fórmula bien formada
o (v w)
o (^ w)
Son FBF distintas que tienen el mismo valor de verdad que w
19
En lógica proposicional se define:
• Literal
o Literal positivo: Un átomo (LISP: un átomo distinto a los que representan conectores. Ej. 'p)
o Literal negativo: negación de un literal positivo (LISP: lista cuyo 1er elemento es el conector
que representa la negación y cuyo segundo elemento es un literal positivo Ej. '(¬ p)).
• Cláusula (cláusula disyuntiva): disyunción de literales.
• Forma normal conjuntiva (FNC): Fórmula bien formada que consiste en una conjunción de
cláusulas disyuntivas.
Al comienzo del programa se deben definir las definiciones de conectores y los predicados para evaluar
si una expresión LISP es un conector, si se trata de un conector que toma 1 argumento (unario), 2
argumentos (binario) o n argumentos (n ≥ 0, enario).
(defconstant
(defconstant
(defconstant
(defconstant
(defconstant
+bicond+
+cond+
+and+
+or+
+not+
'<=>)
'=>)
'^)
'v)
'¬)
(defun conector-p (x)
(or (conector-unario-p x)
(conector-binario-p x)
(conector-enario-p x)))
(defun conector-unario-p (x)
(eql x +not+))
(defun conector-binario-p (x)
(or (eql x +bicond+) (eql x +cond+)))
(defun conector-enario-p (x)
(or (eql x +and+) (eql x +or+)))
3.1 [0.5 puntos] Escriba código LISP para determinar si una FBF es un literal positivo
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; EJERCICIO 3.1
;; Predicado para determinar si una FBF es un literal positivo
;;
;; RECIBE
: FBF en formato prefijo
;; EVALÚA A : T si FBF es un literal positivo, NIL en caso
;;
contrario.
(defun literal-positivo-p (fbf) ...)
(literal-positivo-p
evalúa a T
(literal-positivo-p
(literal-positivo-p
(literal-positivo-p
(literal-positivo-p
(literal-positivo-p
(literal-positivo-p
evalúan a NIL
'p)
'T)
'NIL)
'¬)
'(p))
'(¬ p))
'(¬ (v p q)))
3.2 [0.5 puntos] Escriba código LISP para determinar si una FBF es un literal negativo
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; EJERCICIO 3.2
20
;; Predicado para determinar si una FBF es un literal negativo
;;
;; RECIBE
: FBF en formato prefijo
;; EVALÚA A : T si FBF es un literal negativo, NIL en caso contrario.
;;
(defun literal-negativo-p (fbf) ...)
EJEMPLOS:
>> (literal-negativo-p
>> (literal-negativo-p
>> (literal-negativo-p
>> (literal-negativo-p
>> (literal-negativo-p
>> (literal-negativo-p
>> (literal-negativo-p
'(¬ p))
'(¬ T))
'(¬ NIL))
'(¬ =>))
'p)
'((¬ p)))
'(¬ (v p q)))
;
;
;
;
;
;
;
T
NIL
NIL
NIL
NIL
NIL
NIL
3.3 [0.5 puntos] Escriba código LISP para determinar si una FBF es un literal
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; EJERCICIO 3.3
;; Predicado para determinar si una FBF es un literal
;;
;; RECIBE
: FBF en formato prefijo
;; EVALÚA A : T si FBF es un literal, NIL en caso contrario.
;;
(defun literal-p (fbf) ...)
EJEMPLOS:
>> (literal-p 'p)
>> (literal-p '(¬ p))
evalúan a T
>> (literal-p '(p))
>> (literal-p '(¬ (v p q)))
evalúan a NIL
3.4 [2 puntos] Dado el código para determinar si una FBF está en formato prefijo
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Predicado para determinar si una FBF está en formato prefijo
;;
;; RECIBE
: FBF
;; EVALÚA A : T si FBF está en formato prefijo, NIL en caso contrario.
;;
(defun fbf-prefijo-p (fbf)
(unless (null fbf)
;; NIL no es FBF
(or (literal-p fbf)
;; Un literal es FBF
(and (listp fbf)
;; En caso de que no sea un literal debe ser una lista
(let ((primero (first fbf))
(resto (rest fbf)))
(cond
((conector-unario-p primero) ;; Si el primer elemento es un conector unario
(and (null (rest resto))
;; debería tener la estructura (<conector> FBF)
(fbf-prefijo-p (first resto))))
((conector-binario-p primero) ;; Si el primer elemento es un conector binario
(let ((resto2 (rest resto))) ;; debería tener estruct. (<conector> FBF1 FBF2)
(and (null (rest resto2))
(fbf-prefijo-p (first resto))
(fbf-prefijo-p (first resto2)))))
((conector-enario-p primero) ;; Si el primero es un conector enario
(or (null resto)
;; conjunción o disyunción vacías
(and (fbf-prefijo-p (first resto)) ;; tienen que ser FBF los operandos
(let ((operando2 (rest resto)))
(or (null operando2)
;; El segundo podría estar vacío
(fbf-prefijo-p (cons primero operando2)))))))
(t NIL))))))) ;; Error en FBF
21
Implemente en LISP una función para comprobar si una FBF está en formato infijo
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; EJERCICIO 3.4
;; Predicado para determinar si una FBF está en formato infijo
;;
;; RECIBE
: FBF
;; EVALÚA A : T si FBF está en formato infijo, NIL en caso contrario.
;;
(defun fbf-infijo-p (fbf) ...)
EJEMPLOS:
>> (fbf-infijo-p
>> (fbf-infijo-p
>> (fbf-infijo-p
>> (fbf-infijo-p
>> (fbf-infijo-p
>> (fbf-infijo-p
>> (fbf-infijo-p
>> (fbf-infijo-p
>> (fbf-infijo-p
>> (fbf-infijo-p
>> (fbf-infijo-p
>> (fbf-infijo-p
>> (fbf-infijo-p
>> (fbf-infijo-p
>> (fbf-infijo-p
>> (fbf-infijo-p
>> (fbf-infijo-p
>> (fbf-infijo-p
>> (fbf-infijo-p
>> (fbf-infijo-p
>> (fbf-infijo-p
>> (fbf-infijo-p
>> (fbf-infijo-p
'a)
; T
'( a ^ b ^ (p v q) ^ (¬ r) ^ s))
; T
'(A => B))
; T
'(A => (B <=> C)))
; T
'( B => (A ^ C ^ D)))
; T
'( B => (A ^ C)))
; T
'( B ^ (A ^ C)))
; T
'((p v (a => (b ^ (¬ c) ^ d))) ^ ((p <=> (¬ q)) ^ p ) ^ e)) ; T
nil)
; NIL
'(^))
; NIL
'(a ^))
; NIL
'(^ a))
; NIL
'(a))
; NIL
'((a)))
; NIL
'((a) b))
; NIL
'(^ a b q (¬ r) s))
; NIL
'( B => A C))
; NIL
'( => A))
; NIL
'(A =>))
; NIL
'(A => B <=> C))
; NIL
'( B => (A ^ C v D)))
; NIL
'( B ^ C v D ))
; NIL
'((p v (a => e (b ^ (¬ c) ^ d))) ^ ((p <=> (¬ q)) ^ p ) ^ e)); NIL
3.5 [2.5 puntos] Considere el código que permite transformar FBFs en formato prefijo a FBFs en
formato infijo
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Convierte FBF en formato prefijo a FBF en formato infijo
;;
;; RECIBE
: FBF en formato prefijo
;; EVALÚA A : FBF en formato infijo
;;
(defun prefijo-a-infijo (fbf)
(when (fbf-prefijo-p fbf)
(if (literal-p fbf)
fbf
(let ((conector
(first fbf))
(elementos-fbf (rest fbf)))
(cond
((conector-unario-p conector)
(list conector (prefijo-a-infijo (second fbf))))
((conector-binario-p conector)
(list (prefijo-a-infijo (second fbf))
conector
(prefijo-a-infijo (third fbf))))
((conector-enario-p conector)
(cond
((null elementos-fbf) fbf) ;;; conjunción o disyunción vacías.
;;; no tienen traducción a formato infijo
22
((null (cdr elementos-fbf))
(car elementos-fbf)) ;;; conjunción o disyunción con un único elemento
(t (cons (prefijo-a-infijo (first elementos-fbf))
(mapcan #'(lambda(x) (list conector (prefijo-a-infijo x)))
(rest elementos-fbf))))))
(t NIL)))))) ;; no debería llegar a este paso nunca
Escriba una función que permita transformar FBFs en formato infijo a formato prefijo eliminando
paréntesis redundantes
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; EJERCICIO 3.5
;; Convierte FBF en formato infijo a FBF en formato prefijo
;;
;; RECIBE
: FBF en formato infijo
;; EVALÚA A : FBF en formato prefijo
(defun infijo-a-prefijo (fbf) ...)
EJEMPLOS:
(infijo-a-prefijo 'a) ; A
(infijo-a-prefijo '((p v (a => (b ^ (¬ c) ^ d))) ^ ((p <=> (¬ q)) ^ p) ^ e))
;; (^ (V P (=> A (^ B (¬ C) D))) (^ (<=> P (¬ Q)) P) E)
(infijo-a-prefijo '(¬ ((¬ p) v q v (¬ r) v (¬ s))))
;; (¬ (V (¬ P) Q (¬ R) (¬ S)))
3.6 [2 puntos] Escriba una función LISP para extraer todos los símbolos atómicos a partir de una FBF
en cualquier tipo de formato.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; EJERCICIO 3.6
;;
;; RECIBE
: fórmula en cualquier formato (expr)
;; EVALÚA A : Lista de símbolos atómicos (sin repeticiones)
;;
utilizados en la fórmula bien formada. El orden en la lista no es
;;
relevante.
;;
(defun extrae-simbolos (expr) ...)
EJEMPLOS:
(extrae-simbolos 'A)
; (A)
(extrae-simbolos '(H <=> (¬ H)))
; (H)
(extrae-simbolos '((A <=> (¬ H)) ^ (P <=> (A ^ H)) ^ (H <=> P)))
;;(A P H) [En cualquier orden]
(extrae-simbolos '((<=> A (¬ H)) (<=> P
;; (A P H) [En cualquier orden]
(^ A
H)) (<=> H
P)))
Representación de las fórmulas en forma FNC.
• Cláusula (cláusula disyuntiva): disyunción de literales.
• Forma normal conjuntiva (FNC): Fórmula bien formada que consiste en una conjunción de
cláusulas disyuntivas. Es decir, es una conjunción entre disyunciones de literales.
3.7 [1 punto] Escriba código LISP para determinar si una FBF en formato prefijo es una cláusula
(disyuntiva)
DEFINICIÓN: Una cláusula es una disyunción de literales, es decir, debería tener un formato
'(v lit1 lit2 ... litn)
23
CASOS ESPECIALES:
'(v )
'(v lit1)
disyunción vacía (valor de verdad False)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; EJERCICIO 3.7
;; Predicado para determinar si una FBF es una cláusula
;;
;; RECIBE
: FBF en formato prefijo
;; EVALÚA A : T si FBF es una cláusula, NIL en caso contrario.
;;
(defun clausula-p (fbf) ...)
EJEMPLOS:
(clausula-p
(clausula-p
(clausula-p
(clausula-p
(clausula-p
(clausula-p
(clausula-p
(clausula-p
(clausula-p
(clausula-p
(clausula-p
(clausula-p
'(v )
;
'(v p))
;
'(v (¬ r)))
;
'(v p q (¬ r) s)) ;
'p)
'(¬ p))
NIL)
'(p))
'((¬ p)))
'(^ a b q (¬ r) s))
'(v (^ a b) q (¬ r)
'(¬ (v p q)))
T
T
T
T
;
;
;
;
;
;
s)) ;
;
NIL
NIL
NIL
NIL
NIL
NIL
NIL
NIL
3.8 [1 punto] Escriba código LISP para determinar si una FBF en formato prefijo está en FNC
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; EJERCICIO 3.8
;; Predicado para determinar si una FBF está en FNC
;;
;; RECIBE
: FBF en formato prefijo
;; EVALÚA A : T si FBF está en FNC con conectores,
;;
NIL en caso contrario.
DEFINICIÓN: Una FNC es una conjunción de cláusulas, es decir, debería tener un formato
'(^ K1 K2 ... Kn)
CASOS ESPECIALES:
'(^)
conjunción vacía (valor de verdad True)
'(^ (v ))
conjunción con cláusula vacía (valor de verdad False)
'(^ (v K1))
EJEMPLOS:
(FNC-p '(^ (v a b c) (v q r) (v (¬ r) s) (v a b))) ; T
(FNC-p '(^))
; T
(FNC-p '(¬ p))
; NIL
(FNC-p '(^ a b q (¬ r) s))
; NIL
(FNC-p '(^ (v a b) q (v (¬ r) s) a b))
; NIL
(FNC-p '(v p q (¬ r) s))
; NIL
(FNC-p '(^ (v a b) q (v (¬ r) s) a b))
; NIL
(FNC-p '(^ p))
; NIL
(FNC-p NIL)
; NIL
(FNC-p '((¬ p)))
; NIL
(FNC-p '(p))
; NIL
(FNC-p '(^ (p)))
; NIL
(FNC-p '((p)))
; NIL
(FNC-p '(^ a b q (r) s))
; NIL
(FNC-p '(^ (v a (v b c)) (v q r) (v (¬ r) s) a b)) ; NIL
(FNC-p '(^ (v a (^ b c)) (^ q r) (v (¬ r) s) a b)) ; NIL
(FNC-p '(¬ (v p q)))
; NIL
(FNC-p '(v p q (r) s))
; NIL
24
Descargar