TEMA 2

Aritmética, datos y estructuras en Visual Prolog


 

1 Sintaxis Básica de VISUAL PROLOG

1.1 Aritmética de VISUAL PROLOG

2 Datos simples y Estructuras de Datos: DOMINIOS

[2.1 OBJETOS COMPUESTOS] [2.2 ÁRBOLES] [2.3 LISTAS] [2.4 MATRICES]

3 Bibliografía


 

Sintaxis Básica de VISUAL PROLOG

Hemos visto ya un buen número de convenciones que debemos utilizar para escribir programas en Prolog. La última parte de este tema la dedicaremos a conocer detalladamente las reglas sintácticas que hemos de seguir para que nuestras bases de conocimientos sean reconocidas por Visual Prolog.

Como ya hemos estudiado, un programa Prolog no es más que la especificación de una base de conocimientos lógica con las características siguientes:

[ÍNDICE]

Aritmética de VISUAL PROLOG

Las expresiones aritméticas en Visual Prolog se componen de operandos (números y variables), operadores (+, -, *, /, div, y mod) y paréntesis: A = 1 + 6 / (11 + 3) * Z. Ver Tabla 1.

Los números "0x" o "0o" significan hexadecimal y octal respectivamente: 0xFFF = 4095; 86 = 0o112 + 12.

El valor de una expresión se puede calcular si todas las variables están unificadas en el momento de la evaluación. El cálculo ocurre entonces en un orden determinado por la prioridad de los operadores aritméticos. Los operadores de mayor prioridad son evaluados primero. Ver Tabla 2.

[ÍNDICE]

Operaciones

Operando 1

Operador

Operando 2

Resultado

entero

+, -, *

entero

entero

real

+, -, *

entero

real

entero

+, -, *

real

real

real

+, -, *

real

real

entero ó real

/

entero ó real

real

entero

div

entero

entero

entero

mod

entero

entero

Tabla 1

[ÍNDICE]

Orden de evaluación

Si la expresión contiene subexpresiones entre paréntesis, las subexpresiones se evalúan primero.

Si la expresión contiene multiplicación o división, estas operaciones son realizadas trabajando de izquierda a derecha a través de la expresión.

Las operaciones de suma y resta son llevadas a cabo de izquierda a derecha también.

En el orden de evaluación se tiene en cuenta, lógicamente, la precedencia de los operadores.

Operador

Prioridad

+ -

1

* / mod div

2

