Docsity
Docsity

Prepare-se para as provas
Prepare-se para as provas

Estude fácil! Tem muito documento disponível na Docsity


Ganhe pontos para baixar
Ganhe pontos para baixar

Ganhe pontos ajudando outros esrudantes ou compre um plano Premium


Guias e Dicas
Guias e Dicas


Introdução à Linguagem Prolog, Notas de estudo de Cultura

Apostila de Silvio Lago sobre funções básicas do prolog

Tipologia: Notas de estudo

2015

Compartilhado em 12/05/2015

marcos-lima-64
marcos-lima-64 🇧🇷

5

(2)

6 documentos

1 / 37

Toggle sidebar

Esta página não é visível na pré-visualização

Não perca as partes importantes!

bg1
I
NTRODUÇÃO
À
L
INGUAGEM
PROLOG
S
ILVIO
L
AGO
% distribui(L,A,B) : distribui itens de L entre A e B
distribui([],[],[]).
distribui([X],[X],[]).
distribui([X,Y|Z],[X|A],[Y|B]) :- distribui(Z,A,B).
% intercala(A,B,L) : intercala A e B gerando L
intercala([],B,B).
intercala(A,[],A).
intercala([X|A],[Y|B],[X|C]) :-
X =< Y,
intercala(A,[Y|B],C).
intercala([X|A],[Y|B],[Y|C]) :-
X > Y,
A
C
B
E
F
D
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b
pf1c
pf1d
pf1e
pf1f
pf20
pf21
pf22
pf23
pf24
pf25

Pré-visualização parcial do texto

Baixe Introdução à Linguagem Prolog e outras Notas de estudo em PDF para Cultura, somente na Docsity!

INTRODUÇÃO À LINGUAGEM

PROLOG

SILVIO LAGO

% distribui(L,A,B) : distribui itens de L entre A e B

distribui([],[],[]). distribui([X],[X],[]). distribui([X,Y|Z],[X|A],[Y|B]) :- distribui(Z,A,B).

% intercala(A,B,L) : intercala A e B gerando L

intercala([],B,B). intercala(A,[],A). intercala([X|A],[Y|B],[X|C]) :- X =< Y, intercala(A,[Y|B],C). intercala([X|A],[Y|B],[Y|C]) :- X > Y,

A
C
B
E
D F

Sumário

    1. Elementos Básicos.............................................................................
    • 1.1. Fatos
    • 1.2. Consultas
      • 1.2.1. Variáveis compartilhadas....................................................
      • 1.2.2. Variáveis Anônimas
    • 1.3. Regras............................................................................................
      • 1.3.1. Grafos de relacionamentos
    • 1.4. Exercícios........................................................................................
    1. Banco de Dados Dedutivos
    • 2.1. Aritmética......................................................................................
    • 2.2. Comparação
    • 2.3. Relacionamento entre tabelas
    • 2.4. O modelo relacional
    • 2.4. Exercícios........................................................................................
    1. Controle Procedimental
    • 3.1. Retrocesso
    • 3.2. Cortes.............................................................................................
      • 3.2.1. Evitando retrocesso desnecessário
      • 3.2.2. Estrutura condicional..........................................................
    • 3.3. Falhas
    • 3.4. Exercícios........................................................................................
    1. Programação Recursiva
    • 4.1. Recursividade
    • 4.2. Predicados recursivos
    • 4.3. Relações transitivas.......................................................................
    • 4.4. Exercícios........................................................................................
    1. Listas e Estruturas
    • 5.1. Listas
      • 5.1.1. Tratamento recursivo de listas............................................
      • 5.1.2. Ordenação de listas
    • 5.2. Estruturas
      • 5.2.1. Representando objetos geométricos.....................................
    • 5.3. Exercícios........................................................................................
    1. Base de Dados Dinâmica
    • 6.1. Manipulação da base de dados dinâmica.......................................
    • 6.2. Aprendizagem por memorização....................................................
    • 6.3. Atualização da base de dados em disco
    • 6.4. Um exemplo completo: memorização de capitais
    • 6.5. Exercícios........................................................................................

Outra consulta que poderíamos fazer com relação ao Programa 1.1 é

?- pai(adão,enos).

Nesse caso, porém, o sistema responderia no.

As consultas tornam-se ainda mais interessantes quando empregamos vari- áveis, ou seja, identificadores para objetos não especificados. Por exemplo,

?- pai(X,abel).

pergunta quem é o pai de Abel ou, tecnicamente, que valor de X torna a con- sulta uma conseqüência lógica do programa. A essa pergunta o sistema res- ponderá X = adão. Note que variáveis devem iniciar com maiúscula.

