Sie sind auf Seite 1von 10

Escola Superior de Tecnologia

Instituto Politécnico de Castelo Branco

Aulas Práticas de
Fundamentos de Inteligência Artificial

Arlindo Silva
Ana Paula Neves
Aula

5
Estruturas de Dados e Input/Ouput
Listas de Associações, Arrays, Estruturas e Input/Output
Estruturas de Dados em Lisp

Por vezes é mais fácil e/ou mais rápido utilizar estruturas de dados mais especializadas
do que as listas para realizar determinadas tarefas. O Lisp permite-nos utilizar listas de
associações ou de propriedades para guardar e recuperar valores associados a
determinado símbolo; vectores arrays e strings para armazenamento de sequências de
objectos e, finalmente, estruturas onde guardar conjuntos de dados mais heterogéneos.

Listas de Associações

Uma lista de associações tem o formato:

((<key1> . <expression1>)
(<key2> . <expression2>)
...
(<keyn> . <expressionn>))

Permite associar a uma chave um objecto qualquer. O resultado é uma lista de


associações em que cada associação corresponde a um par de objectos. Um par pode
ser criado utilizando o cons:

> (setf animal1 (cons (cons 'nome 'berlioz) nil))


((NOME . BERLIOZ))

> (setf animal1 (cons (cons 'dono 'beatriz) animal1))


((DONO . BEATRIZ) (NOME . BERLIOZ))

> (setf animal1 (cons (cons 'especie 'gato) animal1))


((ESPECIE . GATO) (DONO . BEATRIZ) (NOME . BERLIOZ))

Para obtermos o objecto associado a uma determinada chave podemos utilizar a função
assoc:

