





























Estude fácil! Tem muito documento disponível na Docsity
Ganhe pontos ajudando outros esrudantes ou compre um plano Premium
Prepare-se para as provas
Estude fácil! Tem muito documento disponível na Docsity
Prepare-se para as provas com trabalhos de outros alunos como você, aqui na Docsity
Encontra documentos específicos para os exames da tua universidade
Prepare-se com as videoaulas e exercícios resolvidos criados a partir da grade da sua Universidade
Responda perguntas de provas passadas e avalie sua preparação.
Ganhe pontos para baixar
Ganhe pontos ajudando outros esrudantes ou compre um plano Premium
Apostila de Silvio Lago sobre funções básicas do prolog
Tipologia: Notas de estudo
1 / 37
Esta página não é visível na pré-visualização
Não perca as partes importantes!






























% 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,
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
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:
?- 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.
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.
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.
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ô.
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
Um conjunto de fatos e regras definem um banco de dados dedutivo, com a mesma funcionalidade de um banco de dados relacional.
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 ).
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.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.
Embora Prolog seja uma linguagem essencialmente declarativa, ela provê recursos que nos permitem interferir no comportamento dos programas.
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.
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).
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.
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.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).
Recursividade é fundamental em Prolog; graças ao seu uso, programas real- mente práticos podem ser implementados.
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.
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