Uma consulta com variáveis pode ter mais de uma resposta. Nesse caso, o sistema apresentará a primeira resposta e ficará aguardando até que seja pressionado enter, que termina a consulta, ou ponto-e-vírgula, que faz com que a próxima resposta possível, se houver, seja apresentada.

?- pai(adão,X). X = cain ; X = abel ; X = seth ; no

1.2.1 Variável compartilhada

Suponha que desejássemos consultar o Programa 1.1 para descobrir quem é o avô de Enos. Nesse caso, como a relação avô não foi diretamente definida nesse programa, teríamos que fazer a seguinte pergunta:

Quem é o pai do pai de Enos?

Então, como o pai de Enos não é conhecido a priori, a consulta corresponden- te a essa pergunta tem dois objetivos:

  • primeiro, descobrir quem é o pai de Enos, digamos que seja Y;
  • depois, descobrir quem é o pai de Y.

?- pai(Y,enos), pai(X,Y). Y = seth X = adão yes

Para responder essa consulta, primeiro o sistema resolve pai(Y,enos), obtendo a resposta Y = seth. Em seguida, substituindo Y por seth no se- gundo objetivo, o sistema resolve pai(X,seth), obtendo X = adão.

Nessa consulta, dizemos que a variável Y é compartilhada pelos objetivos pai(Y,enos) e pai(X,Y). Variáveis compartilhadas são úteis porque nos permitem estabelecer restrições entre objetivos distintos.

1.2.2 Variável anônima

Outro tipo de variável importante é a variável anônima. Uma variável anô- nima deve ser usada quando seu valor específico for irrelevante numa de- terminada consulta. Por exemplo, considerando o Programa 1.1, suponha que desejássemos saber quem já procriou, ou seja, quem tem filhos. Então, como o nome dos filhos é uma informação irrelevante, poderíamos digitar:

?- pai(X,_).

A essa consulta o sistema responderia X = adão e X = seth.

1.3 Regras

Regras nos permitem definir novas relações em termos de outras relações já existentes. Por exemplo, a regra

avô(X,Y) :- pai(X,Z), pai(Z,Y).

define a relação avô em termos da relação pai, ou seja, estabelece que X é avô de Y se X tem um filho Z que é pai de Y. Com essa regra, podemos agora realizar consultas tais como

?- avô(X,enos). X = adão

Fatos e regras são tipos de cláusulas e um conjunto de cláusulas constitui um programa lógico.

1.3.1 Grafos de relacionamentos

Regras podem ser formuladas mais facilmente se desenharmos antes um grafo de relacionamentos. Nesse tipo de grafo, objetos são representados por nós e relacionamentos são representados por arcos. Além disso, o arco que representa a relação que está sendo definida deve ser pontilhado. Por exem- plo, o grafo a seguir define a relação avô.

X
Z

avô Y

pai pai

1.2. Considere a árvore genealógica a seguir:

a) Usando fatos, defina as relações pai e mãe. Em seguida, consulte o sistema para ver se suas definições estão corretas. b) Acrescente ao programa os fatos necessários para definir as relações homem e mulher. Por exemplo, para estabelecer que Ana é mulher e Ivo é homem, acrescente os fatos mulher(ana) e homem(ivo). c) Usando duas regras, defina a relação gerou(X,Y) tal que X gerou Y se X é pai ou mãe de Y. Faça consultas para verificar se sua definição está correta. Por exemplo, para a consulta gerou(X,eva) o sistema deverá apresentar as respostas X = ana e X = ivo. d) Usando relações já existentes, crie regras para definir as relações fi- lho, filha, tio, tia, primo, prima, avô e avó. Para cada rela- ção, desenhe o grafo de relacionamentos, codifique a regra correspon- dente e faça consultas para verificar a corretude.

1.3. Codifique as regras equivalentes às seguintes sentenças:

a) Todo mundo que tem filhos é feliz. b) Um casal é formado por duas pessoas que têm filhos em comum.

Ana

Clô

Bia

Eva

Ivo

Raí

Gil

Ary

Noé Gal

Lia

Capítulo 2

Banco de Dados Dedutivo

Um conjunto de fatos e regras definem um banco de dados dedutivo, com a mesma funcionalidade de um banco de dados relacional.

2.1 Aritmética

Prolog oferece um predicado especial is, bem como um conjunto operadores, através dos quais podemos efetuar operações aritméticas.

?- X is 2+3.