> (assoc 'dono animal1)


(DONO . BEATRIZ)

Veja que o assoc devolve o par inteiro correspondente à associação que procuramos.
Por sua vez, o setf pode ser utilizado para modificar qualquer elemento de um par. No
exemplo seguinte utilizamos o assoc e o rest para aceder ao dono do animal1 e
modificar o seu nome:
> (setf (rest (assoc 'dono animal1)) 'arlindo)
ARLINDO

> animal1
((ESPECIE . GATO) (DONO . ARLINDO) (NOME . BERLIOZ))

1. Defina uma função que receba uma lista e devolva uma lista de associações com o número de
ocorrências de cada elemento da primeira, por exemplo:

> (occurs ‘(a b a d a c d c a))


((a.4) (b.1) (d.2) (c.2))

Listas de Propriedades

Outra forma de associar valores ou objectos a um símbolo é através da sua lista de


propriedades. Sempre que um símbolo é criado, também é criada a sua lista de
propriedades, inicialmente vazia:

> (symbol-plist 'animal2)


NIL

Como pode ser observado nos exemplos seguintes, acede-se às propriedades de um


símbolo utilizando o get. O setf pode ser utilizado para modificar o valor de uma
propriedade. Quando atribuímos um valor a uma propriedade que não existe, essa
propriedade é criada e acrescentada à lista de propriedades do símbolo:

> (setf (get 'animal2 'nome) 'baguira)


BAGUIRA

> (setf (get 'animal2 'dono) 'beatriz)


BEATRIZ

> (setf (get 'animal2 'especie) 'gato)


GATO

> (symbol-plist 'animal2)


(ESPECIE GATO DONO BEATRIZ NOME BAGUIRA)

> (setf (get 'animal2 'dono) 'paula)


PAULA

> (symbol-plist 'animal2)


(ESPECIE GATO DONO PAULA NOME BAGUIRA)

Podemos utilizar o rem-prop para remover uma propriedade da lista de propriedades de


um símbolo:

> (remprop 'animal2 'dono)


T

> (symbol-plist 'animal2)


(ESPECIE GATO NOME BAGUIRA)

2
1. Tente resolver o exercício anterior devolvendo uma lista com apenas uma ocorrência de cada
elemento da lista que é recebida como argumento. Cada elemento da nova lista deverá ter uma
propriedade onde está guardado o número de ocorrências desse elemento na lista argumento.
Assuma que todos os elementos da lista argumento são símbolos.

Vectores e Matrizes

As listas são extremamente úteis e flexíveis como estrutura de dados mas por vezes é
necessário (e mais rápido) utilizar vectores e arrays para armazenamento de dados,
sobretudo quando existem várias dimensões de tamanho fixo e se pretende uma maneira
uniforme de aceder a todos os elementos. Em Lisp um array pode ser criado utilizando
make-array:

> (setf vector1 (make-array '(3)))


#(NIL NIL NIL)

> (setf matriz1 (make-array '(3 3)))


#2A((NIL NIL NIL) (NIL NIL NIL) (NIL NIL NIL))

A função make-array recebe uma lista com o número de elementos para cada dimensão
do array e devolve um novo objecto deste tipo que nos exemplos acima é associado a um
símbolo utilizando o setf.

Os elementos de qualquer array podem ser acedidos utilizando aref:

> (setf (aref matriz1 0 0) 'berlioz)


BERLIOZ

> (setf (aref matriz1 2 2) 'baguira)


BAGUIRA

> matriz1
#2A((BERLIOZ NIL NIL) (NIL NIL NIL) (NIL NIL BAGUIRA))

> (aref matriz1 1 0)


NIL

> (aref matriz1 2 2)


BAGUIRA

Note que os arrays são indexados a 0. O acesso a um vector (array unidimensional) pode
também ser feito utilizando svref o qual funciona da mesma maneira do que o aref
mas é mais rápido e apenas pode ser utilizado em vectores.

1. Defina uma função que receba um array quadrado bidimensional e o devolva rodado 90º. Utilize a
função array-dimensions para determinar as dimensões do array.

3
Strings

Uma string é um vector de caracteres, embora apareça com uma notação especial: uma
sequência de caracteres rodeada por aspas. Como é um vector podemos utilizar as
funções aplicáveis a arrays (como o aref) para o manipular:

> (setf s1 "O baguira é um gato")


"O baguira é um gato"

> (arrayp s1)


T

> (aref s1 16)


#\a

> (setf (aref s1 15) #\p)


#\p

> s1
"O baguira é um pato"

Como podemos ver nos exemplos acima, um caracter é representado pela sequência
#\<caractere>. Cada caracter tem um número associado, o qual pode ser obtido
utilizando char-code. Por sua vez podemos utilizar code-char para obter o caracter
correspondente a determinado número:

> (char-code (aref s1 15))


112

> (code-char 112)


#\p

1. Escreva uma função upper que receba uma string e devolva uma nova string em que as
minúsculas passaram a maiúsculas.

Estruturas

Por vezes é necessário criar estruturas de dados mais específicas, de acordo com as
necessidades do problema a ser tratado. O defstruct permite criar novas estruturas de
dados definidas pelo programador:

> (defstruct point


x
y)
POINT

No exemplo acima criámos uma nova estrutura de dados point com campos x e y.
Quando o programador cria uma nova estrutura de dados com o defstruct, são
criadas pelo Lisp, automaticamente, várias funções auxiliares:

4
§ make-<nome-da-estrutura>: cria e devolve uma nova “instância” da
estrutura de dados.
§ <nome-da-estrutura>-p: devolve nil quando o argumento não é uma
instância da estrutura de dados.
§ copy-<nome-da-estrutura>: devolve uma cópia da estrutura argumento.
§ <nome-da-estrutura>-<nome-do-campo>: conjunto de funções que
permitem aceder a cada um dos campos da nova estrutura.

Eis alguns exemplos da utilização das funções acima para a estrutura point:

> (setf p1 (make-point :x 3 :y 7))


#S(POINT X 3 Y 7)

> (point-p p1)


(#<STRUCTURE-CLASS POINT #xF2B3C8> #<STANDARD-CLASS STRUCTURE-
OBJECT #x9A9A30> #<BUILT-IN-CLASS T #x9A8170>)

> (point-x p1)


3

> (point-y p1)


7

> (setf p2 (copy-point p1))


#S(POINT X 3 Y 7)

> (setf (point-x p2) 0


(point-y p2) 0)
0

> p2
#S(POINT X 0 Y 0)

No exemplo seguinte podemos ver que o defstruct aceita também pares de


argumentos em que o primeiro elemento do par é o nome do campo e o segundo é uma
expressão Lisp que é utilizada para inicializar o campo quando um novo objecto é criado
e o valor do campo não é fornecido:

> (defstruct point


(x 0)
(y (+ x 1)))
POINT

> (make-point)
#S(POINT X 0 Y 1)

> (make-point :x 2)
#S(POINT X 2 Y 3)

1. Defina uma estrutura point3d que permita representar pontos em 3 dimensões.

2. Escreva uma função que receba uma lista de objectos point3d e devolva um novo point3d
que seja o ponto médio (centro de massa) dos pontos da lista.

5
Input/Output

Output

A função format permite não só escrever na consola como também construir strings.
Tem a seguinte estrutura:

(format <destination> <control-string> <optional-arguments>)

control-string é uma string de controlo em que determinadas sequências de


caracteres vão ser substituídas pelos resultados da avaliação das expressões em
optional-arguments. Quando <destination> é nil a string resultante é devolvida
pela função, podendo, por exemplo, ser guardada numa variável:

> (setf nome "Beatriz")


"Beatriz"

> (setf idade 9)


9

> (format nil "A ~A tem ~A meses" nome idade)


"A Beatriz tem 9 meses"

Quando <destination> é t format devolve nil e a string é impressa na consola:

> (format t "A ~A tem ~A meses" nome idade)A Beatriz tem 9 meses
NIL

Nos exemplos acima ~A é substituído pelo resultado da avaliação do parâmetro


correspondente, o qual é formatado de determinada maneira. ~A é portanto uma directiva
de formatação, existindo várias outras que podem ser utilizadas com o format. O help
do Lisp fornece mais informação sobre as várias directivas possíveis.

Input

As funções mais comuns de leitura em Lisp São o read o read-line e o read-char.


O read lê uma expressão em Lisp da consola, ou seja, só pára de ler quando receber um
enter a seguir a uma lista, um átomo ou uma string (com aspas):

> (read)(Berlioz Baguira)


(BERLIOZ BAGUIRA)

> (read)"Berlioz Baguira"


"Berlioz Baguira"

O read devolve aquilo que leu. O read-line assume que tudo o que leu antes do enter
era uma string:

6
> (read-line)(Berlioz Baguira)
"(Berlioz Baguira)"
T

> (read-line)"Berlioz Baguira"


"\"Berlioz Baguira\""
T

O read-line devolve dois valores (sim, isso é possível em Lisp J) . O primeiro é a string
lida e o segundo é T quando a leitura terminou com um enter (caso a leitura tenha sido
feita a partir da consola), ou tenha sido encontrado um fim de ficheiro (mais sobre
ficheiros já a seguir). Caso contrário devolve nil.

O read-char, como seria de esperar, permite realizar uma leitura caracter a caracter.

Ficheiros

A leitura e escrita de ficheiros em Lisp é basicamente igual à leitura e escrita da consola


que descrevemos no ponto anterior. A única coisa que muda é o stream em que a escrita
ou leitura é feita. Um stream é um objecto especial que em Lisp permite lidar com as
operações de escrita e leitura.

Nos pontos anteriores, como não existia um stream definido, as operações de escrita e
leitura foram realizadas sobre a consola, que corresponde ela própria a um stream:
*terminal-io*. A forma mais fácil de realizarmos operações de escrita leitura sobre
um ficheiro consiste em utilizar o with-open-file, que tem o seguinte formato:

(with-open-file (<stream> <filename>) <body>)

Os exemplos seguintes de utilização do with-open-file implicam a existência de um


ficheiro de nome exemplo com o seguinte texto:

(baguira
berlioz)

Eis alguns exemplos de leitura a partir do ficheiro:

> (with-open-file
(in "exemplo")
(read in))
(BAGUIRA BERLIOZ)

> (with-open-file
(in "exemplo")
(read-line in))
"(baguira "
NIL

> (with-open-file
(in "exemplo")
(read-char in))
#\(

7
Nos exemplos acima as três funções de leitura que conhecemos foram utilizadas para ler
do ficheiro, sendo-lhes passado como argumento o stream em causa. O read leu a
primeira expressão, o read-line leu a primeira linha e o read-char leu o primeiro
caracter.
No exemplo seguinte escrevemos várias linhas para o mesmo ficheiro utilizando o
format:

(with-open-file
(in "exemplo" :direction :output)
(format in "A Beatriz ~% tem ~% 9 meses"))

Como ~% é uma directiva de formatação que manda mudar de linha o ficheiro fica com o
seguinte texto:

A Beatriz
tem
9 meses

Para lermos estas três linhas do ficheiro podemos fazer:

(with-open-file (in "exemplo")


(while (not (eof-p in))
(format t "~A" (setf l (read-line in)))))

Note a utilização de um ciclo while na expressão acima. O Lisp permite várias maneiras
de fazer iteração, além dos já mencionados do-times e do-list. Estas incluem o
while, o until e várias outras, além do extremamente flexível do (veja o help ou o livro
aconselhado).

1. Escreva uma função que leia números de um ficheiro e escreva o cubo dos mesmos números num
segundo ficheiro.

Exercícios

1. Construa uma função que devolva a segunda palavra de uma frase (string) que
recebe como argumento.

2. Escreva uma função que receba uma string e devolva uma lista de inteiros
correspondentes aos caracteres que constituem o string.

3. Defina uma estrutura que represente um nodo de uma árvore binária.

4. Escreva uma função que receba uma árvore e um inteiro e devolva uma nova árvore
onde o inteiro foi inserido de forma ordenada.

5. Construa uma função que recebe uma árvore ordenada de inteiros e devolva uma
lista com os inteiros também ordenados.

6. Escreva um programa que permita ao utilizador introduzir dados sobre pessoas. O


programa deve permitir guardar o primeiro e último nome de uma pessoa, o seu

8
endereço, estado civil, e nomes dos seus filhos. Pode utilizar qualquer abordagem,
mas a solução escolhida não deve impor qualquer tipo de limites, tais como limitar o
número de filhos associados ao registo de uma determinada pessoa.

7. Modifique o programa anterior de maneira a que este permita visualizar todos os


registos.

8. Acrescentes ao programa funções que permitam pesquisar e visualizar um registo


por nome ou apelido.

9. Acrescente uma função que permita apagar o registo de uma pessoa.

10. Modifique o programa de maneira a que se torne possível guardar e carregar todos
os registos de um ficheiro.

Das könnte Ihnen auch gefallen