- + (unario

3

Tabla 2

[ÍNDICE]

Funciones y predicados

Visual Prolog posee una gran cantidad de funciones y predicados matemáticos para realizar las más variadas operaciones. La lista completa se ofrece en la Tabla 3.

Nombre

Descripción

X mod Y

Resto de X dividido entre Y.

X div Y

Cociente de X dividido entre Y.

abs(X)

Valor absoluto de X.

cos(X)

Coseno de X.

sin(X)

Seno de X.

tan(X)

Tangente de X.

arctan(X)

Arcotangente de X.

exp(X)

e elevado al valor almacenado en X. (Exponencial).

ln(X)

Logaritmo neperiano de X.

log(X)

Logaritmo en base 10 de X.

sqrt(X)

Raíz cuadrada de X.

random(X)

Almacena en X un número aleatorio real entre 0 y 1.

random(X, Y)

Almacena en Y un número aleatorio en el intervalo 0 <= Y < X.

round(X)

Valor redondeado de X. El resultado es un número real.

trunc(X)

Valor truncado de X. El resultado es un número real.

val(domain,X)

Conversión explícita entre dominios numéricos.

Tabla 3

[ÍNDICE]

Comparaciones

En Visual Prolog podemos comparar expresiones aritméticas, caracteres, cadenas de caracteres y símbolos.

Las comparaciones de este tipo se realizan a través de operadores relacionales. Ver Tabla 4.

Símbolo

Relación

<

menor que

<=

menor o igual que

=

igual que

>

mayor que

>=

mayor o igual que

<> o ><

distinto

Tabla 4

[ÍNDICE]

Comparación de caracteres, cadenas de caracteres y símbolos

Además de las expresiones numéricas, podemos comparar caracteres, cadenas y símbolos: 'a' < 'b'; "antony" > "antonia" y P1 = peter, P2 = sally, P1 > P2.

[ÍNDICE]

Datos simples y Estructuras de Datos: DOMINIOS

En cualquier lenguaje de programación necesitamos representar datos complejos. Podemos definir dato complejo como la agrupación homogénea o heterogénea de otros datos que a su vez pueden ser también complejos o simples. Así pues, a partir de esta definición, los datos se pueden organizar de múltiples formas que ya conocemos: vectores, registros, listas, árboles, grafos, etc.

Un programa en Visual Prolog utiliza distintas secciones para las declaraciones de tipos o dominios con el fin de crear estructuras complejas de datos (DOMAINS), predicados (PREDICATES), reglas y hechos que forman los predicados (CLAUSES) y metas (GOAL).

En la sección de predicados se definen las cabeceras de los predicados que vamos a utilizar en nuestros programas, es decir el nombre del predicado y el dominio o tipo de los argumentos del mismo.

En la sección de cláusulas se define la implementación de cada predicado declarado en PREDICATES.

En la sección GOAL se establece la meta principal del programa.

Centrémonos ahora en la sección DOMAINS y en cómo crear estructuras complejas: objetos compuestos, árboles, listas y matrices.

[ÍNDICE]

OBJETOS COMPUESTOS

Los objetos compuestos están formados por un functor y un conjunto de argumentos. Por ejemplo, cuadro(NS, Autor, Estilo, Dimensiones) representa una estructura que almacena los datos más relevantes para describir este tipo de obra de arte. El nombre cuadro es el functor y los elementos entre paréntesis son los argumentos del objeto compuesto. Los argumentos (campos), de un objeto compuesto pueden ser datos simples o datos complejos.

Para definir objetos compuestos es necesario introducirnos en el concepto de DOMINIO o DOMAINS. Como veremos en la sección de prácticas, la sección DOMAINS de un programa en Visual Prolog agrupa los dominios (tipos), no estándares en el sistema.

Definir un dominio no es más que establecer qué forma tendrán los objetos que pertenecen a dicho dominio o tipo, por ejemplo, para el caso del cuadro, el dominio sería así:

un_cuadro= cuadro(INTEGER, STRING, STRING, STRING)

Como hemos dicho, los argumentos de un objeto compuesto pueden ser complejos:

un_comprador= comprador(STRING, un_cuadro)

El dominio o tipo del ejemplo define a un comprador de un cuadro. Los objetos compuestos de este tipo almacenan una cadena que puede representar el DNI y el cuadro que posee.

Veamos varios objetos de tipo un_comprador:

comprador("111111111", cuadro(1239, "Martín Jiménez", "óleo", "39x23")).

comprador("122111111", cuadro(1449, "Suárez Jiménez", "acuarela", "130x50")).

comprador("111133331", cuadro(2232, "Martín Pérez", "carboncillo", "100x100")).

[ÍNDICE]

ÁRBOLES

Un árbol es una estructura con una definición puramente recursiva, ya que se puede considerar como el elemento raíz cuyos hijos son, a su vez, árboles. Si el árbol tiene únicamente dos hijos se denomina árbol binario. Este modelo específico de árbol se utiliza mucho para resolver gran cantidad de problemas en aspectos de programación.

Un árbol se puede considerar, a su vez, un caso particular de grafo, donde todos los caminos son acíclicos.

La Figura 1 muestra un ejemplo de árbol.

Figura 1

Ejemplo de árbol

Muchos problemas de Inteligencia Artificial donde interviene el concepto de búsqueda se resuelven mediante la implementación de árboles. Los árboles de juego o los utilizados en la resolución de problemas relacionados con el procesamiento de lenguaje natural son casos muy concretos y descriptivos. Por lo tanto, podemos notar que el manejo eficiente de estas estructuras es sumamente importante para conseguir programas de calidad en este ámbito de la Ciencia.

Ya vimos que Prolog es un lenguaje que se adapta adecuadamente a este tipo de problemas. De hecho la técnica de resolución que utiliza Prolog se basa en la construcción de un árbol de búsqueda de soluciones, luego podemos concluir que el conocimiento de esta estructura es clave para este tipo de metodología declarativa.

Como en cualquier lenguaje, lo que necesitamos saber es la forma de declarar el árbol, ya que su implementación se puede realizar de muchas formas, por ejemplo, aunque una lista es un caso particular de árbol, un árbol se puede representar a través de listas, aunque en el caso de Visual Prolog, estudiaremos la construcción de árboles utilizando objetos compuestos recursivos.

Dado que un árbol está formado por la raíz y un conjunto de hijos, podemos representarlo utilizando la siguiente notación:

oración (sujeto (artículo (el), sustantivo (hombre)), predicado (verbo (come), CD (pan)))

El árbol generado se muestra en la Figura 2.

Figura 2

Árbol de análisis de una frase en español

Como se observa, el uso de un predicado con dos argumentos en el caso de árbol binario o N argumentos en el caso de árbol N-ario es una forma sencilla de representar un árbol.

Mediante objetos compuestos recursivos del tipo arbol(nodo, hijoizq, hijoder), donde hijoizq e hijoder son también árboles, podemos representar un árbol binario. El árbol vacío se representa a través del hecho vacio:

arbol(1,arbol(2,arbol(4,vacio,vacio),arbol(5,vacio,vacio)),arbol(3,vacio,vacio))

En la sección DOMAINS podemos crear un tipo árbol de enteros del modo siguiente:

mi_arbol= arbol(INTEGER, mi_arbol, mi_arbol); vacio

Operaciones con árboles representados mediante objetos compuestos recursivos

domains

arbol= nodo(integer, arbol, arbol); vacio

lista= integer*

predicates

concatenar(lista, lista, lista)

preorden(arbol, lista)

inorden(arbol, lista)

postorden(arbol, lista)

clauses

concatenar([],[],[]):-!.

concatenar([],L2,L2):-!.

concatenar(L1,[],L1):-!.

concatenar([X|Y],L2,[X|Aux]):-concatenar(Y,L2,Aux).

preorden(vacio,[]):-!.

preorden(nodo(X,Izq,Der),[X|L]):-preorden(Izq,L1),

preorden(Der,L2),

concatenar(L1,L2,L).

inorden(vacio,[]):-!.

inorden(nodo(X,Izq,Der),L):-inorden(Izq,L1),

inorden(Der,L2),

concatenar(L1,[X|L2],L).

postorden(vacio,[]):-!.

postorden(nodo(X,Izq,Der),L):-postorden(Izq,L1),

postorden(Der,L2),

concatenar(L1,L2,L3),

concatenar(L3,[X],L).

goal

inorden(nodo(1,nodo(2,nodo(4,vacio,vacio),nodo(5,vacio,vacio)),nodo(3,vacio,vacio)), L1),

preorden(nodo(1,nodo(2,nodo(4,vacio,vacio),nodo(5,vacio,vacio)),nodo(3,vacio,vacio)), L2),

postorden(nodo(1,nodo(2,nodo(4,vacio,vacio),nodo(5,vacio,vacio)),nodo(3,vacio,vacio)), L3).

[ÍNDICE]

LISTAS

Una lista se puede considerar como un caso particular de árbol del modo que se muestra en la Figura 3.

Figura 3

Lista implementada en forma de árbol

A su vez, una lista se puede considerar de forma recursiva. Es decir, siempre está formada por un elemento seguido de otra lista (ver Figura 4). Cuando la lista tienen un sólo elemento podemos considerar que está formada por dicho elemento y la lista vacía. Esta definición es muy interesante, ya que su conocimiento nos permitirá llevar a cabo todos las operaciones que se pueden realizar sobre las listas con poco esfuerzo.

Figura 4

Definición recursiva de una lista

Hemos de recordar que en Prolog no existen estructuras para realizar bucles luego todo los algoritmos que representemos se definirán de forma recursiva. El hecho de que la implementación de la lista sea también recursiva facilita la construcción de operaciones sobre la misma.

Es necesario comprender este tipo de estructura para construir algoritmos eficientes. La mayor parte de las operaciones que se realizan sobre una lista implica un recorrido de la misma, luego hemos de centrarnos en conocer cómo se lleva a cabo este algoritmo utilizando técnicas de recursividad.

Una lista se puede especificar en un predicado o en un objetivo a través de:

Las listas pueden ser homogéneas o heterogéneas, es decir, almacenar elementos del mismo tipo, o elementos de distinto tipo.

Para definir un tipo de lista en particular es necesario declararla en la sección DOMAINS.

lista= elementos* (lista de una dimensión)

lista2= elementos** (lista de dos dimensiones)

lista3= elementos*** (lista de tres dimensiones)...

Es interesante observar la sintaxis de definición de una lista: elementos representa el dominio o tipo de los elementos que componen la lista.

El tipo elementos puede representar un dominio simple, es decir, sólo un tipo de elementos se corresponde con dicho dominio o un dominio complejo, donde varios tipos de elementos se corresponden con dicho dominio.

Por ejemplo, una lista homogénea de elementos simples estaría definida como:

lista= integer*

Una lista heterogénea de elementos estaría definida como:

listaenteros=integer*

elementos= i(integer); s(symbol); c(char); le(listaenteros)

lista= elementos*

Ejemplo:

domains

listaenteros= integer*

elementos= i(integer); c(char); s(symbol); le(listaenteros)

lista= elementos*

predicates

recorrer(lista)

clauses

recorrer([]):-!.

recorrer([X|Y]):-write(X), nl, recorrer(Y).

goal

recorrer([i(1),c('a'),s(pepe),i(5),c('b'),le([1,2,3])]).

 

Se observa que la lista puede contener cuatro tipo de elementos distintos: enteros, caracteres, símbolos y listas de enteros. En la sección de declaración del dominio o tipo elementos no podemos escribir:

elementos= integer; char; symbol; listaenteros

para expresar que dicho dominio agrupa a cuatro tipos de elementos distintos sino que la sintaxis a utilizar es aquella que representa que elementos agrupa a cuatro tipos de objetos compuestos distintos.

El resultado de la ejecución de la meta es el siguiente:

i(1)

c('a')

s("pepe")

i(5)

c('b')

le([1,2,3])

yes

Las operaciones típicas que se pueden realizar sobre listas son: la inserción de elementos al principio, al final, en orden; borrado, búsqueda de elementos, recorrido, eliminación de duplicados y, en general, todas las de las que se pueden realizar sobre conjuntos de elementos tales como: intersección, unión, diferencia, pertenencia, comprobación de lista vacía, concatenación, etc.

Las listas se pueden utilizar para implementar otras estructuras tales como listas circulares, vectores, pilas, colas, árboles, grafos y matrices.

De cada estructura nos interesa saber cuáles son los algoritmos para acceder a ellas. Una vez que conocemos, perfectamente, el tipo de operaciones que las definen, cualquier tipo de implementación es válida. Por ejemplo, es frecuente usar una implementación mediante listas para plasmar matrices.

Por otro lado, es fundamental aplicar técnicas de diseño descendente para resolver todos nuestros problemas e implementar las estructuras necesarias en nuestras aplicaciones.

[ÍNDICE]

MATRICES

Podemos definir matrices a partir de listas, primero de 2 dimensiones y, más tarde, generalizar matrices de dimensión N.

En un lenguaje imperativo, recorrer una matriz de dos dimensiones implica el uso de un par de bucles anidados, que proporcionan una complejidad computacional O(n2). Sin embargo, en Prolog, no disponemos de este tipo de estructuras de control, por tanto, cualquier operación debe ser resuelta de forma recursiva mediante la declaración formal de su enunciado.

Un tratamiento elemento a elemento de las matrices tal y como se realiza en un lenguaje imperativo no es adecuado en Prolog, por tanto, conviene entender la estructura matriz como una lista de listas, y aplicar los algoritmos diseñados sobre listas para resolver problemas matriciales.

Una matriz de cuatro dimensiones se puede ver como la secuencia de un conjunto de matrices de tres dimensiones, una matriz de tres dimensiones como un conjunto de matrices de dos dimensiones, una matriz de dos dimensiones como un conjunto o lista de matrices de una dimensión (vector o lista), por último, un vector o lista no es más que una secuencia de elementos simples.

Las definiciones dadas son recursivas, luego los algoritmos que debemos construir para realizar operaciones sobre este tipo de estructuras, así definidas, también serán recursivos.

Operaciones con matrices bidimensionales

domains

fila=integer*

matriz= fila*

predicates

sumafila(fila, fila, fila)

sumar(matriz, matriz, matriz)

clauses

/*Predicado para calcular la suma de los elementos de una fila */

sumafila([],[],[]):-!.

sumafila([], L2, L2):-!.

sumafila(L1, [], L1):-!.

sumafila([C1|Cola1], [C2|Cola2], Res):- S=C1+C2,

sumafila(Cola1, Cola2, ColaRes),

Res=[S|ColaRes].

/*Predicado de recorrido de las filas para sumar los elementos mediante el uso del predicado anterior */

sumar([],[],[]):-!.

sumar([], L2, L2):-!.

sumar(L1,[], L1):-!.

sumar([C1|Cola1], [C2|Cola2], LR):-sumafila(C1, C2, Res),

sumar(Cola1, Cola2, ColaRes),

LR=[Res|ColaRes].

goal

sumar([[1,2,3],[2,2,2],[4,4,4]],[[1,1,1],[2,1,2],[1,2,3]],R).

 

[ÍNDICE]

Bibliografía

[Adarraga, 1994] Adarraga, Pablo. Zaccagnini José Luis. "Psicología e Inteligencia Artificial". Editorial Trotta. 1994.

[ÍNDICE]