X = 5

Os operadores aritméticos são + (adição), - (subtração), * (multiplicação), mod (resto), / (divisão real), // (divisão inteira) e ^ (potenciação).

Programa 2.1: Área e população dos países.

% país(Nome, Área, População)

país(brasil, 9, 130). país(china, 12, 1800). país(eua, 9, 230). país(índia, 3, 450).

O Programa 2.1 representa uma tabela que relaciona a cada país sua área em Km^2 e sua população em milhões de habitantes. Note que a linha inician- do com % é um comentário e serve apenas para fins de documentação. Com base nesse programa, por exemplo, podemos determinar a densidade demo- gráfica do Brasil, através da seguinte consulta:

?- país(brasil,A,P), D is P/A. A = 9 P = 130 D = 14.

Uma outra consulta que poderia ser feita é a seguinte: "Qual a diferença entre a população da China e da Índia?".

?- país(china,,X), país(índia,,Y), Z is X-Y. X = 1800 Y = 450 Z = 1350

Agora, com base no Programa 2.2, podemos, por exemplo, consultar o sistema para recuperar os dependentes de Ivo, veja:

?- func(C,ivo,_), dep(C,N). C = 3 N = raí ;

C = 3 N = eva

Observe que nessa consulta, o campo chave C é uma variável compartilhada. É graças a essa variável que o relacionamento entre funcionário e dependen- tes, existente na tabela original, é restabelecido.

Outra coisa que podemos fazer é descobrir de quem Ary é dependente:

?- dep(C,ary), func(C,N,_). C = 1 N = ana

Ou, descobrir quem depende de funcionário com salário inferior a R$ 950,00:

?- func(C,_,S), dep(C,N), S<950.

C = 3 S = 903. N = raí ;

C = 3 S = 903. N = eva

Finalmente, poderíamos também consultar o sistema para encontrar funcio- nários que não têm dependentes:

?- func(C,N,), not dep(C,). C = 2 N = bia

Nessa última consulta, not é um predicado primitivo do sistema Prolog que serve como um tipo especial de negação, denominada negação por falha, que será estudada mais adiante. Por enquanto, é suficiente saber que o predicado not só funciona apropriadamente quando as variáveis existentes no objetivo negado já se encontram instanciadas no momento em que o predicado é ava- liado. Por exemplo, na consulta acima, para chegar ao objetivo not dep(C,), primeiro o sistema precisa resolver o objetivo func(C,N,); mas, nesse caso, ao atingir o segundo objetivo, a variável C já foi substituída por uma constante (no caso, o número 2 ).

2.4 O modelo de dados relacional

Programas lógicos são uma poderosa extensão do modelo de dados relacional. Conjuntos de fatos correspondem às tabelas do modelo relacional e as opera- ções básicas da álgebra relacional (seleção, projeção, união, diferença simétri- ca e produto cartesiano) podem ser facilmente implementadas através de regras. Como exemplo, considere o Programa 2.3.

Programa 2.3: Uma tabela de filmes.

% filme(Título, Gênero, Ano, Duração)

filme('Uma linda mulher', romance, 1990, 119). filme('Sexto sentido', suspense, 2001, 108). filme('A cor púrpura', drama, 1985, 152). filme('Copacabana', comédia, 2001, 92). filme('E o vento levou', drama, 1939, 233). filme('Carrington', romance, 1995, 130).

Suponha que uma locadora precisasse de uma tabela contendo apenas filmes clássicos (i.e. lançados até 1985), para uma determinada promoção. Então, teríamos que realizar uma seleção na tabela de filmes:

clássico(T,G,A,D) :- filme(T,G,A,D), A =< 1985.

Suponha ainda que a locadora desejasse apenas os nomes e os gêneros dos filmes clássicos. Nesse caso, teríamos que usar também projeção:

clássico(T,G) :- filme(T,G,A,_), A =< 1985.

Agora, fazendo uma consulta com esse novo predicado clássico, obteríamos as seguintes respostas:

?- clássico(T,G).

T = 'A cor púrpura' G = drama ;

T = 'E o vento levou' G = drama

2.5 Exercícios

2.1. Inclua no Programa 2.1 uma regra para o predicado dens(P,D), que relaciona cada país P à sua densidade demográfica correspondente D. Em seguida, faça consultas para descobrir: a) qual a densidade demográfica de cada um dos países; b) se a Índia é mais populosa que a China.

Capítulo 3

Controle Procedimental

Embora Prolog seja uma linguagem essencialmente declarativa, ela provê recursos que nos permitem interferir no comportamento dos programas.

