Escolar Documentos
Profissional Documentos
Cultura Documentos
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
((<key1> . <expression1>)
(<key2> . <expression2>)
...
(<keyn> . <expressionn>))
Para obtermos o objecto associado a uma determinada chave podemos utilizar a função
assoc:
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:
Listas de Propriedades
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:
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.
> matriz1
#2A((BERLIOZ NIL NIL) (NIL NIL NIL) (NIL NIL 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:
> 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:
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:
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:
> p2
#S(POINT X 0 Y 0)
> (make-point)
#S(POINT X 0 Y 1)
> (make-point :x 2)
#S(POINT X 2 Y 3)
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 t "A ~A tem ~A meses" nome idade)A Beatriz tem 9 meses
NIL
Input
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
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
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:
(baguira
berlioz)
> (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
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.
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.
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.
10. Modifique o programa de maneira a que se torne possível guardar e carregar todos
os registos de um ficheiro.