3.1 Retrocesso

O núcleo do Prolog, denominado motor de inferência, é a parte do sistema que implementa a estratégia de busca de soluções. Ao satisfazer um objetivo, geralmente há mais de uma cláusula no programa que pode ser empregada; como apenas uma delas pode ser usada de cada vez, o motor de inferência seleciona a primeira delas e reserva as demais para uso futuro.

Programa 3.1: Números binários de três dígitos.

d(0). % cláusula 1 d(1). % cláusula 2

b([A,B,C]) :- d(A), d(B), d(C). % cláusula 3

Por exemplo, para satisfazer o objetivo

?- b(N).

a única opção que o motor de inferência tem é selecionar a terceira cláusula do Programa 3.1. Então, usando um mecanismo denominado resolução, o sistema fará N=[A,B,C] e reduzirá^1 a consulta inicial a uma nova consulta:

?- d(A), d(B), d(C).

Quando uma consulta contém vários objetivos, o sistema seleciona sempre aquele mais à esquerda e, portanto, o próximo objetivo a ser resolvido será d(A). Para resolver d(A), há duas opções: cláusulas 1 e 2. O sistema sele- cionará a primeira delas, deixando a outra para depois. Usando a cláusula 1, ficaremos, então, com A=0, N=[0,B,C] e a consulta será reduzida a:

?- d(B), d(C).

A partir daí, os próximos dois objetivos serão resolvidos analogamente a d(A) e a resposta N=[0,0,0] será exibida no vídeo. Nesse ponto, se a tecla ponto-e-vírgula for pressionada, o mecanismo de retrocesso será acionado e o sistema tentará encontrar uma resposta alternativa. Para tanto, o motor de inferência retrocederá na última escolha feita e selecionará a próxima alter-

(^1) Reduzir significa que um objetivo complexo é substituído por objetivos mais simples.

nativa. A figura a seguir mostra a árvore de busca construída pelo motor de inferência, até esse momento, bem como o resultado do retrocesso.

3.2 Cortes

Nem sempre desejamos que todas as possíveis respostas a uma consulta se- jam encontradas. Nesse caso, podemos instruir o sistema a podar os ramos indesejáveis da árvore de busca e ganhar eficiência.

Tomando como exemplo o Programa 3.1, para descartar os números binários iniciando com o dígito 1, bastaria podar o ramo da árvore de busca que faz A=1. Isso pode ser feito do seguinte modo:

bin([A,B,C]) :- d(A),! , d(B), d(C).

A execução do predicado! (corte) poda todos os ramos ainda não explorados, a partir do ponto em que a cláusula com o corte foi selecionada.

?- b(N). 3, N=[A,B,C]

?- d(A), d(B), d(C).

1, A=0 2

?- d(B), d(C).

1, B=0 2

?- d(C).

1, C=0 2, C=

R=[0,0,0] R=[0,0,1]

...

...

?- bin(N). 3, N=[A,B,C]

?- d(A), !, d(B), d(C).

1, A=0 2

?- !, d(B), d(C).

!

...

?- d(B), d(C).

3.2.2 Estrutura condicional

Embora não seja considerado um estilo declarativo puro, é possível criar em Prolog um predicado para implementar a estrutura condicional if-then-else.

Programa 3.4: Comando if-then-else.

if(Condition,Then,Else) :- Condition,! , Then. if(,,Else) :- Else.

Para entender como esse predicado funciona, considere a consulta a seguir:

?- if(8 mod 2 =:= 0, write(par), write(ímpar)).

Usando a primeira cláusula do Programa 3.4, obtemos

Condition = 8 mod 2 =:= 0 Then = write(par) Else = write(ímpar)

e a consulta é reduzida a três objetivos:

?- 8 mod 2 =:= 0,! , write(par).

Como a condição expressa pelo primeiro objetivo é verdadeira, mais uma redução é feita pelo sistema e obtemos

?- !, write(par).

Agora o corte é executado, fazendo com que a segunda cláusula do programa seja descartada, e a consulta torna-se

?- write(par).

Finalmente, executando-se write, a palavra par é exibida no vídeo e o pro- cesso termina.

Considere agora essa outra consulta:

?- if(5 mod 2 =:= 0, write(par), write(ímpar)).

Novamente a primeira cláusula é selecionada e obtemos

?- 8 mod 2 =:= 0,! , write(par).

Nesse caso, porém, como a condição expressa pelo primeiro objetivo é falsa, o corte não chega a ser executado e a segunda cláusula do programa é, então, selecionada pelo retrocesso. Como resultado da seleção dessa segunda cláu- sula, a palavra ímpar é exibida no vídeo e o processo termina.

3.3 Falhas

Vamos retomar o Programa 3.1 como exemplo:

?- b(N).

N = [0,0,0] ; N = [0,0,1] ; N = [0,1,0] ; ...

Podemos observar na consulta acima que, a cada resposta exibida, o sistema fica aguardando o usuário pressionar a tecla ' ; ' para buscar outra solução.

Uma forma de forçar o retrocesso em busca de soluções alternativas, sem que o usuário tenha que solicitar, é fazer com que após cada resposta obtida o sistema encontre um objetivo insatisfatível. Em Prolog, esse objetivo é repre- sentado pelo predicado fail, cuja execução sempre provoca uma falha.

Programa 3.5: Uso de falhas para recuperar respostas alternativas.

d(0). d(1).

bin :- d(A), d(B), d(C), write([A,B,C]), nl, fail.

O Programa 3.5 mostra como podemos usar o predicado fail. A execução dessa versão modificada do Programa 3.1 exibirá todas as respostas, sem interrupção, da primeira até a última:

?- bin.

[0,0,0] [0,0,1] [0,1,0] ...

3.4 Exercícios

3.1. O programa a seguir associa a cada pessoa seu esporte preferido.

joga(ana,volei). joga(bia,tenis). joga(ivo,basquete). joga(eva,volei). joga(leo,tenis).

Capítulo 4

Programação Recursiva

Recursividade é fundamental em Prolog; graças ao seu uso, programas real- mente práticos podem ser implementados.

4.1 Recursividade

A recursividade é um princípio que nos permite obter a solução de um proble- ma a partir da solução de uma instância menor dele mesmo. Para aplicar esse princípio, devemos assumir como hipótese que a solução da instância menor é conhecida. Por exemplo, suponha que desejamos calcular 211. Uma instância menor desse problema é 210 e, para essa instância, "sabemos" que a solução é 1024. Então, como 2 × 210 = 211 , concluímos que 211 = 2 × 1024 = 2048.

A figura acima ilustra o princípio de recursividade. De modo geral, procede- mos da seguinte maneira: simplificamos o problema original transformando- o numa instância menor; então, obtemos a solução para essa instância e a usamos para construir a solução final, correspondente ao problema original.

O que é difícil de entender, a priori, é como a solução para a instância menor é obtida. Porém, não precisamos nos preocupar com essa parte. A solução da instância menor é gerada pelo próprio mecanismo da recursividade. Sendo assim, tudo o que precisamos fazer é encontrar uma simplificação adequada para o problema em questão e descobrir como a solução obtida recursivamen- te pode ser usada para construir a solução final.

4.2 Predicados recursivos

A definição de um predicado recursivo é composta por duas partes:

1 o^ base: resolve diretamente a instância mais simples do problema. 2 o^ passo: resolve instâncias maiores, usando o princípio de recursividade.

problema original

instância menor

solução final

solução

simplifica usa

obtemos

Programa 4.1: Cálculo de potência.

% pot(Base,Expoente,Potência)

pot(_,0,1). % base pot(B,N,P) :- % passo N>0, % condição do passo M is N-1, % simplifica o problema pot(B,M,R), % obtém solução da instância menor P is B*R. % constrói solução final

O Programa 4.1 mostra a definição de um predicado para calcular potências. A base para esse problema ocorre quando o expoente é 0 , já que qualquer número elevado a 0 é igual a 1. Por outro lado, se o expoente é maior que 0 , então o problema deve ser simplificado, ou seja, temos que chegar um pouco mais perto da base. A chamada recursiva com M igual a N-1 garante justa- mente isso. Portanto, após um número finito de passos, a base do problema é atingida e o resultado esperado é obtido.

Um modo de entender o funcionamento dos predicados recursivos é desenhar o fluxo de execução.

A cada expansão, deixamos a oval em branco e rotulamos a seta que sobre com a operação que fica pendente. Quando a base é atingida, começamos a preencher as ovais, propagando os resultados de baixo para cima e efetuando as operações pendentes. O caminho seguido é aquele indicado pelas setas duplas. As setas pontilhadas representam a hipótese de recursividade.

pot(2,3,R) R=

R=

2 ∗

obtemos

3 − 1

pot(2,2,R)

2 − 1

pot(2,1,R)

1 − 1

pot(2,0,R)

R=

2 ∗

obtemos

R=

2 ∗

obtemos

obtemos