Vous êtes sur la page 1sur 72

FACULDADE DE ENGENHARIA DA UNIVERSIDADE DO PORTO

EXERCÍCIOS DO CNPL – Concurso


Nacional de Programação em Lógica

LUÍS PAULO REIS

LICENCIATURA EM ENGENHARIA INFORMÁTICA E COMPUTAÇÃO


PROGRAMAÇÃO EM LÓGICA - 3º ANO
SETEMBRO DE 2003
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL2003/I – Concurso Nacional de Programação em Lógica 2003

CNPL 2003/I. CNPL 2003 - Salinas


Introdução
Nas Salinas do Samouco o sr.Silva vende cristais de Sal aos visitantes, como iniciou o negócio há pouco
tempo não tem balança e usa duas caixas de madeira, uma, a caixa 1 têm capacidade para 500 gramas e a
outra, a caixa 2, tem capacidade para 200 gramas. Sempre que um cliente lhe pede uma determinada
quantidade o Sr. Silva fornece-lha ou explica que não lha consegue vender. O Sr. Silva obtêm as quantidades
pedidas com as seguintes operações sobre as caixas: encher até à capacidade total, despejando totalmente,
despejando uma caixa na outra até que a outra esteja totalmente cheia ou que a que está a ser despejada esteja
vazia, ou despejando a uma caixa no saco do cliente.
Por exemplo, para vender 100 gramas de cristais de Sal o Sr. Silva faz as seguintes acções: enche a caixa 2 e
a seguir despeja-a na caixa 1; enche a caixa 2, outra vez, e despeja-a na caixa 1; volta a encher a caixa 2 e
despeja-a na caixa 1 até a caixa 1 estar cheia; fica com as 100 gramas na caixa 2 e despeja-a no saco do
cliente.
Tarefa
Faça um programa para ajudar o Sr. Silva a decidir se pode ou não vender o peso de sal que lhe pedem e se
puder quais são os movimentos que ele deve fazer (deve indicar a melhor solução, i.e. a solução com
o menor número de movimentos). Para que o Sr. Silva continue a poder usar o seu programa caso
alguma caixa se parte e ele a tenha que substituir por outra de capacidade diferente, deve receber as
capacidades máximas das caixas como parâmetro. Inicialmente as caixas estão vazias.

Dados
Input: salinas (Pret, Max1, Max2). %3 inteiros
Pret - Número de gramas pretendido.
Max1 - Capacidade máxima da caixa 1 em gramas.
Max2 - Capacidade máxima da caixa 2 em gramas.
Resultado
Output: uma lista de acções -
e(c1) - enche caixa 1
e(c2) - enche caixa 2
d(c1) - despeja caixa 1
d(c2) - despeja caixa 2
d(c1,c2)- despeja a caixa 1 sobre a 2 até encher a 2 ou vazar a 1
d(c2,c1)- despeja a caixa 2 sobre a 1 até encher a 1 ou vazar a 2
d_saco(c1) - despeja a caixa 1 no saco do cliente.
d_saco(c2) - despeja a caixa 2 no saco do cliente.
Exemplos
input: salinas(100,500,200).
output: [e(c1),d(c1,c2),d(c2),d(c1,c2),d_saco(c1)]
-----------
input: (50,500,200).
output: no
-----------
input:(200,250,550).
output: [e(c1),d(c1,c2),e(c1),d(c1,c2),e(c1),d(c1,c2),d_saco(c1)]
-----------
input:(100,250,550).
output:
[e(c2),d(c2,c1),d(c1),d(c2,c1),d(c1),d(c2,c1),e(c2),d(c2,c1),d(c1),d(c2,c1),d_sa
co(c2)]
ou qq uma das seguintes (que têm o mesmo número de movimentos), só precisa de devolver uma solução.
[e(c2),d(c2,c1),d(c1),d(c2,c1),d(c1),d_saco(c2),e(c2),d(c2,c1),d(c1),d(c2,c1),d_saco(c2)]
ou
[e(c2),d(c2,c1),d(c1),d(c2,c1),d_saco(c2),e(c2),d(c1),d(c2,c1),d(c1),d(c2,c1),d_saco(c2)]
ou
[e(c2),d(c2,c1),d(c1),d(c2,c1),d_saco(c2),d(c1),e(c2),d(c2,c1),d(c1),d(c2,c1),d_saco(c2)]
ou
[e(c2),d(c2,c1),d(c1),d(c2,c1),d_saco(c2),d(c1,c2),e(c2),d(c2,c1),d(c1),d(c2,c1),d_saco(c
2)]
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL2003/A – Concurso Nacional de Programação em Lógica 2003

CNPL 2003/A. CNPL 2003 - Autómatos Celulares


Introdução
Um Autómato Celular (AC) é um sistema dinâmico determinístico que consiste num array A de células
idênticas que repetidamente mudam o seu estado ou cor de acordo com uma determinada regra de
actualização ra. Esta regra é aplicada simultaneamente a todas as células de A em unidades de tempo
discretas. Quando ra é aplicada a uma célula particular x ∈ A, a nova cor para x é determinada pelo valor
corrente das células numa vizinhança Vx de x. Embora existam muitas escolhas interessantes para A, vamos
restringir a nossa atenção a arrays uni-dimensionais de inteiros. A vizinhança de raio r (r é um inteiro
positivo) de uma célula x é então o intervalo Vx = {y ∈ Z : |x-y| ≤ r}. De um modo geral, as células de um AC
tomam uma cor de um conjunto de k cores diferentes (k ≥ 2). No nosso problema tomamos r = 1 e k = 2. As
duas cores possíveis são representadas por 0 e 1.
Para os ACs uni-dimensionais com r = 1 e k = 2, a regra de actualização ra de uma célula x depende das cores
correntes das três células na vizinhança de x, pelo que podemos representar a nova cor de x por ra(p,q,r),
onde p, q, r denotam as cores correntes respectivamente das células x-1, x e x+1. Por exemplo, a regra
ra(p,q,r) = r desloca a sequência de cores (a sequência de zeros e uns) de uma unidade para a esquerda em
cada actualização.
Tarefa
A tarefa consiste em escrever um programa Prolog que dada a configuração inicial das células; a regra de
actualização; e o número N de actualizações a realizar; mostra na primeira linha do ecrã a configuração inicial
e nas N linhas seguintes as N configurações que lhe sucedem.
Dados
O array uni-dimensional A é representado por uma lista de zeros e uns. A regra ra(p,q,r) é representada da
maneira óbvia. Por exemplo, a regra
p + (1-2p) (q+r-qr)
é codificada como
p + (1-2*p)*(q+r-q*r)
À esquerda da primeira célula (à esquerda do primeiro elemento na lista) e à direita da última célula (à direita
do último elemento na lista) supomos um zero para efeitos de aplicação da regra (supomos, respectivamente,
que p = 0 e r = 0).
Exemplos
O programa deve ser invocado através do predicado:
ca(+ConfiguracaoInicial,+RegraActualizacao,+NumActualizacoes)
Seguem-se dois exemplos ilustrativos:
?- ca([0,0,0,0,1],r,6).
0 0 0 0 1
0 0 0 1 0
0 0 1 0 0
0 1 0 0 0
1 0 0 0 0
0 0 0 0 0
0 0 0 0 0

?- ca([0,0,0,1,0,0,0],p+(1-2*p)*(q+r-q*r),13).
0 0 0 1 0 0 0
0 0 1 1 1 0 0
0 1 1 0 0 1 0
1 1 0 1 1 1 1
1 0 0 1 0 0 0
1 1 1 1 1 0 0
1 0 0 0 0 1 0
1 1 0 0 1 1 1
1 0 1 1 1 0 0
1 0 1 0 0 1 0
1 0 1 1 1 1 1
1 0 1 0 0 0 0
1 0 1 1 0 0 0
1 0 1 0 1 0 0
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício – CNPL2003/B – Concurso Nacional de Programação em Lógica 2003

CNPL 2003/B. CNPL 2003 - Smoke


Introdução
A Smokesoft é uma conhecida empresa de desenvolvimento de software, onde se usam as mais modernas
ferramentas (incluindo a famosa distribuição Debian) e onde o controlo da produtividade é muito apertado.
Na Smokesoft trabalham muitos programadores, dos melhores que há. Todos partilham uma ampla sala, em
regime de open-space. Infelizmente, há programadores que fumam e, felizmente, há programadores que não
fumam. A convivência social não é problemática mas a produtividade ressente-se e a administração teve de
actuar.
Por um lado, verificou-se que estando o ar poluído com fumo a produtividade dos não fumadores desce
imediatamente. De facto, estudos científicos mostraram que a perda marginal de produtividade para um não
fumador causada pelo fumo do x-ésimo cigarro fumado na sala é x LOC por dia. Explicando melhor: a
produtividade dos programadores mede-se em “lines of codes”, LOC, por dia. Tome o caso do João, que não
fuma. A sua produtividade nominal é 128 LOC/dia. No entanto, se um colega fumar um cigarro na sua
presença, o João fica ligeiramente desconcentrado e já só conseguirá fazer 127 linhas nesse dia. Se forem
fumados dois cigarros, já só conseguirá fazer 125 linhas, com 3 cigarros já só vai até 122, etc. Se fumarem
para aí uns 20 cigarros, mais vale mandá-lo para casa, porque já não vai conseguir fazer nada que se
aproveite.
Sendo assim, poderíamos pensar em proibir o fumo, mas isso seria uma má ideia, porque a produtividade dos
programadores fumadores ressentir-se-ia. De facto, o ganho marginal de produtividade para um programador
fumador pelo x-ésimo cigarro por ele fumado é 12 – x LOCs por dia. Veja o que se passa com a Isabel,
grande fumadora. A sua produtividade nominal é 96 LOC/dia. No entanto, se fumar um cigarrito, a
produtividade sobe para 107, se fumar 2, sobe para 117, se fumar 3, sobe para 126, etc. Se fumar mais do que
12, fica de tal maneira intoxicada que a produtividade começa a descer, e se fumar trinta e tal cigarros já não
faz mais nada.
Perante isto, a administração tem de escolher a nova política de fumo no trabalho. Não se trata de apenas
decidir qual é o número de cigarros que cada fumador pode fumar, porque é possível instalar um aparelho de
reciclagem de ar que elimina uma percentagem do fumo, assim contribuindo para o bem-estar e
produtividade dos não fumadores. O custo de operação dessa máquina é 1000 * x2 euros por dia (1000 vezes x
ao quadrado), sendo x a percentagem do fumo eliminado. Por exemplo, eliminar 50% do fumo custa ¼SRU
dia, eliminar 80% custa ¼SRUGLD
Qual é a política de fumo mais vantajosa para a empresa?
Tarefa
A sua tarefa é escrever um programa Prolog que, dados o número de programadores fumadores, o número de
programadores não fumadores e o valor da linha de código em cêntimos, calcula o número de cigarros que
cada fumador pode (e deve!) fumar, a percentagem de fumo que a máquina de reciclagem de ar deve extrair e
o ganho de produtividade assim obtido truncado aos cêntimos, de maneira que o lucro da Smokesoft seja o
maior possível.
No caso de haver várias soluções igualmente vantajosas, o seu programa deve escolher a que corresponda ao
menor número de cigarros por fumador.
Esclarecimento: os não fumadores só são sensíveis ao fumo de cigarros “inteiros”. Por exemplo, se houver no
ar o fumo de 10 cigarros e a máquina estiver a funcionar a 75%, os não fumadores só são incomodados pelo
equivalente a 2 cigarros.
Outro esclarecimento: a máquina só pode funcionar em “modo inteiro”, isto é, a percentagem de fumo que
consegue extrair é um número inteiro a dividir por 100. Para os efeitos deste problema, percentagem 17, por
exemplo, significa 17%.
O seu programa deve conter um predicado smoke(S, N, L, C, R, P) que, quando chamado com S, N e
L instanciados com números inteiros positivos representado o número de fumadores, o número de não
fumadores e o valor de uma linha de código, devolve em C o número inteiro de cigarros que cada fumador
deve fumar, em R o número inteiro que representa a percentagem de fumo que a máquina deve extrair do ar e
em P o ganho de produtividade assim obtido.
Exemplo
Eis um exemplo de utilização do predicado smoke:
| ?- smoke(1, 1, 400, C, R, P).
C = 6;
R = 1;
C = 14390;
No
| ?- smoke(2, 0, 400, C, R, P).
C = 11;
R = 0;
C = 52800;
no
| ?- smoke(3, 1, 500, C, R, P).
C = 6;
R = 39;
C = 33790;
no
| ?- smoke(1, 5, 1000, C, R, P).
C = 4;
R = 26;
C = 16240;
no
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL2003/C – Concurso Nacional de Programação em Lógica 2003

CNPL 2003/C. CNPL 2003 – Dicionário Errado

Introdução
Um dicionário contem definições (ou entradas) para expressões de uma língua, podendo essas definições
conter referências a outras expressões. Se uma referência numa das definições estiver errada é necessário
corrigir o dicionário de uma forma eficiente.
O autor do dicionário de que aqui se trata é muito propenso a erros do tipo: dar como referência a expressão E
se o que deveria referir é uma expressão E' que aparece referida numa das definições de E. Por exemplo,
referir gato em vez de felino, que aparece referido numa definição de gato --- é claro que na definição de gato
como marosca (aqui há gato!), felino não consta.
Supõe-se que o autor do dicionário não é tão incompetente que crie definições contendo referências para a
própria expressão a definir.

Tarefa
Escreva um programa que resolva problemas do tipo anterior, em que um dicionário é representado por uma
lista de entradas da forma e(X,Defs) com X um termo único que descreve a expressão, e Defs uma lista
não vazia de definições.
Cada definição é representada por um termo d(N,Descr) com N o número da definição e Descr uma lista
de termos de duas formas: p(P) para palavras e ref(R) para referências.

Dados
O programa a escrever tem como dados o dicionário errado D e uma expressão E, passados como argumentos
ao predicado de topo.

Resultados
Para um dicionário errado D e uma expressão E pretende-se obter um dicionário corrigido C substituíndo
todas as referências a E por referências a Ec, que é (segundo o autor do dicionário!) a única expressão
referida nas definições de E. Para evitar problemas futuros com o referido autor, o programa deve falhar se
houver mais que uma referência nas definições de E, bem como em situações de: mais de uma entrada para a
expressão dada, ou não existência de referência que se possa usar.
Por questões de eficiência, o dicionário errado só pode ser visitado (explicita ou implicitamente a nível do
programa Prolog) uma única vez.
O predicado de topo do programa deve ser refsx(D,E,C).
Exemplos
Exemplos usando o seguinte dicionário:
[e(autor,[d(1,[p(criador)]),
d(2,[p(escritor)]),
d(3,[p(compositor)])]),
e(ave,[d(1,[p(comida),p(de),ref(gato)])]),
e(gato,[d(1,[ref(felino),p(pequeno)]),
d(2,[p(marosca)])]),
e(leao,[d(1,[p(especie),p(de),ref(gato)])]),
e(marosca,[d(1,[p(tramoia)])]),
e(rato,[d(1,[p(comida),p(de),ref(gato)]),
d(2,[p(esperto)]),
d(3,[p(alvo),p(para),ref(gato)])]),
e(terminal,[d(1,[p(fim)])])]
?- teste_dic(D), refsx(D,gato,C).
D = ...
C = [e(autor,[d(1,[p(criador)]),
d(2,[p(escritor)]),
d(3,[p(compositor)])]),
e(ave,[d(1,[p(comida),p(de),ref(felino)])]),
e(gato,[d(1,[ref(felino),p(pequeno)]),
d(2,[p(marosca)])]),
e(leao,[d(1,[p(especie),p(de),ref(felino)])]),
e(marosca,[d(1,[p(tramoia)])]),
e(rato,[d(1,[p(comida),p(de),ref(felino)]),
d(2,[p(esperto)]),
d(3,[p(alvo),p(para),ref(felino)])]),
e(terminal,[d(1,[p(fim)])])] ? ;
no
?- teste_dic(D),
refsx([e(leao,[d(1,[p(mau)])])|D],leao,C).
Mais de uma entrada para :leao
no
?- teste_dic(D), refsx(D,rato,C).
Mais de uma referencia na definicao de :rato
no
?- teste_dic(D), refsx(D,felino,C).
Nao existe referencia na definicao de :felino
no
?- teste_dic(D), refsx(D,lixo,C).
Nao existe referencia na definicao de :lixo
no
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL2003/D – Concurso Nacional de Programação em Lógica 2003

CNPL 2003/D. CNPL 2003 – Divisões

Introdução
Como estão certamente fartos de saber, os números racionais são aqueles que se podem obter a partir da
divisão de dois números inteiros. Quando passado para a forma decimal, um número racional nem sempre
pode ser representado com um número finito de casas decimais. Por exemplo: 1/3 = 0.33333... Mas, para os
números racionais, essa sequência (mesmo que infinita) de casas decimais não pode ser qualquer: a partir de
certa altura há sempre uma sequência de algarismos que se repete indefinidamente. Por isso mesmo, até é
usual representar esses números na forma decimal com a dita sequência no fim e entre parêntesis.
Por exemplo: 1/3 = 0.(3) 8/15 = 0.5(3) 1/7 = 0.(142857) 10/7 = 1.(428571)

Tarefa
Escrever um predicado Prolog que, dados dois números inteiros positivos n e m, calcule a sequência de
algarismos que se repete indefinidamente na representação decimal do número racional n/m.

Resultados
Deverá implementar o predicado ciclo(N,M,C) que, quando chamado com N e M instanciados com números
inteiros positivos, devolve em C uma lista com a sequência de algarismos que se repete indefinidamente na
representação decimal de N/M. Se N/M se conseguir representar com um número finito de casas decimais,
então em C deve ser devolvido [0]. Note que, em rigor, essa é uma sequência que se repete indefinidamente:
por exemplo, 1/4 = 0.25(0). No caso de haverem várias sequências possíveis (por exemplo 1/11 = 0.(09) =
0.0(90)), o seu predicado deverá devolver apenas uma delas. E, já agora, que seja aquela que dá origem a uma
representação mais curta do número (o 0.(09) é maiscurto que o 0.0(90), pelo que deverá ser devolvido [0,9]
e não [9,0]).

Exemplo
| ?- ciclo(1,3,C).
C = [3]; | ?- ciclo(1,11,C).
no C = [0,9];
| ?- ciclo(8,15,C). no
C = [3]; | ?- ciclo(1,4,C).
no C = [0];
| ?- ciclo(1,7,C). no
C = [1,4,2,8,5,7]; | ?- ciclo(7,7,C).
no C = [0];
| ?- ciclo(10,7,C). no
C = [4,2,8,5,7,1];
no
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL2003/E – Concurso Nacional de Programação em Lógica 2003

CNPL 2003/E. CNPL 2003 – Celebridades (nota: Este enunciado contém alguns erros)

Introdução
Numa festa, uma pessoa C é uma celebridade se C é uma pessoa que toda a gente conhece. Por exemplo, na
figura abaixo, está representada uma festa em que a celebridade é o Ivo (uma seta de A para B significa que A
conhece B).

Dado que festas sem celebridades não são interessantes, nem sequer são consideradas.

Tarefa
A tarefa é escrever um programa eficiente para encontrar a celebridade da festa. Por eficiente entende-se que
a pesquisa deve ser linear no número de pessoas da festa.

Dados
O programa recebe a festa através do ficheiro “ party.pl” . A festa é representada por factos people/1 (cujo
parâmetro é a lista de pessoas na festa) e knows/2 (que significa que o primeiro parâmetro conhece o
segundo).

Resultados
Durante a pesquisa, o programa deverá imprimir os testes que executa, ou seja, imprimir frases do tipo “ Será
que a Ana conhece o Zé?” .

Exemplo
people([ana,ze,to,rui,ivo]).
knows(ana,to).
knows(ana,ivo).
knows(ze,ivo).
...
A invocação do programa deverá ser executada através do predicado celebrity/1 cujo parâmetro deverá ficar
unificado com a celebridade da festa.
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL2003/F – Concurso Nacional de Programação em Lógica 2003

CNPL 2003/F. CNPL 2003 – Descodificador

Introdução
Pretende-se desenvolver um programa para descodificar mensagens cifradas de acordo com uma cifra de
substituição: cada letra da mensagem original foi substituida por outra letra (existindo uma bijecção entre as
letras do alfabeto original e as letras do alfabeto do código). Por exemplo, podemos substituir a letra 'e' pela
letra 'r', a letra 'h' pela letra 'x', a letra 'l' pela letra 'v' e a letra 'o' pela letra 'p' e a mensagem 'hello' será
codificada como 'xrvvp'.
O processo de descodificação de um código deste não pode ser cego (no sentido de se tentarem todas as
combinações pois existem 26! combinações possíveis). Uma solução passa por tirar partido do facto de nem
todas as letras aparecerem com a mesma frequência. De facto, as letras em Inglês surgem pela seguinte ordem
(da mais frequente para a menos frequente): E, T, A, O, I, N, S, H, R, D, L, C, U, M, W, F, G, Y, P, B, V, K,
J, X, Q, Z. Então, a primeira etapa passa por contar o número de vezes que cada letra surge na mensagem, e
ordená-las por ordem descrescente de ocorrência. Com esta informação, podemos tentar quebrar o código:
basta associar a letra mais frequente à letra 'E', a segunda frequente à letra 'T' e assim sucessivamente (para
não surgirem confusões entre as letras já foram substituidas e as que ainda não foram, assume-se que as letras
na mensagem original (codificada) são minúsculas e que as letras que já foram descodificadas são
maiúsculas). Este método mesmo assim não é muito eficiente. Imagine-se que se chegou a uma situação
ÄOEET wTrEd". Obviamente, este caminho não irá conduzir à solução pois AOEET, que já resulta da
substituição, não é uma palavra inglesa. Por isso, uma optimização passa por, sempre que se tiver uma
palavra completa já instanciada, testar se esta existe, recorrendo a um dicionário. Se existir, continuar a
descodificação, senão, voltar atrás e procurar outra substituição, modificando a última substituição feita. Por
exemplo, se a última substituição foi 'x' por 'O' então, em vez de se considerar 'O' deve considerar-se a letra
seguinte na lista, ou seja, 'I'. Assume-se que os espaços estão no sítio certo.

Tarefa
Escrever em Prolog um programa para descodificar mensagens codificadas de acordo com o método anterior.

Dados
O programa deverá receber como argumentos uma string correspondendo à mensagem codificada. Está
disponível um ficheiro dicionario.pl com um pequeno dicionário de palavras inglesas (através do predicado
word/1).

Resultados
O programa deverá ser chamado através do predicado decode/1 e mostrará no ecrã a mensagem
descodificada, tal como nos exemplos seguintes:
?- decode(’uaof of l uxfu uv uax cxmvcxg’).
A mensagem e: THIS IS A TEST TO THE DECODER
?- decode(’rxpp cvqx tve alzx hlqlnxc uv cxmvcx uax hxfflnx’).
...
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL2003/G – Concurso Nacional de Programação em Lógica 2003

CNPL 2003/G. CNPL 2003 – Discos Pintados

Introdução
Pretende-se pintar um disco com n coroas circulares e 2n sectores, usando para tal apenas duas cores
(preto e branco). Para que haja alguma diversidade na pintura do discos, mas que as diferenças
sejam esbatidas, a pintura deverá obedecer às seguintes regras:

• Cada sector em cada coroa tem apenas uma das cores


• Não podem haver dois sectores exactamente com a mesma configuração de cores
• Dois sectores adjacentes só podem diferir na cor de uma das suas coroas

Dos discos abaixo, ambos com n = 3, apenas o da esquerda está pintado de acordo com as regras.
Quanto ao da direita, não só o padrão "preto-branco-preto" se repete (sectores indicados com setas)
como existem sectores adjacentes que diferem na cor de mais do que uma coroa (o sector mais
acima difere de ambos os seus adjacentes na cor das coroas mais externa e mais interna.

Tarefa

Deverá implementar o predicado pintar(N,L) que, quando chamado com N instanciado com um
número inteiro positivo, devolve em L uma forma de pintar um disco com N coroas e 2N sectores.
Resultados
A forma de pintar um disco é devolvida no segundo argumento do predicado pintar/2 como uma
lista L. Cada elemento de L corresponde à forma de pintar cada um dos sectores do disco, sendo que
elementos consecutivos correspondem a sectores adjacentes. Note que o primeiro e último
elementos de L também correspondem a sectores adjacentes. Cada elemento da lista L é ele próprio
uma lista de valores preto e branco, correspondendo à pintura de cada uma das coras desse sector,
do seu interior para o exterior.
Por exemplo, o resultado abaixo corresponde à forma de pintar o disco da esquerda.
| ?- pintar(3,L).
L = [ [branco,branco,branco],
[branco,branco,preto],
[branco,preto,preto],
[branco,preto,branco],
[preto,preto,branco],
[preto,preto,preto],
[preto,branco,preto],
[preto,branco,branco] ];
no
| ?-
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL2003/H – Concurso Nacional de Programação em Lógica 2003

CNPL 2003/H. CNPL 2003 – StackGest – Gestor de uma Stack de Números

Introdução
A maioria dos telemóveis tem uma stack que guarda os últimos números chamados (até a um máximo de M).
Teoricamente a sua gestão consistira apenas em inserir no topo da stack, o último número chamado. Contudo
é preciso atender a duas restrições:

1. a stack só pode conter no máximo M números, o que significa que, o inserir mais um número, o mais
antigo deve ser eliminado, se a stack estiver cheia.
2. se o número a inserir já estiver guardado em alguma posição da stack, então passa para o topo, e não
se acrescenta nenhum número novo, isto é, a stack não pode conter números repetidos.

Além de tudo isso, em vez do número, deve ser inserida na stack a sigla correspondente, caso exista numa
lista telefónica (pessoal) presente.

Tarefa
Escreva um programa Prolog que, sendo dado um novo número telefónico e a stack dos últimos números
chamados, e tendo em consideração a lista telefónica existente na base de conhecimento, devolva a stack
actualizada.

Dados
Os únicos dados do problema são: o novo número a inserir na stack; e a stack actual---ambos argumentos do
predicado (a desenvolver) gere/3.
A lista telefónica a usar, para procurar a sigla associada a cada número, terá de ser acrescentada à base de
conhecimento na forma de um conjunto de factos correspondentes ao predicado lista/2 cujo 1º argumento
é um número de telefone e o 2º argumento é a sigla associada.

Resultado
O resultado esperado, nova stack com o número dado à cabeça, será fornecido como 3º argumento do
predicado gere/3.

Exemplos
Supondo que a BC contém a lista telefónica:
lista( 12345, aaa ).
lista( 23456, bbb ).
lista( 34567, ccc ).
lista( 45678, ddd ).
lista( 56789, eee ).
lista( 67890, fff ).
Seguem-se 4 exemplos de uso do gestor, gere, da stack dos números chamados (em que se supõe que o
comprimento máximo é M=4):
?- gere( 12345, [bbb], Snova ).
Snova = [ aaa, bbb ]
yes

?- gere( 12345, [bbb, aaa, 78901], Snova ).


Snova = [ aaa, bbb, 78901 ]
yes

?- gere( 12345, [bbb, ccc, 78901, ddd], Snova ).


Snova = [ aaa, bbb, ccc, 78901 ]
yes

?- gere( 45678, [bbb, ccc, 78901, ddd], Snova ).


Snova = [ ddd, bbb, ccc, 78901 ]
yes
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL2003/J – Concurso Nacional de Programação em Lógica 2003

CNPL 2003/J. CNPL 2003 – Comando Make


Introdução
Para se construírem sistemas de software complexos (ou talvez não tanto...) é necessário efectuar diversas
tarefas, algumas dependentes de outras. A maioria dos ambientes de desenvolvimento oferecem uma
ferramenta para automatizar o processo de construção, facilitanto a vida ao programador.
Tarefa
Trata-se de implementar uma cópia (lobotomizada) do comando Un*x make, que deverá:
• Aceitar uma especificação feita de regras e um objectivo.
• Produzir a sequência mais curta de acções necessária para atingir o objectivo designado.
A especificação consiste em regras que dizem que um objectivo específico depende de outros objectivos
intermédios, obtendo-se após a execução duma acção. Pode haver mais de uma acção por objectivo. Cada
objectivo pode depender de vários outros objectivos, os seus antecedentes, o que significa que todos os
antecedentes deverão encontrar-se satisfeitos para que o objectivo considerado seja elegível. Pode haver
múltiplas formas de satisfazer um determinado objectivo.
Dados
Uma especificação é um ficheiro, com termos Prolog, que poderá ser lido pelo programa ou (de preferência)
ser incluido com este, aquando da compilação. A especificação contém termos que se apresentam como
factos para o predicado rule/3, que será interpretado como sendo da forma rule(OBJECTIVO,
ANTECEDENTES, ACÇÕES) onde OBJECTIVO é um átomo que designa o objectivo desta regra,
ANTECEDENTES uma lista de átomos que designam os antecedentes deste objectivo e ACÇÕES é uma outra
lista de átomos que designa ``comandos' 'que seria necessário executar para satisfazer o objectivo em questão,
caso os seus antecedentes já estejam satisfeitos.
Resultados
O objectivo designado é especificado pela interrogação:
| ?- make(OBJECTIVO, ACÇÕES, EFEITOS_SECUNDÁRIOS).
Em que OBJECTIVO é o nome do objectivo pretendido (instanciado), ACÇÕES será a lista de acções que é
necessário efectuar para atingir o objectivo (variável) e EFEITOS_SECUNDÁRIOS será a lista de objectivos
``secundários''que tiveram de ser satisfeitos para que o objectivo pretendido também o fosse. Os seguintes
pontos deverão sempre verificar-se acerca do predicado make/3:
• A solução produzida deverá sempre ser a mais curta (em termos de número de acções).
• Poderão haver múltiplas ocorrências dum determinado objectivo, no decorrer do processo
desencadeado pelo make: serão consideradas como independentes.
• No caso em que o objectivo é impossível de satisfazer, o predicado make/3 deverá falhar
silenciosamente.
• Caso haja um ciclo nas dependências, o predicado make/3 deverá levantar uma excepção da forma
loop(LISTA). LISTA é uma lista com todos os objectivos que formam a dependência circular (o
ciclo.)

Exemplo
Considere as regras:
rule(f1, [], []).
rule(f2, [], []).
rule(g, [f1, f2], [preprocess([f1,f2]), compile(g)]).
rule(h, [g], [bork(h)]).
rule(h, [f2], [shortcut(h)]).
rule(l, [m], [foo]).
rule(m, [l], [bar]).
Poderemos obter a seguinte sessão:
| ?- load(rules).
| ?- make(g, A, S).
A = [preprocess([f1,f2]), compile(g)],
S = [f2, f1]

| ?- make(h, A, S).
A = [shortcut(h)],
S = [f2]

| ?- make(l, A, S).
ERROR: Unhandled exception: loop([l, m])

| ?- make(x, A, S).
no
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL2002/A – Concurso Nacional de Programação em Lógica 2002

CAÇA ÀS HIPÓTESES
Introdução
Num sistema de conhecimento baseado em modelos, o conhecimento causal é apresentado por regras do tipo
rN :: C <- A1 & A2 & ...& An
com o significado usual de que os antecedentes A1, A2, ... An, causam o consequente C (rN é apenas um
identificador para a regra). Para simplificar, assumiremos que os antecedentes e os consequentes são
proposições atómicas, representadas por átomos. Alguns desses átomos podem ser conhecidos, o que é
especificado através de factos do tipo
fN ! F.
em que F é o facto observado e fN um identificador único. Num sistema deste tipo, a tarefa de diagnóstico
consiste em determinar hipóteses que juntamente com os factos e as regras conhecidas “ justifiquem”
causalmente um determinado conjunto de observações. Nem todas as hipóteses são interessantes, pelo que o
sistema apenas considera hipóteses especificadas através de declarações
hN ? H.
em que H é uma hipótese e hN um identificador único. De notar que o mesmo átomo pode ser declarado
como hipótese e como facto (por exemplo, após ter sido validado).
Exemplo
Um exemplo de uma base de conhecimento causal pode ser
r1 :: febre <- gripe.
r2 :: febre_alta <- febre & fraqueza.
r3 :: dores <- gripe & febre_alta.
r4 :: febre <- anginas.
r5 :: dor_de_garganta <- anginas.
r6 :: dor_de_garganta <- constipação.
f1 ! fraqueza.
h1 ? gripe.
h2 ? fraqueza.
h3 ? anginas.
h4 ? constipação.

Neste exemplo, a observação de febre, pode ser justificada pelas hipóteses de gripe (pela regra r1) ou
anginas (pela regra r4). Já a observação de dores é justificada pela hipótese de gripe (em conjunto
com o facto f1 e as regras r1 e r2). Finalmente, a justificação das observações febre_alta e
dor_de_garganta é justificada ou pela hipótese simples anginas (com o facto f1 e as regras r2 e r4),
quer pela hipótese dupla constipação e gripe (com o facto f1 e as regras r1, r2 e r6).
Tarefa
O objectivo deste problema é implementar um predicado diag(+O, -K, -H), com o seguinte significado
1. O é um conjunto de observações que se pretende justificar, na forma O1 & O2 & ...& On (n
>= 1).
2. K retorna uma lista de identificadores de factos e regras que justificam em conjunto com H (ver
abaixo) as observações O;
3. H retorna uma lista com um conjunto de hipóteses, com cardinalidade mínima, que em conjunto
com os factos e regras de K justificam as observações O.
De notar, que o predicado só deve retornar hipóteses de cardinalidade mínima. Como a observação de
febre_alta e dor_de_garganta pode ser justificada pela hipótese simples anginas, o predicado
diag/3 não deve retornar a hipótese dupla gripe e constipação. Por retrocesso, o predicado deve
retornar todas as hipóteses de cardinalidade mínima, podendo repetir hipóteses se as regras e factos utilizados
forem diferentes.
Para a sua implementação do predicado diag/3 pode tomar em consideração que
a) todas as observações devem ter uma justificação com um máximo de 5 hipóteses
b) as regras da sua base de conhecimento não contêm dependências circulares.
Por exemplo, as regras abaixo não podem co-existir, pois febre_alta depende de febre e febre
depende de febre_alta.
rx :: febre <- febre_alta.
r2 :: febre_alta <- febre, fraqueza.
Note bem, que o programa deverá permitir ler ou dum ficheiro ou dum terminal o conjunto de regras e factos
que constituem a sua base de conhecimento, na sintaxe apresentada (poderá usar outra sintaxe, mas sofrerá de
uma penalização de 20% na avaliação).
Resultados
Assumindo que o ficheiro “ dados.pl” contem a base de conhecimento acima (regras r1 a r6, o facto f1 e as
hipóteses h1 a h4), o predicado diag/3 deve produzir os diagnósticos seguintes:
?- diag(febre, K, H).
K = [r1] , H = [gripe] ;
K = [r4] , H = [anginas] ;
no.
?- diag(febre_alta & dor_de_garganta, K, H).
K = [r5,f1,r4,r2], [H] = anginas ;
no.
?- diag(dores & dor_de_garganta, K, H).
K = [r5,f1,r1,r2,r3] , H = [gripe,anginas] ;
K = [r6,f1,r1,r2,r3] , H = [gripe,constipação] ;
K = [r5,f1,r4,r2,r3] , H = [gripe,anginas] ;
no.
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL2002/B – Concurso Nacional de Programação em Lógica 2002

Palavras Cruzadas
Introdução
Todos sabem, de certeza, o que é um jogo de palavras cruzadas. Nem vale a pena estar com grandes
explicações. Aqui vamos jogar um jogo, também solitário, parecido com o das palavras cruzadas. Tal como
nas palavras cruzadas clássicas, há um quadro que inicialmente tem uma série de quadrados em branco e
outros preenchidos a negro. Só que, ao contrário do jogo clássico, aqui sabemos à partida quais as palavras
que temos de colocar no quadro (nas palavras cruzadas só temos dicas sobre que palavras poderão ser). Não
sabemos é em que posição colocar essas palavras (no jogo clássico, neste aspecto a coisa é mais facilitada,
pois sabe-se sempre em que linha ou coluna deve ser colocada cada palavra). O objectivo do jogo é colocar
no quadro todas as palavras dadas inicialmente , seguindo as regras usuais das palavras cruzadas (um caracter
apenas por quadrado; não podem haver duas palavras consecutivas, na mesma linha ou coluna, sem que pelo
meio haja pelo menos um quadrado a preto, etc).
Por exemplo, se forem dadas as palavras CENPL, PROLOG, UPA, PAI, POOP, GATO, CAO, PATITO,
ANIZ, PIGET, ZIG, TPTI e o quadro da figura 1, uma solução possivel é a que se apresenta na figura 2.

U P O O P
P A I R
A G A T O
C E N P L
P A T I T O
O Z I G

Figura 1 Figura 2
O que se pretende é que faça um programa Prolog que, dados um quadro não preenchido de palavras cruzadas
e uma lista de palavras a colocar, preencha o quadro com essas palavras.
Dados
O programa deverá ser chamado através do predicado cruzadas/0 e afixar os resultados no terminal
conforme explicado abaixo.
A descrição do quadro inicial e da lista de palavras a colocar é feita, respectivamente, através dos factos
quadro/1 e palavras/1, que serão adicionados ao seu programa.
O argumento do facto palavras/1 será uma lista de palavras, sendo que cada palavra (para lhe simplificar a
tarefa) é dada como uma lista de caracteres. Por exemplo, se se pretender colocar a lista de palavras do
exemplo acima, adiciona-se o facto:
palavras([[c,e,n,p,l],[p,r,o,l,o,g],[u,p,a],[p,a,i],[p,o,o,p],[g,a,t,o],
[c,a,o],[p,a,t,i,t,o],[a,n,i,z],[p,i,g,e,t],[z,i,g],[t,p,t,i]]).
O argumento do facto quadro/1 é uma matriz, representada como uma lista de listas (onde cada lista
elemento corresponde a uma linha). Nessa matriz as posições correspondentes a quadrados em branco têm
variáveis livres, e as correspondentes a quadrados a preto têm o símbolo `+'
. Por exemplo, o quadro da figura
1 é representado por:
quadro([[_,+,_,_,_,_],
[_,_,_,+,+,_],
[_,+,_,_,_,_],
[+,_,_,_,_,_],
[_,_,_,_,_,_],
[+,_,+,_,_,_]]).
Resultados
Caso não seja possivel colocar todas as palavras no quadro, o predicado cruzadas/0 deve falhar. Se for
possivel, deve escrever no ecrã o quadro preenchido com as palavras (uma linha do ecrã por linha do quadro;
os quadrados preenchidos a preto devem ter o símbolo `+' ; se houverem casas por preencher após a colocação
de todas as palavras, no seu lugar deverá estar o símbolo `_').
Por exemplo, se chamado com os dois factos acima, o resultado deverá ser o que se apresenta à esquerda. Se,
para o mesmo quadro, a lista de palavras for a acima, mas sem as duas últimas palavras (tpti e zig) o resultado
será o que apresenta à direita (note o `_'na penúltima posição da última linha):
| ?- cruzadas. | ?- cruzadas.
u+poop u+poop
pai++r pai++r
a+gato a+gato
+cenpl +cenpl
patito patito
+o+zig +o+z_g
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL2002/C – Concurso Nacional de Programação em Lógica 2002

EMPILHAR CUBOS COLORIDOS


Introdução
Suponha que tem quatro cubos. As faces destes cubos podem ser amarelas, beiges, castanhas ou douradas,
sendo que cada cubo tem pelo menos uma face de cada côr. O problema consiste em empilhar os cubos de
forma a que cada côr apareça nas quatro faces da pilha.

Exemplo

Note que
• é irrelevante a ordem pela qual os cubos são empilhados (e portanto pode ser considerada fixa);
• a escolha da face frontal de um cubo apenas determina a face de trás (ou vice-versa);
• a escolha da face lateral direita determina a da face lateral esquerda (ou vice-versa).
O problema pode portanto ser resolvido escolhendo primeiro as faces frente/trás e depois, face a esta escolha,
escolhendo as faces laterais.
Tarefa
A tarefa é escrever um programa que solucione o problema. Para isso considere que os dados do problema (os
quatro cubos) são representados pelo seguinte grafo não orientado e etiquetado:
• os nós do grafo são as quatro cores — a,b,c,d;
• para i=1,2,3,4 e para todo o par de cores (cor,cor’), existe uma aresta entre cor e cor’ etiquetada
com o número i sse esse par de cores se encontra em faces opostas no cubo i.
Esta forma de representar os dados reflecte o facto da escolha de uma face de cada cubo determinar a face
oposta e permite portanto restringir o espaço de pesquisa da solução.
Dados
O programa será invocado através do predicado empilha_cubos/1 com o argumento instanciado com uma
lista de triplos

representando, como explicado no ponto anterior, os dados do problema.


Resultados
O seu programa deve indicar uma solução do problema mostrando no ecrã as faces frente/trás e as faces
esquerda/direita dos 4 cubos.
Exemplo:
?- empilha_cubos([(a,1,b),(c,1,b),(d,1,a),
(d,2,a),(a,2,b),(c,2,c),
(a,3,c),(b,3,d),(d,3,c),
(a,4,b),(d,4,d),(c,4,d)]).

Frt/Tras Esq/Dir
Cubo 1 : d / a c / b
Cubo 2 : c / c b / a
Cubo 3 : b / d a / c
Cubo 4 : a / b d / d
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL2002/D – Concurso Nacional de Programação em Lógica 2002

DETECTIVES
Introdução
O local, uma estância de verão, tinha um aspecto tão agradável que seria impossível imaginá-la como palco
de um crime de odor tão sórdido. A estância era composta por sete bungalows, quatro junto à lagoa (A, B, C,
D), dois junto ao oceano (F e G) e um no meio (E), ligados por caminhos de acordo com a figura:

Um empregado viu um homem de aspecto sinistro, carregando um balde grande, aproximar-se da estância
vindo da lagoa e esconder-se dentro de um dos bungalows próximos desta. O homem percorreu os caminhos
da estância deixando rastos de peixe podre por todo o lado.
A polícia, chamada ao local, detectou pelas pegadas lamacentas deixadas pelo criminoso, que este tinha
percorrido todos os caminhos da estância exactamente uma vez. Os detectives repararam também que não
havia pegadas que conduzissem para fora da estância, concluindo por isso que o infractor se devia ainda
encontrar escondido dentro de um dos bungalows. Infelizmente, as pegadas eram tão pouco nítidas que os
detectives foram incapazes de determinar a direcção destas. Para piorar ainda mais as coisas, o empregado da
estância que tinha avistado o infractor não se lembrava do bungalow em que este tinha entrado. A polícia
ficou impossibilitada de reproduzir o caminho do infractor. O máximo que conseguiu apurar foi que ele
nunca percorreu o mesmo caminho duas vezes.
Tarefa
Escrever em Prolog um programa para auxiliar a polícia a descobrir em que bungalow o infractor se encontra
escondido. O programa deverá poder ser aplicado em estâncias com topologias diferentes da do exemplo, mas
mantendo sempre o requisito de cada caminho ser percorrido exactamente uma vez.

Dados
O programa deverá receber como argumentos a lista dos possíveis pontos de partida (no caso do exemplo, a
lista [a, b, c, d]) e a topologia da estância, representada por uma lista de pares correspondentes aos vários
caminhos (no caso do exemplo, [(a,b), (a,c), (b,c), (b,e), (b,g), (c,e), (c,d), (d,e), (d,f), (e,g), (f,g)]).

Resultados
O programa deverá ser chamado através do predicado detective/2 que mostrará no ecrã o par (ou pares,
caso exista mais do que uma solução) de bungalows em que o primeiro elemento do par é o bungalow de
onde o infractor partiu e o segundo elemento do par é o bungalow onde o infractor está escondido.

Exemplos:
?- detective([a,b,c,d],
[(a,b),(a,c),(b,c),(b,e),(b,g), (c,e),(c,d),(d,e),(d,f),(e,g),(f,g)]).
[(d, g)]

?- detective([a,b],[(a,b),(a,c),(b,c)]).
[(a, a), (b, b)]
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL2002/E – Concurso Nacional de Programação em Lógica 2002

Alocador de Registos1
Introdução
Na construção de compiladores, um sub-problema comum e bem identificado é o da geração de código para
expressões aritméticas. As arquitecturas reais terem um número limitado de registos, facto que condiciona o
código que pode ser gerado. Um compilador deverá saber qual o número de registos de que dispõe para
calcular as expressões intermédias.
Tarefa
Considere que estamos a gerar código para um PDP-11 glorificado (ie. com número arbitrário de registos),
que dispõe das seguintes instruções:
Instrução Acção Observações
mov SRC, DST DST ← SRC Copia SRC para DST.

add SRC, DST DST ← DST + SRC Efectua uma operação aritmética.

sub SRC, DST DST ← DST - SRC

mul SRC, DST DST ← DST * SRC

div SRC, DST DST ← DST / SRC

Os operandos poderão ser registos, da forma rN (p/ex. r0, r7), constantes da forma #NNN (p/ex. #12) ou
nomes, que obedecem à sintaxe habitual para os identificadores.
Pretende-se determinar o número mínimo de registos com os quais se consegue avaliar uma determinada
1
expressão sem ``spilling,'
' produzindo neste processo uma sequência de instruções que avalie a expressão,
colocando o resultado no registo r0.
O algoritmo a aplicar pode ser resumido da seguinte forma: considere que se pretende gerar código para uma
expressão E=EL @ ER, em que ER e EL são expressões e @ é uma operação aritmética. Seja NL e NR o número
de registos necessários à avaliação de EL e ER respectivamente, o número de registos para avaliar E será dado
por N' =max(NL,NR+1) se avaliarmos EL primeiro e guardarmos o seu valor num registo enquanto avaliarmos
ER. Se optarmos por avaliar ER primeiro, precisaremos de N' '=max(NL+1,NR) registos. O objectivo será
escolher a ordem de avaliação que resulte no menor valor de N (de entre N'e N' '
), preservando a correcção do
resultado (a operação deverá ser comutativa).
Notas:
• Para avaliar uma expressão trivial (constante ou nome) precisamos de 0 registos pois estas podem ser
utilizadas directamente em operações.
• Depois de avaliar uma subexpressão, todos os registos usados nesse processo (excepto aquele em que
ficou o resultado) estão disponíveis para avaliar outra subexpressão.
• Atenção: aplicam-se as regras de comutatividade habituais.

Dados
Pretende-se obter N, tal que sejam necessários N registos distintos (de r0 a rN-1) para avaliar uma expressão
aritmética E. E será representada por um termo Prolog ``ground' '
, encarado como uma árvore cujas folhas
poderão ser átomos (designam os nomes) ou números (designam as constantes) e cujos nós interiores
corresponderão a uma operação aritmética (+/2, -/2, */2 e ’/’/2). Deverá ser implementado o predicado
sethi(+EXPR, ?NREGS) que produz como output uma sequência de instruções do PDP-11 correspondente
à avaliação de EXPR com NREGS registos, colocando o resultado final no registo r0. NREGS será instanciado,
caso esteja livre à entrada
Resultados
Exemplos de execução:
| ?- sethi(a+7*b, NR).~
mov #7,r0
mul b,r0
add a,r0
NR = 1
Yes

| ?- sethi((a+3*b-4/c)*(7-8*d), NR).
mov #3,r0
mul b,r0
add a,r0
mov #4,r1
div c,r1
sub r1,r0
mov #7,r1
mov #8,r2
mul d,r2
sub r2,r1
mul r1,r0
NR = 3
yes
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL2002/F – Concurso Nacional de Programação em Lógica 2002

CAVALEIRO DE EULER
Introdução
O problema que enunciamos de seguida foi colocado pelo matemático Leonhard Euler há mais de 200 anos.
Diz respeito à procura de um caminho Hamiltoniano num tabuleiro de xadrez (caminho que passa uma e uma
só vez por cada uma das casas do tabuleiro).
Começamos por lembrar que o cavalo de xadrez, numa dada casa do tabuleiro, pode ocupar, depois do seu
salto, um máximo de oito casas diferentes. Ele pode movimentar-se duas casas para cima, seguido de uma
deslocação para a esquerda ou para a direita; movimentar-se de duas casas para baixo, seguido de uma
deslocação para a esquerda ou para a direita; movimentar-se de duas casas para a direita, seguido de uma
deslocação para cima ou para baixo; movimentar-se de duas casas para a esquerda, seguido de uma
deslocação para cima ou para baixo. A questão colocada por Euler foi a de saber se dado um tabuleiro N ×N é
possível, ou não, a um cavalo, partindo de uma casa 1, passar uma só vez por cada uma das restantes N2 - 1
casas. Esta tem sido uma questão bem estudada, com algumas publicações científicas recentes, e algumas
variações do enunciado. Sabe-se que para tabuleiros de dimensão N ≥ 5 existem sempre muitas soluções.
Segue-se uma resposta possível para o tabuleiro de xadrez convencional (8 ×8):

1 42 37 44 25 4 15 18

38 55 40 3 14 17 24 5

41 2 43 36 45 26 19 16

56 39 54 13 48 35 6 23

63 12 57 46 61 22 27 20

58 53 62 49 34 47 30 7

11 64 51 60 9 32 21 28

52 59 10 33 50 29 8 31

O Algoritmo de Warnsdorff
É fácil engendrar um método de pesquisa para encontrar um caminho, se um existir: backtracking.
Deslocamos o cavalo tão longe quanto possível e, se chegarmos a um beco sem saída, voltamos atrás de
maneira a seguirmos outra direcção. O problema é que para tabuleiros grandes (nem é preciso que N seja tão
grande quanto isso...) o backtracking torna-se muito lento.
Em 1823 Warnsdorff propôs um algoritmo com a pretensão de encontrar sempre um caminho (quando ele
existe) sem nunca retroceder. Em cada posição do cavalo faz-se uma classificação das posições sucessoras,
deslocando-se o cavalo para o sucessor com a maior classificação. As posições sucessoras são as casas do
tabuleiro que ainda não foram visitadas e podem ser alcançadas por um único salto do cavalo a partir da
posição corrente. A classificação é maior para o sucessor com menor número de sucessores. Desta maneira,
as casas que tendem a estar isoladas são visitadas primeiro, evitando-se que fiquem de facto isoladas.
O tempo necessário para a execução deste algoritmo cresce linearmente com o número de casas do tabuleiro,
o que é bom, mas infelizmente sabemos hoje, com a ajuda dos computadores (que não estavam disponíveis
no tempo de Warnsdorff) que para tabuleiros de lado N > 76 podem ocorrer becos sem saída. Existem
algoritmos recentes de complexidade linear para todo o N, mas são extremamente complicados, lidando com
muitos casos particulares cujas soluções são já conhecidas à priori. A questão de saber se existe um
algoritmo, comparável ao de Warnsdorff em simplicidade, que resolva o problema em tempo linear para todo
o N •SHUPDQHFHHPDEHUWR

Tarefa
Na verdade, o algoritmo de Warnsdorff não elimina completamente a necessidade de backtracking. Surgem
situações em que existe mais do que um sucessor com menor número de sucessores, alguns dos quais
conduzem a becos sem saída. Aqui vamos pedir que implementem um melhoramento do esquema de
classificação de Warnsdorff. Acrescentamos a regra que, em caso de empate, escolhe um dos sucessores
(X,Y) com maior distância ao centro do tabuleiro: distancia = (X - C)2 + (Y - C)2, com C = N/2, onde N é a
dimensão do lado do tabuleiro. Note-se que, mesmo com esta regra adicional, podem existir vários candidatos
à posição seguinte do cavalo (todas elas devem ser usadas, se necessário, por backtracking). Para garantir a
unicidade de solução, deve adoptar as seguintes convenções:
• Prioridade dos saltos por ordem decrescente (olhe para o tabuleiro como uma matriz - o primeiro
elemento do par denota o incremento na linha, o segundo o incremento na coluna): (-1,2), (-2,1),
(-2,-1), (-1,-2), (1,-2), (2,-1), (2,1), (1,2).
• Considere como ponto de partida do cavalo o canto superior esquerdo: (1,1). No caso de não
existir solução (com as regras adoptadas) deve procurar uma usando um outro ponto de partida.
Partindo de (1,1) deve incrementar a coluna e só depois a linha: (1,1), (1,2), (1,3), …, (N,N).
A sua tarefa consiste então em escrever um programa Prolog que dada a dimensão N do tabuleiro (N • 
devolva o tabuleiro T, descrito por uma lista de linhas, onde cada linha é uma lista de N números da sucessão
1,…,N2, que descreve o caminho descrito pelo ‘‘Cavaleiro de Euler’’. Implemente para isso o predicado
wm(N,T) (iniciais de Warnsdorff Melhorado).

Dados
O seu programa será testado através do seguinte predicado ce(N) (iniciais de Cavaleiro de Euler):
ce(N) :- wm(N,L), mostra(L).

mostra([]).
mostra([L|R]) :- mostraLinha(L), nl, mostra(R).
mostraLinha([]).
mostraLinha([X|R]) :-
X < 10, write(’ ’), write(X), write(’ ’), mostraLinha(R).
mostraLinha([X|R]) :-
X < 100, write(’ ’), write(X), write(’ ’), mostraLinha(R).
mostraLinha([X|R]) :-
write(X), write(’ ’), mostraLinha(R).

Resultados
Mostramos de seguida alguns exemplos que ilustram os resultados esperados.
?- ce(2).
No

?- ce(5).
1 10 15 20 3
16 21 2 9 14
11 8 23 4 19
22 17 6 13 24
7 12 25 18 5
Yes

?- ce(8).
1 42 37 44 25 4 15 18
38 55 40 3 14 17 24 5
41 2 43 36 45 26 19 16
56 39 54 13 48 35 6 23
63 12 57 46 61 22 27 20
58 53 62 49 34 47 30 7
11 64 51 60 9 32 21 28
52 59 10 33 50 29 8 31
Yes
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL2002/F – Concurso Nacional de Programação em Lógica 2002

MIND THE GAP


Introdução
O metropolitano de uma cidade encontra-se parcialmente representado no mapa da figura 1.

A empresa Mind-theGap, Lda. que explora o Metro, encomendou-lhe um programa para aconselhar os
utentes sobre quais os melhores percursos a seguir nos seus trajectos. Para já, pretendem que o programa
considere exclusivamente a zona representada no mapa.
Este Metropolitano tem duas peculiaridades: as composições são novíssimas e deslocam-se a velocidades
muito elevadas, pelo que os tempos de viagem são curtos quando realizados numa única linha; contudo, as
estações são antigas e pouco funcionais, pelo que o tempo necessário para mudar de linha é muito elevado. Se
considerarmos que o custo de uma viagem na zona em causa é fixo, ou seja, independente da distância
percorrida e do número de linhas utilizado, compreenderemos o pedido feito pela Mind-the-Gap: “ Queremos
que o programa forneça ao utente o percurso que lhe permita chegar ao destino com o número mínimo de
mudanças de linha. Nem que o utente tenha que dar a volta completa à cidade! É melhor do que mudar de
linha para encurtar caminho” .
Tarefa
Escrever um programa Prolog planner/3 que produza um percurso de uma estação de origem X a outra
estação de destino Y. Um percurso entre duas estações X e Y pode envolver mais do que uma linha. Quando
tal acontece, é necessário realizar pelo menos uma mudança de linha numa estação de ligação. Por exemplo,
um percurso possível entre as estações 14 e 10 consiste em efectuar uma viagem na linha B até à estação 16,
seguida de outra na linha D até à estação 10.
Dados
O programa será invocado através do predicado
planner( X, Y, Percurso)
que deverá ter os dois primeiros argumentos instanciados com Estações do mapa da Figura 1: X com a
estação de partida, Y com a estação de chegada.
A descrição da topologia do Metropolitano é fornecida no ficheiro metro.pl. Neste ficheiro, cada linha é
descrita por uma estrutura com a forma linha(Nome, Estacoes), onde Nome identifica a linha e
Estacoes é uma lista com as estações da linha.
Por exemplo:
linha('
B', [13,14,15,16,17,7])
Resultados
O programa deverá instanciar Percurso, o terceiro argumento, com uma lista cujos elementos terão a forma
Linha-Muda, significando que o percurso utiliza Linha até à estação Muda. Esta lista deverá conter um
percurso entre X e Y que minimize as mudanças de linha. Apenas se exige uma solução.
Exemplos:
?- planner(3, 8, Percurso).
Percurso = [’A’ - 8]
yes
?- planner(6, 10, Percurso).
Percurso = [’A’ - 2,’D’ - 10]
yes
?- planner(23, 17, Percurso).
Percurso = [’E’ - 11,’A’ - 13,’B’ - 17]
yes

Conteúdo do Ficheiro metro.pl:


linha(’A’, [6,4,3,2,1,13,12,11,9,8,7]).
linha(’B’, [13,14,15,16,17,7]).
linha(’C’, [12,15,18,19,20,5]).
linha(’D’, [2,22,10,19,16,21,9]).
linha(’E’, [11,18,22,4,23]).
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL2002/G – Concurso Nacional de Programação em Lógica 2002

QUASE
Introdução
Nós, os da informática, somos bons em potências de 2. Todos sabemos que 210 é 1024 e que com quatro bytes
podemos representar números inteiros entre –231 e 231-1, ou seja, entre cerca de menos dois mil milhões e
cerca de mais dois mil milhões.
Também sabemos que o único factor primo de uma potência de 2 é 2. Logo, todos os divisores de uma
potência de 2 são potências de 2. Claro, dirá você. Mas então vejamos isto sob uma perspectiva diferente.
Dado um número inteiro positivo x, será que existe uma potência de 2 da qual x é “ quase” um divisor? Por
definição, diremos que um número inteiro x maior que 1 é quase divisor de y se x for divisor de y ou x for
divisor de y + 1 ou x for divisor de y – 1. (Repare que o problema só tem interesse se desconsiderarmos a
potência de expoente zero, 20, pois todos os números inteiros positivos seriam quase divisores de 1.)
Pois bem, um momento de reflexão mostra que se x for ímpar, então sim, existe uma potência de 2 da qual x é
quase divisor. De facto, existindo uma, existe uma infinidade, mas estamos interessados apenas na menor de
todas. Qual será ela, para um dado x?
Tarefa
Escreva um programa Prolog quase(+X, -N) que, dado um número inteiro X ímpar, instancie N com o
expoente da menor potência de 2 tal que X é quase divisor de 2N. O número representado por X pertence ao
intervalo 21+1..109-1.
Dados
O programa será invocado através do predicado quase(X, N) que deverá ter o primeiro argumento
instanciado com um inteiro pertencente ao intervalo 21+1..109-1.
Resultados
O programa deverá instanciar N, o segundo argumento, com o expoente da menor potência de 2 tal que X é
quase divisor de 2N.
Exemplos:
?- quase(5, N). Correcto: 210 = 1024 e 25 é um divisor de 1024+1.
N = 2 ?- quase(2001, N).
Yes N = 308
2
Correcto: 2 = 4 e 5 é um divisor de 4 + 1. yes
?- quase(25, N). ?- quase(999999999, N).
N = 10 N = 667332
Yes yes
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL2001/A – Concurso Nacional de Programação em Lógica 2001

O Robot Atarefado
Introdução
Um Robot desloca-se numa área quadrangular como a representada na
Figura 1, onde se encontram mesas (pelo menos uma) e um armário. A
área tem NxN posições discretas, e cada mesa ocupa apenas uma M
posição, o mesmo sucedendo com o Robot e o armário. As mesas e o
armário não se tocam (ou seja, não ocupam posições contíguas). R
A
O Robot tem por função transportar livros que estão em cima das
mesas para o armário. Todas as mesas têm um (e um só) livro sobre M
Y
elas. M
Considere-se ainda o seguinte: M
• o Robot pode deslocar-se na horizontal e na vertical, mas não na
diagonal;
• em cada passo do seu movimento, o Robot desloca-se uma casa;
X
• o Robot só pode transportar um livro de cada vez;
• para pegar num livro existente numa mesa, o Robot tem que passar pela casa ocupada por essa mesa;
• sempre que o Robot passe por uma casa onde exista uma mesa com livro, pega obrigatoriamente nesse
livro;
• para pousar no armário um livro que esteja a transportar, o Robot tem que passar pela casa ocupada pelo
armário; quando tal acontece, o Robot pousa obrigatoriamente o livro no armário;
• o Robot não pode passar por uma casa ocupada por uma mesa excepto para pegar no livro nela existente;
o Robot não pode passar pela casa ocupada pelo armário excepto para pousar um livro nele.
Quando inicia a sua tarefa, o Robot começa por se deslocar para uma
mesa, onde pega no livro nela existente. Depois, transporta-o para o
armário. Visita depois cada uma das restantes mesas, transportando de M
cada uma delas um livro para o armário. R
A Figura seguinte 2 ilustra um dos caminhos possíveis para o exemplo. A
M
Tarefa Y
M
Escrever um programa Prolog atarefado/5 que produza um M
caminho completo do Robot, desde a sua posição inicial até ao
momento em que conclui a sua tarefa. Um caminho entre duas
posições A e B é uma sequência de posições contíguas, representadas X
por pares X/Y, que começa em A e termina em B.
Dados
O programa será invocado através do predicado
atarefado(R, LMesas, A, N, Caminho)
que deverá ter os quatro primeiros argumentos instanciados: R com a posição inicial do Robot; LMesas com a
lista das posições ocupadas pelas mesas; A com a posição ocupada pelo armário; e N com a dimensão da
área.

Resultados
O programa deverá instanciar Caminho, o quinto argumento, com a lista de posições percorridas pelo Robot
na resolução do problema. Apenas se exige uma solução.

Exemplos:
?- atarefado(1/1, [3/3], 1/3, 3, Caminho).
Caminho = [1 / 1,2 / 1,3 / 1,3 / 2,3 / 3,2 / 3,1 / 3]
yes

?- atarefado(1/1, [3/1, 3/3], 1/3, 3, Caminho).


Caminho = [1 / 1,2 / 1,3 / 1,2 / 1,1 / 1,1 / 2,1 / 3,2 / 3,3 / 3,2
/ 3,1 / 3]
yes

?- atarefado(1/1, [3/2, 3/5, 5/3], 1/3, 5, Caminho).


Caminho = [1 / 1,2 / 1,3 / 1,3 / 2,2 / 2,1 / 2,1 / 3,2 / 3,3 / 3,3
/ 4,3 / 5,2 / 5,1 / 5,1 / 4,1 / 3,2 / 3,3 / 3,4 / 3,5 / 3
4 / 3,3 / 3,2 / 3,1 / 3]
yes

?- atarefado(1/1, [3/3, 5/3], 1/3, 5, Caminho).


Caminho = [1 / 1,2 / 1,3 / 1,3 / 2,3 / 3,2 / 3,1 / 3,2 / 3,2 / 4,3
/ 4,4 / 4,5 / 4,5 / 3,4 / 3,4 / 4,3 / 4,2 / 4,1 / 4,1 / 3
yes

?- atarefado(1/1, [1/5], 1/3, 5, Caminho).


Caminho = [1 / 1,1 / 2,2 / 2,2 / 3,2 / 4,1 / 4,1 / 5,1 / 4,1 / 3]
yes

?- atarefado(1/2, [5/2, 7/2, 9/2], 3/2, 9, Caminho).


Caminho = [1 / 2,2 / 2,2 / 3,3 / 3,4 / 3,5 / 3,5 / 2,4 / 2,3 / 2,4
/ 2,4 / 3,5 / 3,6 / 3,7 / 3,7 / 2,6 / 2,6 / 3,5 / 3,4 / 3,3 / 3,3 /
2,4 / 2,4 / 3,5 / 3,6 / 3,7 / 3,8 / 3,9 / 3,9 / 2,8 / 2,8 / 3,7 /
3,6 / 3,5 / 3,4 / 3,3 / 3,3 / 2]
yes
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL2001/B – Concurso Nacional de Programação em Lógica 2001

Diagnóstico de Circuitos
Introdução
Algumas das avarias mais frequentes em portas (“ gates” ) de circuitos digitais, conhecidas por “ stuck-at-0” e
“ stuck-at-1” , fazem com que as saídas dessas fiquem fixas num valor lógico constante, (0 ou 1,
respectivamente) independentemente do valor das entradas.
Pretende-se que defina um predicado, circuito/2, que para um circuito constituído exclusivamente por portas
NAND, permita não só avaliar o seu funcionamento correcto mas também diagnosticar a existência de uma
ou mais portas avariadas. Esse predicado, tem os seguintes argumentos:
• Portas lista de portas NAND. Cada porta é representada pelo functor nand/3 com argumentos:
− Um identificador único da porta (um inteiro)
− Uma lista de entradas da porta
− A saída da porta
• Avarias lista de avarias. Cada avaria é representada por um par N/S em que N é o identificador da gate
avariada e S o tipo da avaria (s0 ou s1).
As entradas e saídas das portas podem ser constantes booleanas (0 ou 1) ou variáveis, sendo uma ligação
entre uma saída e uma variável representada pela mesma variável
Exemplo: O circuito seguinte que, sem avarias, representa as condições D { B t A e E { A t B

2 D
A

1 C

3 E
B
pode ser testado chamando-se o predicado
circuit o( [ nand( 1,[ A,B] ,C) , nand( 2,[ A,C] ,D) , nand( 3,[ B,C] ,E) ] ,L)
em que as variáveis A,B, C, D e E podem ser instanciadas a valores booleanos (0 ou 1) e a lista de avarias L
pode ser igualmente instanciada, total ou parcialmente.

Exemplos:
?- circuit o( [ nand( 1,[ A,B] ,C) , nand( 2,[ A,C] ,D) , nand( 3,[ B,C] ,E) ] , [ ] ) , A= 1, B= 0.
C = 1,
D = 0, E = 1;
no % sem gat es avariadas é falso que B t A ( D = 0 ) é verdadeiro que A t B ( E= 1)
?- circuit o( [ nand( 1,[ A,B] ,C) , nand( 2,[ A,C] ,D) , nand( 3,[ B,C] ,E) ] , [ ] ) , D= 0, E= 0.
no % sem gat es avariadas é im possível ser falso que B t A e A t B

?- circuit o( [ nand( 1,[ A,B] ,C) , nand( 2,[ A,C] ,D) , nand( 3,[ B,C] ,E) ] , [ ] ) , A= B.
A = 1, B = 1, C = 0,
D = 1, E = 1;
A = 0, B = 0, C = 1,
D = 1, E = 1;
no % sem gat es avariadas, se A= B ent ão é B t A e A t B

?- circuit o( [ nand( 1,[ A,B] ,C) , nand( 2,[ A,C] ,D) , nand( 3,[ B,C] ,E) ] , [ ] ) , D= E.
A = 1, B = 1, C = 0,
D = 1, E = 1;
A = 0 , B = 0, C = 1,
D = 1 , E = 1;
no % sem gat es avariadas, é im possível B t A e A t B serem am bos falsos ( D= E= 0 )
% sem gat es avariadas, se B t A e A t B ( D = E= 1 ) ent ão deve ser A = B

?- circuit o( [ nand( 1,[ A,B] ,C) , nand( 2,[ A,C] ,D) , nand( 3,[ B,C] ,E) ] , [ X] ) , A= 1, B= 1, D= 0, E= 1.
A = 1, B = 1, C = 0, D = 0, E = 1 ,
X = 2 / s0 ;
no % se A= B= 1 , não é possível ser falso B t A. O circuit o t em um a avaria. Com o E= 1, a
% única avaria que j ust ifica o erro, envolvendo um a só port a é a port a 2 est ar st uck- at - 0.

?- circuit o( [ nand( 1,[ A,B] ,C) , nand( 2,[ A,C] ,D) , nand( 3,[ B,C] ,E) ] , [ X] ) , A= 1, B= 1, D= 0.
A = 1, B = 1, C = 0, D = 0, E = 1 ,
X = 2 / s0 ;
A = 1, B = 1, C = 1, D = 0, E = 0,
X = 1 / s1 ;
No % se A= B= 1 , não é possível ser falso B t A. O circuit o t em pois um a avaria. Com E= 1, a
% única avaria que j ust ifica o erro, envolvendo um a só port a é a port a 2 est ar st uck- at - 0.
% Com E= 0, a única avaria envolvendo um a só port a, é a port a 1 est ar st uck- at - 1.

?- circuit o( [ nand( 1,[ A,B] ,C) , nand( 2,[ A,C] ,D) , nand( 3,[ B,C] ,E) ] , L) , A= 0, B= 0, D= 0, E= 0.
A = 0, B = 0, C = 1 , D = 0, E = 0 ,
L = [ 2 / s0 , 3 / s0 ] ;
A = 0, B = 0, C = 0, D = 0, E = 0,
L = [ 1 / s0 , 2 / s0 , 3 / s0 ] ;
A = 0, B = 0, C = 1 ,D = 0, E = 0 ,
L = [ 1 / s1 , 2 / s0 , 3 / s0 ] ;
no. % se A= B= 0 , não é possível ser falso B t A nem é possível ser falso B t A. O circuit o
% t em pois avarias. Os dois erros nas saídas só são explicadas com , pelo m enos, duas
% port as avariadas.
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL2000/A – Concurso Nacional de Programação em Lógica 2000

A Aposta: O conteúdo secreto das 9 caixas


Introdução
Um amigo apostou consigo o dinheiro (PTE) que estava em nove caixas se adivinhasse o conteúdo dessas
caixas. As caixas estão dispostas num quadrado de 3 linhas e 3 colunas, como indicado na figura abaixo.

C1 C2 C3

C4 C5 C6

C7 C8 C9

Para o ajudar a adivinhar, o seu amigo deu-lhe as seguintes pistas:

1. Todas as caixas têm uma e uma só nota;


2. Há uma nota de 1000 em cada linha;
3. Nas quatro caixas dos cantos estão três notas de 500;
4. Há duas notas de 2000 na segunda linha;
5. Nas caixas da terceira coluna existem duas notas de 1000;
6. A única nota de 5000 não está na terceira linha.

Tarefa
Atendendo a que, para ganhar a aposta, tem de:

• descobrir o conteúdo de cada caixa;


• provar que não há mais do que uma solução (doutra forma, a sua solução podia não coincidir
com a ``real'
'e assim perderia a aposta).

escreva um programa Prolog que lhe permita vencer a aposta e, já agora, calcular quanto vai ganhar.

Resultados

O programa deve ser invocado através do predicado aposta/0 que mostrará no ecrã uma lista com os
valores das notas existentes nas caixas C1 a C9 (por esta ordem).
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL2000/B – Concurso Nacional de Programação em Lógica 2000

Carreiras de Autocarro
Introdução
O problema seguinte foi inspirado num outro proposto nas Olimpíadas Internacionais de Informática, IOI’94:
Um homem chega a uma paragem de autocarro às 9H00 de um dia e ai permanece 60 minutos (até
às 9H59), anotando o momento de chegada de cada autocarro que ai pára (apenas regista os
minutos). Note que esse Observador poderia ter escolhido outra qualquer hora do dia, pois o
padrão de passagem dos autocarros repete-se de hora a hora.
Os autocarros podem pertencer a carreiras diferentes e podem chegar no mesmo momento, mas os
autocarros da mesma carreira chegam sempre a intervalos regulares (tendo cada carreira
frequências de passagem diferentes).
O Observador em causa constatou, ainda, que todas as carreiras ai pararam pelo menos duas
vezes.

Tarefa
Escreva um programa Prolog que, dada a lista ordenada com os minutos de paragem de cada autocarro
observado, indique um conjunto de carreiras em que eles podem ser agrupados, identificando a respectiva
frequência.

Dados
É dada a lista de minutos correspondente às paragens registadas, na forma do primeiro argumento do
predicado agrupa/2 pelo qual o seu programa deve ser invocado.

Resultados
O resultado esperado, conjunto de carreiras em que os autocarros observados podem ser agrupados de modo a
satisfazer as restrições acima indicadas, deve ser apresentado na forma de uma lista de pares em que o
primeiro elemento do par é o minuto correspondente à primeira paragem dessa carreira e o segundo elemento
do par é a frequência de paragem respectiva.
Essa lista unificará com a variável passada como segundo argumento do referido predicado
agrupa/2.

Exemplo:
?- agrupa([0,4,5,7,10,20,20,24,30,35,37,40,44,50,50], Carreiras).
Carreiras = [(0,10),(4,20),(5,15),(7,30)]
yes
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL2000/C – Concurso Nacional de Programação em Lógica 2000

Etiquetar cidades dum Mapa


Introdução
Suponha-se no papel de um cartógrafo cuja missão é etiquetar um novo mapa em desenvolvimento,
associando a cada cidade marcada o respectivo nome (palavra única).
O mapa é visto como uma grelha quadriculada C * L, C -número de quadrículas na horizontal (eixo XX' )eL
- número de quadrículas na vertical (eixo YY' ). Cada cidade é representada por um ponto que ocupa
justamente uma quadrícula. O nome deve ser escrito numa linha horizontal (totalmente dentro da grelha do
mapa, como é óbvio), começando numa quadrícula que toca à direita a quadrícula da cidade, na linha de cima
ou de baixo, ou então acabando na quadrícula que toca à esquerda a quadrícula da cidade, também, na linha
de cima ou na de baixo.
Como também é evidente, as etiquetas correspondentes aos nomes das cidades não se podem sobrepor umas
às outras, nem às cidades; entre 2 etiquetas que fiquem na mesma linha terá de existir, no mínimo, 1 espaço
separador (para que as palavras não se colem uma à outra).
A figura abaixo ilustra as 4 posições que um nome (no exemplo BRAGA) pode ocupar em relação à
quadrícula (no exemplo assinalada com X) que representa a respectiva cidade:

B R A GA B RA G A

B R A GA B RA G A

Tarefa
Escreva um programa Prolog que proceda à etiquetação do dito mapa, sendo conhecidas as suas dimensões, a
posição das cidades e o respectivo nome.

Dados
As dimensões (C*L) do mapa são definidas pelo predicado binário dimensoes/2 (que deve supor existente
na sua BC).
As cidades serão definidas através de uma lista de triplos, que constitui o argumento único do predicado
cidades/1 (suponha-o também existente na sua BC), em que o primeiro e segundo elementos são as
coordenadas (X,Y) da quadrícula que representa a cidade, e o terceiro elemento é o nome da cidade.
Note que as colunas são numeradas a partir de 0 (zero) da esquerda para a direita e as linhas, também, a partir
de 0 (zero) de baixo para cima; a quadrícula (0,0) corresponderá ao canto inferior esquerdo do mapa.
Resultados
O resultado esperado, colocação das etiquetas junto de cada cidade, será produzido ao invocar o predicado
sem argumentos etiqueta/0, devendo ser apresentado na forma de uma lista de triplos (coordenadas da
quadrícula onde começará cada etiqueta, e respectivo nome) que constituirá o argumento único do predicado
nomes/1 a ser acrescentado à BC.

Exemplo

Supondo que a BC contém os predicados:


dimensoes(15,8).
cidades([(1,5,barcelos), (2,1,famalicao),(4,2,guimaraes),(4,4,braga)]).
após activar o programa
?- etiqueta.
yes
a BC deverá conter a seguinte solução (uma entre várias possíveis)
nomes([(2,6,barcelos),(3,0,famalicao),(5,3,guimaraes),(5,5,braga)] ).
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL2000/D – Concurso Nacional de Programação em Lógica 2000

Gruas em Comboio
Introdução
O problema seguinte foi inspirado num outro descrito em Jogos da Mente, Puzzlegramas, Temas e
Debates, 1994:
Uma fila compacta de camiões-grua que se dirige de Norte para Sul encontra-se com uma fila semelhante
que se dirige de Sul para Norte. A estrada não permite o cruzamento de dois camiões e os camiões não
podem recuar. No entanto, um camião-grua pode levantar um outro camião-grua que lhe esteja à frente e
pô-lo atrás, desde que para tal exista um espaço livre de 15 metros; notar que os camiões da fila que vai
para Norte têm a frente para Norte e vice-versa. Supondo que o espaço entre os camiões à frente de cada fila
é precisamente de 15 metros e que as filas têm cada uma n camiões, pretende-se saber se as filas poderão
continuar o seu caminho e como.
Tarefa
Escreva um programa que resolva problemas do tipo anterior, descrevendo as várias operações
necessárias para que as filas continuem se tal for possível.
Dados
É dado o comprimento das filas (que são iguais).
Resultados
O programa deve ser posto em execução chamando o predicado gruas/1 cujo argumento é o dado.
O programa deve imprimir a sequência de operações a realizar como no exemplo abaixo, ou indicar
que não existe solução.
Exemplo:
?- gruas(2).
a-ultimo a-1 :___: b-1 b-ultimo
a-ultimo :___: a-1 b-1 b-ultimo
a-ultimo b-1 a-1 :___: b-ultimo
a-ultimo b-1 a-1 b-ultimo :___:
a-ultimo b-1 :___: b-ultimo a-1
:___: b-1 a-ultimo b-ultimo a-1
b-1 :___: a-ultimo b-ultimo a-1
b-1 b-ultimo a-ultimo :___: a-1
yes
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL2000/E – Concurso Nacional de Programação em Lógica 2000

O Problema de Langford
Introdução
Este problema é adaptado de Mathematical Magic Show, de Martin Gardner. O matemático escocês
C. Dudley Langford, ao ver o seu filho brincar com 3 pares de cubos de cores diferentes, verificou
que existia 1 cubo entre os cubos vermelhos, 2 cubos entre os amarelos e 3 cubos entre os azuis.
Substituindo as cores por números, a sequência pode ser representada por
312132

O problema pode ser generalizado para um número maior de pares:


Dado um número N, determinar uma sequência de 2 N números que contenha, para todo o 1 ≤ k ≤ N, uma
sequência Sk = k, ..., k começada e terminada com o número k e com k outros números de permeio.
Tarefa
A sua tarefa consiste em escrever um programa Prolog que, dado o número N, solucione o desafio
acima colocado.
Resultados
O programa deve ser invocado através do predicado
langford(+N,-L)
que determina, para o número N dado, uma lista L que constitui uma sequência de Langford. Deve
ser possível, por backtracking, obter todas as listas L que são sequências de Langford, não se
permitindo repetições obtidas por inversão da lista.
Exemplo
?- langford(3,L).
L = [3,1,2,1,3,2];
no
ou então
?- langford(3,L).
L = [2,3,1,2,1,3];
no
Será que o seu programa pode ser usado para determinar quantas soluções existem para N = 6 e N =
7? E para N = 9?
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL2000/F – Concurso Nacional de Programação em Lógica 2000

Mini Batalha Naval


Introdução
Num tabuleiro de NxN casas pretende-se dispor M peças como a representada na Figura 1.

Figura 1
A colocação das peças no tabuleiro obedece às regras da Batalha Naval: as peças não se podem tocar (nem
nos cantos), e podem ser rodadas, pelo que podem assumir as 4 formas diferentes que se representam na
Figura 2.

Figura 2
Na Figura 3 está representada uma possível solução para a colocação de 3 peças num tabuleiro de dimensão
5.

Figura 3
Tarefa
Escrever um programa Prolog que produza uma solução para a colocação de M peças num tabuleiro quadrado
de dimensão N.
Dados
O programa deverá ser invocado através do predicado minibn/2 que deverá ter os dois argumentos
instanciados: o primeiro com a dimensão do tabuleiro, o segundo com o número de peças a colocar.
Resultados
O programa deverá encontrar uma solução para o problema e desenhar o tabuleiro resultante no ecrã. As
casas ocupadas por peças deverão ser representadas no ecrã pela letra '
p', enquanto que as casas não ocupadas
deverão ser representadas pelo símbolo '
.'(ponto final). Caso não o programa não encontre nenhuma solução,
deverá falhar.
Exemplos:
?- minibn(4,2).
no
?- minibn(5,3).
Solucao:
Linha 1: ppp..
Linha 2: .p..p
Linha 3: ...pp
Linha 4: .p..p
Linha 5: ppp..
yes
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL2000/G – Concurso Nacional de Programação em Lógica 2000

Jogo das Moedas


Introdução
Seis moedas iguais estão colocadas em cima duma mesa, formando um paralelograma conforme ilustrado na
figura. Apenas se pode mover uma moeda de cada vez, e sem nunca a levantar da mesa. Um movimento só é
válido se a moeda que se move:
• não afecta a posição de nenhuma
outra moeda; 1 2 3
• fica numa posição em que toca pelo 4 5 6
menos duas outras moedas.
O objectivo do jogo é, com apenas 3 movimentos, colocar as moedas em círculo, onde no meio caberia à
justa uma moeda (conforme se ilustra na figura 2).

1 1
4 2 3 1 3 2 3
5 6 2 4 5 6 4 5 6

Note, por exemplo, que os movimentos das figuras 3 e 4 não são válidos. No primeiro caso, a moeda 2, ao
mover-se afecta a posição das moedas 1 e 4 (têm que ser afastadas para ela passar). No segundo caso, a
moeda 2 (que se move) fica apenas a tocar numa moeda.
Mas o movimento da figura 5 já é válido. Apesar de após o movimento a moeda 4 só ficar a tocar numa outra
moeda, a moeda que se move (neste caso a 1) fica a tocar em duas. E é só sobre a moeda que se move que se
impõe esta restrição.
Tarefa
Escrever um programa Prolog que resolva este problema, ou seja, que devolva uma lista com 3 movimentos
de moeda que, a partir do paralelograma inicial, coloque as moedas em círculo.
Resultados
O programa deverá ser posto em execução chamando o predicado moedas/1 cujo argumento de saída deverá
ser instanciado com uma sequência (i.e. lista) de 3 movimentos que resolva o problema. Para identificação, as
moedas são numeradas de 1 a 6, sendo a situação inicial a da figura 1.
Cada movimento deverá ser um termo da forma move(Moeda,ListaAdjacentes) que representa o movimento
da moeda Moeda para junto das moedas ListaAdjacentes. Por exemplo, o movimento da figura 5 seria
representado por move(1,[2,3]).
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL2000/H – Concurso Nacional de Programação em Lógica 2000

Jogo do Nim
Introdução
O Nim joga-se com pilhas de feijões e foi primeiro estudado por Charles Bouton em 1901.1 Neste
jogo, cada jogador, quando lhe toca jogar, pode retirar feijões de uma só pilha, mas quantos quizer,
de um mínimo de um a um máximo de toda a pilha. Ganha o jogador que retirar o último punhado
de feijões.
Tarefa
A sua tarefa consiste em escrever um programa Prolog que permita jogar o Nim sem nunca perder!
Nímeros e o jogo do Nim
``Nímero' 'é a palavra que resulta de se combinar ``Nim'
'com ``número''.2 Os nímeros têm uma nova
aritmética, para a qual usaremos aqui os símbolos ⊕ e ≡ . Quais são as posições boas que convém
atingir num jogo Nim? Se se estiver a jogar Nim apenas com um monte, a resposta é fácil. É bom
passar para 0 e é mau passar para qualquer outro nímero 1, 2, 3, 4, 5, 6, ... visto que então o
oponente poderá contra-atacar, passando para 0. Eis o que é necessário saber sobre a adição de
nímeros:
• A soma de dois nímeros iguais é sempre zero.
• Se o maior de dois nímeros for uma potência de 2: 1 ou 2 ou 4 ou 8 ou 16 ou ..., eles somam-se como
os correspondentes números ordinários.
Com estas regras e as leis da álgebra, pode fazer-se aritmética Nim. Exemplo:
5 ⊕6 ≡ (4 ⊕1) ⊕(4 ⊕2) ≡ (4 ⊕4) ⊕(1 ⊕2) ≡ 3.

Qualquer conjunto de montes Nim, a, b, c, ... pode ser substituído por um monte único de tamanho a
⊕b ⊕c⊕…. Assim, a resposta à questão colocada será: deverá tentar--se jogar para uma situação
a, b, c, ... para a qual se tenha a ⊕b ⊕c ⊕… ≡ 0. Vejamos um exemplo. Se o utilizador passou para
3, 5, 7 (ou tiver escolhido esta como configuração inicial) então, como 3 ⊕5 ⊕7 ≡ (2 ⊕1) ⊕(4 ⊕1)
⊕7 ≡ 6 ⊕7 ≡ … ≡ 1 o seu programa deverá jogar para uma das possibilidades 2, 5, 7 ou 3, 4, 7 ou
3, 5, 6 que somam zero: 2 ⊕5 ⊕7 ≡ 3 ⊕4 ⊕7 ≡ 3 ⊕5 ⊕6 ≡ 0. Prova-se que é sempre possível passar
de uma situação perdente para uma ganhante. Para transformar uma situação perdente numa
ganhante, somamos o valor do tamanho da situação com o número de feijões de uma determinada
pilha. Se o resultado for inferior ou igual ao número de feijões da pilha, devemos tirar elementos
dessa pilha de modo a deixar lá esse resultado. Se o resultado for superior, devemos tirar feijões de
uma outra pilha. Por exemplo, da situação perdente 1, 8, 4, 7 (que tem tamanho 1 ⊕8 ⊕4 ⊕7 ≡ 10)
podemos passar para a situação ganhante 1, 2, 4, 7: 10 ⊕8 ≡ 2 < 8 e 1 ⊕2 ⊕4 ⊕7 ≡ 0.
Dados
Os dados a considerar neste problema são o número de pilhas e o número de feijões em cada pilha.
Estes dados serão especificados pelo utilizador por uma lista Pilhas do tipo [NumFeijPilha1,
..., NumFeijPilhaN] aquando da chamada ao predicado nim(Pilhas). Antes do início do jogo
propriamente dito, o seu programa deve usar o predicado soma(Pilhas,T), que devolve em T a
soma dos nímeros em Pilhas, para decidir se começa ele ou se, pelo contrário, opta por deixar o
utilizador fazer a primeira jogada. Todas as jogadas do computador devem ser realizadas à custa do
predicado proximaSituacao(SP,SG) que, dada uma situacao perdente SP, devolve uma situação
ganhante SG (outras situações ganhantes deverão poder ser obtidas por backtracking).
Resultados
Seguem-se alguns exemplos:
?- soma([1,8,4,7],S).
S = 10
?- proximaSituacao([1,8,4,7],PS).
PS = [1,2,4,7];
no
?- nim([1,1,2,3,1]). % Computador cede a 1a jogada ao utilizador
--> Utilizador joga para: [1,1,2,0,1]. % por exemplo
--> Computador joga para: [1,1,1,0,1]
--> Utilizador joga para: ........... % etc.

1
Mais informação sobre este e outros jogos do mesmo tipo, pode ser encontrada no Boletim no 40 da Sociedade Portuguesa de
Matemática no artigo Jogórios de Jorge Nuno Silva.
2
J. H. Conway e R. K. Guy, O Livro dos Números, colecção Gradiva Universidade de Aveiro, 1999.
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL2000/H – Concurso Nacional de Programação em Lógica 2000

Mas que Raio: Uma Caixa com Espelhos


Introdução
Existe uma caixa (fechada) que contém espelhos. Ao emitir um raio luminoso por uma fresta em uma face, o
raio reflecte-se nos espelhos e sai por outra face (eventualmente a mesma). É claro que pode haver várias
configurações de espelhos que justifiquem o comportamento (entrada e saida) de um raio. Mas se fizermos
incidir mais raios podemos esclarecer melhor qual a disposição interna dos espelhos .... As faces da caixa são
identificadas por:
• i para a face inferior;
• e para a face esquerda;
• s para a face superior;
• d para a face direita;
Cada espelho pode estar colocado em diagonal (direita) (d), ou diagonal esquerda (e). Uma disposição de
espelhos pode ser representada por uma lista de termos (X/Y+E) em que X e Y representam coordenadas e E
representa a posição do espelho (direita ou esquerda).
Figura 1: A caixa com três espelhos
[1/1+d,2/1+d,2/2+e].

Para representar os raios indicam-se as coordenadas de entrada e saida.


Note que:
1. Os espelhos estão colocados apenas em diagonal, (i.e. não há espelhos horizontais nem verticais);
2. Os espelhos são espelhados de ambos os lados (note o exemplo dado).
Figura 2: Uma disposição para os raios
[(1+i,2+e),(1+e,3+s)].
Tarefa
Dado uma lista de raios (i.e. de pares entrada, saida), descobrir a disposição dos espelhos dentro da caixa que
justifica o comportamento dos raios apresentados.
Dados
Um raio (par (entrada,saida)) é representado na forma: (ce+fe, cf+fs) em que ce (resp. cf)
representa a coordenada na face do fe (resp. fs). A caixa é quadrada.
Resultados
O programa deve ser invocado através do predicado:
raios(ListaDeRaios,Tabuleiro,Dimensao)
em que ListaDeRaios é uma lista de raios (pares entrada-saida) e Tabuleiro é a disposição dos espelhos.
Exemplo
Como exemplo tem-se (veja a Figura ) à pergunta:
?- raios([(1+i,2+e),(1+e,1+s)],Tabuleiro).
uma resposta (das 14 existentes) é :
Tabuleiro = [1/1+d,2/1+d,2/2+e]
Para referência apresentam-se as soluções para a lista de raios acima.

Note que poderá haver outras soluções obtidas a partir destas 14 acrescentando espelhos que não são usados
para reflectir raios. É claro que estas não têm qualquer interesse.
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL2000/I – Concurso Nacional de Programação em Lógica 2000

Sangradura
Introdução
Os cincos duques de Sangradura (Bóris, o Cruel; Bóris, o Impiedoso; Ferdinando, o Selvagem; Ivan, o
Flagelador; e Klaus, o Inqualificável) decidiram declarar guerra a países vizinhos, usando pretextos fúteis.
Partindo da seguinte informação:
• O governador de Pacífica provocou a ira de um duque de Sangradura, que não era Klaus, o
Inqualificável, aprovando uma lei anti-violência que o duque considerou contrária à ordem natural da
criação e, portanto, herética;
• Bóris, o Cruel, levou dez dias para triunfar sobre o seu inimigo;
• A guerra mais longa não foi travada contra Betífica;
• Foi o duque Ferdinando, o Selvagem, quem declarou guerra quando o monarca de um país vizinho se
esqueceu de o cumprimentar pelo seu aniversário com um presente apropriado;
• A vítima de Ferdinando, o Selvagem, não foi Misericórdia;
• A guerra mais curta foi a declarada quando alguns pombos de um país vizinho invadiram o espaço
aéreo do ducado de Sangradura;
• A guerra de Ivan, o Flagelador, foi contra Inocência, que não era o país onde hastearam a bandeira de
Sangradura de cabeça para baixo, ofendendo o duque que estava de visita oficial à capital do país
inimigo;
• A guerra com Miserircórdia durou sete dias; Felícia não conseguiu resistir tanto;
• Chamava-se Bóris o duque que reinava quando um cidadão inocente de Sangradura foi injustamente
feito prisioneiro por um estado vizinho, por ter assassinado acidentalmente um habitante local a quem
devia dinheiro;
determine, para cada duque, o país atacado, o motivo da guerra e quantos dias se passaram em cada caso (2,
4, 7, 10 ou 13 dias), até ao triunfo inevitável das forças de Sangradura.
Tarefa
A sua tarefa consiste em escrever um programa Prolog que solucione o desafio acima colocado.
Resultados
O programa deve ser invocado através do predicado sangradura/0 que mostrará no ecrã os cinco quádruplos
[Duque,País,Pretexto,DuraçãoDaGuerra] que são consequência lógica da informação dada.

1
A fonte de inspiração foi o Jornal ‘‘O Integral’’ no 5, Ano III, do Clube de Matemática - Univ. Aveiro
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL2000/J – Concurso Nacional de Programação em Lógica 2000

Vira Virando: Variações sobre o “ Vira”


Introdução
Dada uma árvore e um nó qualquer da mesma, pretende-se ``virar'
'a árvore do avesso, de tal modo que o nó
dado passe a ser a raíz.
Mais formalmente: defina-se árvore como sendo um grafo dirigido acíclico, conexo e tal que cada nó tem um
só antecessor, com excepção da raíz, a qual não tem antecessores. Considere-se a transformação T(A) que
obtem um grafo não dirigido a partir da árvore A substituíndo cada arco i → j por um arco não dirigido i - j.
Seja n um nó de A. Pretende-se obter a árvore V que tenha como raíz o nó n e tal que T(V) e T(A) são o
mesmo grafo.
Tarefa
Escreva um programa que resolva problemas do tipo anterior, em que as árvores são representadas por termos
da forma n(N,L) com N um inteiro único que distingue o nó e L uma lista, eventualmente vazia, de árvores
que são os descendentes do nó.
Dados
É dada uma árvore n(M,L) e um inteiro N que identifica um nó da árvore. Pressupõe-se que todos os nós da
árvore têm números diferentes.
Resultados
O programa deve ser posto em execução chamando o predicado vira/3 cujos argumentos são os dados e a
árvore resultado.
Exemplos
?- vira(n(1,[n(2,[n(4,[]),n(5,[]),n(6,[])]),
n(3,[n(7,[]),n(8,[n(9,[]),n(10,[])])])]),
7,V).
V = n(7,[n(3,[n(1,[n(2,[n(4,[]),n(5,[]),n(6,[])])]),
n(8,[n(9,[]),n(10,[])])])]) ? ;
no

?- vira(n(1,[n(2,[n(4,[]),n(5,[]),n(6,[])]),
n(3,[n(7,[]),n(8,[n(9,[]),n(10,[])])])]),
3,V).
V = n(3,[n(7,[]),
n(8,[n(9,[]),n(10,[])]),
n(1,[n(2,[n(4,[]),n(5,[]),n(6,[])])])]) ? ;
no
?- vira(n(11,[n(12,[]),
n(1,[n(2,[n(4,[]),n(5,[]),n(6,[])]),
n(3,[n(7,[]),
n(8,[n(9,[]),n(10,[])])])])]),
3,V).
V = n(3,[n(7,[]),
n(8,[n(9,[]),n(10,[])]),
n(1,[n(11,[n(12,[])]),
n(2,[n(4,[]),n(5,[]),n(6,[])])])]) ? ;
no
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL99/A – Concurso Nacional de Programação em Lógica 1999

Labirinto da Cobra
Introdução
Uma cobra move-se num labirinto onde existem vários obstáculos e vários ovos. O objectivo da cobra é,
partindo de uma posição inicial pré-definida, comer todos os ovos existentes no labirinto (i.e. passar por todos
os pontos em que existem ovos) sem chocar contra os obstáculos nem contra si própria.
Inicialmente a cobra é pequena mas de cada vez que come um ovo o seu tamanho aumenta de uma unidade.
Durante o jogo a cobra pode mover-se para a direita, esquerda, para cima e para baixo (sem nunca sair do
labirinto, claro).
Tarefa
Escrever um programa em Prolog que, dado um labirinto conforme descrito abaixo, devolva os movimentos
que a cobra deve fazer para comer todos os ovos. Os movimentos possiveis são e (mover para a esquerda), d
(mover para a direita), b (mover para baixo) e c (mover para cima).
Dados
O labirinto consiste numa matriz quadrada cuja dimensão é dada através de um facto dimensao/1, onde os
obstáculos estão assinalados por uma série de factos obstaculo(X,Y) (indicando que existe um obstáculo
na linha X coluna Y da matriz). A localização dos vários ovos é dada por uma série de factos ovo(X,Y)
(indicando que existe um ovo na linha X coluna Y da matriz).
Inicialmente a cobra tem dimensão 1 (i.e. ocupa apenas uma posição da matriz) e a está na linha 1 coluna 1 da
matriz (posição essa que garantidamente não tem nenhum obstáculo nem ovo).
De cada vez que a cobra come um ovo (i.e. passa pela primeira vez numa posição que tenha um ovo) o seu
tamanho aumenta de uma unidade.
Resultados
O programa deve ser posto em execução chamando o predicado movimentos/1 cujo argumento de saida
deverá ser instanciado com uma lista de movimentos que resolva o problema da cobra. A descrição do
labirinto está num outro ficheiro que, para testar o seu programa, será consultado previamente.
Exemplo
Com a seguinte descrição do labirinto:
dimensao(4).
uma solução seria:
obstaculo(2,2).
obstaculo(3,2). ?- movimentos(M).
obstaculo(4,2). M = [d,d,d,b,e,c,e,e,b,b,b]
ovo(1,4). yes
ovo(4,1).
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL99/B – Concurso Nacional de Programação em Lógica 1999

Comandos: Controlo à Distância


Introdução
Um aparelho usado para controlar à distância o funcionamento de uma aparelhagem electrónica de som, com
sintonizador AM/FM, leitor de CDs e leitor de cassetes, aceita comandos para: ligar e desligar (ON/OFF) a
aparelhagem; escolher a modalidade que se pretende ouvir (entre os 3 dispositivos existentes). Após aceitar
um comando, o controlador à distância envia-o para o aparelho (processo esse que é irrelevante neste
contexto) e continua a aceitar novos comandos.
O comando para desligar a aparelhagem pode ser emitido em qualquer momento, após se ter ligado o
equipamento.
Só faz sentido seleccionar um modo de funcionamento depois de se ter ligado a aparelhagem.
Além disso, só são úteis as sequências de comandos que verifiquem as seguintes indicações: no caso de se
escolher o sintonizador, deve indicar-se a seguir a banda (AM ou FM) e a frequência do canal a ouvir; se a
escolha for o leitor de CDs, indicar-se-á apenas o número da pista; finalmente, optando-se pelo leitor de
cassetes, indicar-se-á a operação desejada (FF, RW, Play), ou Stop depois de uma das outras.
Note que a introdução de uma sequência inválida de comandos faz com que a aparelhagem seja
imediatamente (logo que se detecte o erro) desligada.
Tarefa
A sua tarefa é escrever um programa Prolog que dada uma sequência de comandos indique, caso a sequência
seja válida, o estado em que o aparelho fica a funcionar.
Para resolver o problema, considere que o controlador à distância pode ser encarado como uma Máquina de
Transição de Estados, e então implemente o autómato determinista que modela o sistema.
Dados
Várias listas-exemplo (com sequências de comandos a testar) estão no ficheiro ’cnpl99p3.dat’, o qual
deverá ser consultado para acrescentar à BC o predicado comandos/1.
Para representar cada comando é usado o seguinte alfabeto (conjunto de símbolos terminais):
{ on, off,
sint, lcd, lcasset,
am, fm, freq, pista, ff, rw, play, stop }
Resultados
O programa deve ser chamado através do seguinte predicado:
simulaControlador( Cmds,Estado ).
em que se supõe instanciado Cmds, com uma das sequências definidas em comandos/1; os resultados
virão em Estado.
Exemplo
?- simulaControlador([on],E).
E = ’ligado, espera comandos’ ;
no
?- simulaControlador([on,freq,off],E).
E = ’desligado apos erro, sequencia cmds inutil’ ;
no
?- simulaControlador([on,lcasset,rw],E).
E = ’leitor de cassetes a puxar a fita para tras’ ;
no
?- simulaControlador([on,lcasset,stop],E).
E = ’desligado apos erro, sequencia cmds inutil’ ;
no
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL99/C – Concurso Nacional de Programação em Lógica 1999

Cucamonga: Mães e Filhos à Mistura


Introdução
No sexto número do jornal O Integral do ``Clube de Matemática'
', pode ser encontrado o seguinte problema:
A população da aldeia de Cucamonga é constituída por seis homens (Abelardo, Béléléu, Cunta Kinté, Didol,
Eleutério e Flaneco) e pelas suas mães.
Cada uma das mães é viúva e casada, em segundas núpcias, com um dos outros cinco homens.
Assim, a esposa de Didol faz notar à mãe de Cunta Kinté que ela (mãe de Cunta Kinté) se tornou bisavó (por
afinidade) da esposa de Eleutério, que Abelardo se tornou avô de Béléléu e que a esposa de Flaneco se tornou
nora da neta da esposa de Cunta Kinté.
Quem casou com quem?
Tarefa
Escreva um programa em Prolog que resolva este jogo, i.é, determine a resposta à questão acima colocada.
Resultados
O seu programa deve ser activado através do predicado
solucao/0
que mostra no ecrã a solução para este problema:
abelardo casou com mae_?
beleleu casou com mae_?
cuntaKinte casou com mae_?
didol casou com mae_?
eleuterio casou com mae_?
flaneco casou com mae_?
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL99/D – Concurso Nacional de Programação em Lógica 1999

Pares Espelhados
Introdução
Pretende-se
1. Dada uma lista L de elementos não repetidos, formar uma lista LP de pares em espelho (X,Y) onde
X está em L e Y é o elemento da inversa de L com a mesma posição de X. A lista L só pode ser
visitada uma vez e a posição de (X,Y) na lista resultante é a posição de X em L.
2. Dado um elemento A de L e a lista LP, como na questão anterior, obter uma lista LD apagando A de
LP. Isto é, LD é a lista de pares obtida como na alínea anterior mas para a lista resultante de apagar A
em L. A lista LP só pode ser visitada uma vez.
Tarefa
Escreva um programa Prolog que resolva as duas questões anteriores, seguindo a condição de visitar uma
única vez a lista dada.
Dados
Os dados são os indicados nas questões acima. No primeiro caso, uma lista fechada com elementos quaisquer
não unificáveis. No segundo, uma lista de pares em espelho e um elemento de um par.
Resultados
O programa deve ser chamado através de um dos seguintes predicados:
forma_pares(L,LP)
apaga_elemento(LP,X,NLP)
em que se supõe instanciados: L, no primeiro, e LP e X no segundo; os resultados virão em LP e NLP,
respectivamente.
Exemplos
?- forma_pares([a,b,c,d],LP).
LP = [(a,d),(b,c),(c,b),(d,a)] ;
no

?- apaga_elemento([(a,d),(b,c),(c,b),(d,a)],b,NLP).
NLP = [(a,d),(c,c),(d,a)] ;
no
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL99/E – Concurso Nacional de Programação em Lógica 1999

Indícios e Enganos
Introdução
O problema seguinte foi adaptado de Jogos da Mente, Puzzlegramas, Temas e Debates, 1994:
Um extra-terrestre foi visto por quatro pessoas que o descreveram de maneiras diferentes. A primeira disse
que tinha pele verde, era baixo, tinha uma máscara e luvas amarelas. A segunda confirmou a máscara e as
luvas, mas disse que a pele era amarela e a estatura era média. A terceira referiu que era baixo, que a pele
não estava visível, que tinha luvas amarelas e não usava máscara. A última disse que era alto e de pele
castanha, tinha máscara e luvas verdes. Supondo que cada pessoa só acertou num dos detalhes que deu, e
que cada detalhe do extra-terrestre só foi descrito correctamente por uma pessoa, como era o extra-
terrestre?
Tarefa
Escreva um programa que resolva problemas genéricos do tipo anterior, em que há n pessoas e n tipos de
indícios, cada pessoa só acertou num indício e cada indício só foi descrito correctamente por uma pessoa.
Dados
É dada uma lista com n elementos da forma P:L, sendo P a identificação da pessoa, e L uma lista de n pares
da forma I-Obs, com I o nome de um indício e Obs o ``valor' 'observado pela pessoa P para o indício I.
Pressupõe-se que na lista dada as identificações das pessoas são únicas, que nas listas de indícios observados
há apenas n indícios diferentes e em cada lista os indícios aparecem uma e uma só vez.
Resultados
O programa deve ser posto em execução chamando o predicado indicios/1 cujo argumento é a lista com
os dados.
O programa deve imprimir todas as hipóteses compatíveis com os dados e as condições impostas no
enunciado acima.
Exemplos:
?- indicios([
p1:[pele-verde, altura-baixo, mascara-sim, luvas-amarelas],
p2:[pele-amarela, altura-media, mascara-sim, luvas-amarelas],
p3:[pele-indefinida, altura-baixo, mascara-nao, luvas-amarelas],
p4:[pele-castanha, altura-alto, mascara-sim, luvas-verdes]]).
Hipotese:
pele-verde referido por:p1
altura-media referido por:p2
mascara-nao referido por:p3
luvas-verdes referido por:p4
Nao existem (mais) hipoteses
yes

?- indicios([
p1:[pele-verde, altura-baixo, mascara-sim, luvas-amarelas,
olhos-azuis],
p2:[pele-amarela, altura-media, mascara-sim, luvas-amarelas,
olhos-pretos],
p3:[pele-indefinida, altura-baixo, mascara-nao, luvas-amarelas,
olhos-roxos],
p4:[pele-castanha, altura-alto, mascara-sim, luvas-verdes,
olhos-castanhos],
p5:[pele-roxa, altura-media, mascara-sim, luvas-azuis,
olhos-amarelos]]).
Hipotese:
pele-verde referido por:p1
olhos-pretos referido por:p2
mascara-nao referido por:p3
altura-alto referido por:p4
luvas-azuis referido por:p5
Hipotese:
olhos-azuis referido por:p1
pele-amarela referido por:p2
mascara-nao referido por:p3
altura-alto referido por:p4
luvas-azuis referido por:p5
Nao existem (mais) hipoteses
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL99/F – Concurso Nacional de Programação em Lógica 1999

Parentes: Árvore Genealógica


Introdução
Familias é um grafo orientado não-pesado em que os vértices (ou nodos) representam indivíduos
(identificados por um nome único) e a relação binária que define os ramos (ou arestas) do grafo é
eFilhoDe. Assim, se os indivíduos I1 e I2 formam um ramo (o par (I1,I2) pertence à relação), quer
dizer que I1 é filho de I2.
Tarefa
A sua tarefa é escrever um programa Prolog que, usando Familias, seja capaz de verificar se há laços de
sangue entre dois indivíduos dados, isto é se eles são parentes.
Além de verificar a consanguinidade entre os indivíduos, interessa também detectar a proximidade ou
afastamento dessa ligação, determinando o grau de parentesco entre ambos. Para esse efeito, considera-se o
grau de parentesco entre dois indivíduos como sendo a soma das distâncias de cada um ao parente comum
mais próximo; note, para isso, que a distância de um filho ao seu progenitor é igual a 1.
Dados
Necessita de uma instância do grafo Familias, que pode consultar a partir de um ficheiro.
Resultados
O programa deve ser chamado através do seguinte predicado:
consanguineos( Ind1,Ind2,Grau ).
em que se supõe instanciados Ind1 e Ind2 com nomes de indivíduos que figuram em Familias como
vértices; o resultado virá em Grau, que será igual a 0 (zero) se não houver qualquer laço de sangue entre os
dois, ou então indicará o grau de parentesco entre eles.
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL99/G – Concurso Nacional de Programação em Lógica 1999

Passwd: Gestão de Utilizadores


Introdução
O responsável pelos laboratórios e por uma sala aberta de informática pretende ter um auxiliar que lhe
proponha a palavra-secreta de cada novo utilizador dos recursos, assim que procede ao seu registo.
Tarefa
A sua tarefa é escrever um programa Prolog que, dada uma lista de N novos utilizadores, identificados pelo
seu nome, gere uma lista de N passwords todas diferentes, com L caracteres cada (letras minúsculas e dígitos)
dos quais, pelo menos um, será dígito.
Para uma password ser válida não pode ter mais de M posições com caracteres iguais a uma das outras
passwords.
Por exemplo, supondo L = 4 e M = 2, a lista seguinte contém passwords válidas
[ ’abc1’, ’bac2’, ’acb1’, ’1cab’ ]
enquanto que as passwords abaixo são inválidas
[ ’abc1’, ’abc2’, ’adc1’, ’dcab’ ]
Dados
Ser-lhe-á fornecida a lista com os nomes dos alunos, como acima se diz.
Várias listas-exemplo estão no ficheiro ’cnpl99p1.dat’, o qual dever'
a ser consultado para acrescentar à
BC o predicado alunos/1. Cada cláusula contém uma lista de nomes.
Quanto aos parâmetros L e M serão fornecidos como argumentos do predicado principal que se definirá a
seguir.
Resultados
O programa deve ser chamado através do seguinte predicado:
geraPasswd( Als,L,M, Passes ).
em que se supõe instanciados Als com uma lista de nomes (poderá ser uma das listas definidas em
alunos/1), L com o comprimento pretendido para as passwords e M com o número máximo permitido de
caracteres iguais em posições iguais; os resultados virão em Passes.
Exemplo:
?- geraPasswd( [’to’, ’rui’, ’ze’],4,2, Passes ).
Passes = [’1abc’, ’2def’, ’3ghi’] . yes
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL98/A – Concurso Nacional de Programação em Lógica 1998

Animais e Caça
Introdução
Atente ao seguinte enunciado:
Cinco amigos de nomes predestinados, Coelho, Pato, Rola, Pombo e Lebre, regressam de uma caçada.
Trazem consigo animais correspondentes aos respectivos nomes. Cada um matou um único animal, que não
corresponde, porém, ao seu nome. Cada um falhou um animal diferente do que matou e que não corresponde
também ao próprio nome. O coelho foi morto pelo caçador cujo nome é o do animal falhado por Rola. O
pato foi morto pelo caçador cujo nome é o do animal falhado por Lebre. Pato, que falhou uma lebre, ficou
muito aborrecido por ter morto apenas uma rola. Quais os bichos mortos e falhados por cada um dos
caçadores? in "Pierre Berloquin, 100 Jogos Lógicos, O Prazer da Matemática, no 4, Gradiva"
Tarefa
Escreva um programa em Prolog que resolva este jogo, i.é, determine a resposta à questão acima colocada
Resultados
O seu programa deve ser activado através do predicado
jogo/0
que mostra no ecrã os 5 triplos
Cacador/Animal_Morto/Animal_Falhado
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL98/B – Concurso Nacional de Programação em Lógica 1998

Degraus: os 10 Degraus do Miguel


Introdução
À entrada da casa do Miguel há uma escada com 10 degraus. Cada vez que entra em casa, o Miguel avança
pelas escadas subindo um ou dois degraus em cada passada. De quantas maneiras diferentes pode o Miguel
subir as escadas?" in "Pierre Berloquin, 100 Jogos Lógicos, O Prazer da Matemática, no 4, Gradiva"
Tarefa
A sua tarefa consiste em desenvolver um programa em Prolog que resolva este problema, para um número
qualquer de degraus.
Dados
O único dado necessário para a resolução deste problema é o número de degraus. Esse valor será um
parâmetro do predicado jogo_degraus que tem de desenvolver, como se explica a seguir.
Resultados
O seu programa deve ser activado através do predicado jogo_degraus/3 que recebe o número de degraus e
devolve o número de possibilidades diferentes de subir as escadas, assim como a respectiva lista de
possibilidades (cada possibilidade é, por sua vez, também representada por uma lista).
Exemplo
?-jogo_degraus(3,N,L).
N=3
L = [[1,1,1],[1,2],[2,1]]
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL98/C – Concurso Nacional de Programação em Lógica 1998

Cobre e Coberturas
Introdução
Considere que tem um tabuleiro de N x M casas e um número ilimitado de pedras de dominó (todas iguais e
sem pintas). As dimensões das casas do tabuleiro e das pedras de dominó são tais que uma pedra de dominó
cobre precisamente duas casas do tabuleiro. Se tivermos um tabuleiro 2x2 bastam duas pedras de dominó
para cobrir o tabuleiro e todas as pedras do dominó estão a cobrir alguma casa do tabuleiro, havendo assim
uma cobertura perfeita. No entanto, se retirarmos um canto do tabuleiro já não será possível cobrir o tabuleiro
de um modo perfeito. Se tivermos um tabuleiro 4x4 é fácil de verificar que são necessárias exactamente 8
pedras para cobrir perfeitamente o tabuleiro. O que acontecerá se fizermos dois buracos no tabuleiro, isto é,
se retirarmos duas casas quaisquer do tabuleiro? Será ainda possível cobrir perfeitamente o tabuleiro?
Tarefa
Implemente um programa Prolog para determinar se um tabuleiro (eventualmente com buracos) pode ser
coberto perfeitamente por peças de dominó.
Dados
Toda a informação que é necessária para executar o programa que lhe é solicitado,
será fornecida através dos argumentos do predicado usado para o activar, como se detalha a seguir. O
programa deve ser invocado com o predicado
cobertura(NLinhas,NColunas,Lista_Buracos)
em que NLinhas indica o número de linhas do tabuleiro, NColunas o número de colunas do tabuleiro e
Lista_Buracos é uma lista com as coordenadas b(L,C) dos buracos do tabuleiro.
Resultados
O predicado cobertura/3 deve ser executado com sucesso se for possível cobrir perfeitamente o tabuleiro
com pedras de dominó e deve falhar se tal não for possível.
Exemplos:
Dado que é possível cobrir perfeitamente um tabuleiro de 2x2 que tem 0 buracos ([]),
cobertura(2,2,[]) deve ser executado com sucesso:
?-cobertura(2,2,[]).
yes
uma vez que um tabuleiro de 2x2 com um buraco em 1x1, ou seja, ao qual foi retirado um canto, não pode
ser coberto perfeitamente, cobertura(2,2,[b(1,1)]) deve falhar:
?-cobertura(2,2,[b(1,1)]).
no
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL98/D – Concurso Nacional de Programação em Lógica 1998

Codificador Primo
Introdução
Consideremos os números naturais e a ideia muito simples de número primo: um número que não se pode
obter pela multiplicação de dois números mais pequenos onde não se inclui a unidade. Sabia-se desde tempos
remotos, como um facto empírico, que todos os números se exprimem, de forma única, como um produto de
primos; e os Gregos conseguiram prová-lo.
Algumas perguntas sobre primos surgem naturalmente. Por exemplo: Quantos primos existem e como estão
os primos distribuídos entre os outros naturais? Como se determinam primos? Como é que se mostra que um
dado natural é, ou não, um primo? Como se factoriza, num produto de primos, um natural arbitrário?
Mesmo com tão simples matéria-prima, a matemática fez uma obra espantosa e, se questões como as acima
colocadas, podem ser olhadas numa perspectiva abstracta e como um ramo da matemática inaplicável, a
verdade é que estas questões têm um significado crucial para a criptografia; tanto mais que têm havido sérias
tentativas de classificar alguns dos resultados desta área como segredos militares.
Tudo indica que são os métodos de codificação baseados em números primos que constituirão a parte central
do sistema de segurança da futura auto-estrada da informação.
Tarefa
Implemente em Prolog um codificador de mensagens e o respectivo descodificador. O codificador deve
substituir o código ASCII de cada caracter da mensagem por um outro de acordo com o algoritmo de
encriptação descrito pelos seguintes passos:
• Converter a mensagem dada numa lista L com os códigos ASCII de cada caracter.1
• Gerar a lista P de todos os primos até ao código ASCII do primeiro caracter da frase a codificar (1o
elemento de L), usando um predicado primos/2 que terá de desenvolver segundo o método a
indicar mais adiante.
• O primeiro caracter da frase não é alterado.
• Ao segundo caracter somamos o primeiro número primo em P; ao terceiro caracter somamos o
segundo elemento de P, etc, considerando a lista P de um modo circular --depois de somar o último
primo em P, voltar ao início da lista.
Lembramos que o código ASCII é um número entre 0 e 255 e que é preciso ter o cuidado de garantir que os
códigos ASCII codificados também estão nesta gama! Mais uma vez, deve usar a noção de circularidade: por
exemplo, se estamos a somar o primo 3, o código ASCII 33 será transformado no 36 enquanto o 254 será
transformado no 1. O algoritmo de descodificação deverá proceder à operaç ão inversa do de codificação.
Subtarefa
Implemente um predicado em Prolog para gerar números primos segundo o algoritmo designado por Crivo de
Eratóstenes. Esse método permite-nos construir uma lista de todos os primos até um limite dado, de acordo
com a seguinte descrição3:
‘‘Escrevemos os naturais desde 2 até ao limite desejado; por exemplo 200:

Começando no princípio da lista, o primeiro número que encontramos é 2, um primo. Deixamos 2 de lado, e
passamos à frente marcando os números de 2 em 2, isto é, marcamos 4, 6, 8, 10, . Depois de ter
marcado todos os números pares até 200, voltamos ao princípio da lista para encontrar o primeiro número
depois de 2 que não foi marcado: é 3. Deixamos 3 de lado (é primo), e passamos à frente marcando os
números de 3 em 3: marcamos 6, 9, 12, 15, . Prosseguimos assim. Depois de fazermos este jogo

repetidamente para 2, 3, , N, onde N é o maior natural não marcado tal que , então os
números da lista que ainda não foram marcados são os primos até 2002.'
'
Dados
O único dado necessário para a resolução da tarefa principal deste problema é a mensagem a codificar. Essa
mensagem será um parâmetro do predicado encode/2 que tem de desenvolver, como se explica na secção
seguinte. Para a subtarefa de geração de números primos também precisa de conhecer à partida apenas um
dado: o valor do limite superior da lista a gerar.
Resultados
O seu programa deve ser invocado através dos predicados:
a) encode/2 que recebe a mensagem e retorna no segundo argumento a lista dos códigos ASCII da mensagem
codificada
b) decode/2 que recebe a lista dos códigos ASCII da mensagem codificada e devolve no segundo argumento
a mensagem original.
c) primos/2 que recebe como primeiro argumento um número natural N e devolve no segundo argumento a
lista de todos os primos até N.
Exemplos
?- primos(100,L).
L = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,
53,59,61,67,71,73,79,83,89,97]
?- encode(’Ola malta’,C), decode(C,D).
C = [79,110,100,37,116,108,121,133,116]
D = ’Ola malta’
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL98/E – Concurso Nacional de Programação em Lógica 1998

Ginástica Rítmica
Introdução
Para organizar um espectáculo de ginástica rítmica, o responsável pelas actividades recreativas da Associação
Académica necessita de estabelecer um plano e distribuir trabalho pelos seus ginastas. O plano identifica a
sequência dos exercícios que vão ser apresentados e a distribuição de trabalho indica quais os ginastas que
vão participar em cada exercício. Para fazer a distribuição, o responsável pelo espectáculo necessita de
conhecer: os requisitos específicos de cada exercício; as características pertinentes de cada ginasta e as suas
exigências; e os ginastas que podem participar no dia do espectáculo em organização.
Tarefa
A sua tarefa consiste em desenvolver um programa em Prolog que realize automaticamente a referida
distribuição de trabalho --afectação de ginastas a cada exercício do plano - a partir duma base de
conhecimento que descreve precisamente: o plano; os exercícios; os ginastas; e os disponíveis (ginastas que
participarão no espectáculo).
Restrições:
O seu programa deve fazer a afectação tendo em consideração as seguintes regras:
• em cada exercício participarão pelo menos o número de ginastas especificado como mínimo para esse
exercício;
• em cada exercício não participarão mais ginastas do que o número especificado como máximo para
esse exercício;
• não serão deixados ginastas de fora, se tal não contrariar nenhuma exigência do exercício, como, por
exemplo, o total de participantes ter de ser par ou ímpar;
• se num exercício, forem deixados de fora alguns ginastas, esses serão os primeiros candidatos a
participar no exercício seguinte;
• os ginastas actuarão todos o mesmo número de vezes ou um número de vezes o mais próximo
possível;
• se o exercício especificar que é para ser executado aos pares, então cada par terá de ser formado por
ginastas de sexos diferentes, devendo, nesse caso, verificar-se que os elementos do par tenham a
mesma altura e o mesmo peso a menos de um delta pré-definido.
Dados
A Base de conhecimento necessária para testar o seu programa reside num ficheiro de nome
"ginastas.dat", o qual deve ser consultado no início. Esse ficheiro contém:
• os factos:
deltaAltura/1
deltaPeso/1
que definem, respectivamente, a máxima diferença entre alturas e entre pesos, permitida entre os
elementos de cada par;
• o facto plano/1 que especifica a lista de identificadores dos exercícios que se quer incluir no
espectáculo a organizar, isto é, descreve o plano do espectáculo;
• uma colecção de factos referente ao predicado: participa/1 sabendo-se que participa(num)
identifica cada ginasta que estará presente e apto no espectáculo em causa;
• uma colecção de factos referentes ao predicado: exercicio/6 cujos argumentos têm o seguinte
significado:
exercicio( id,descricao,minginastas,maxginastas,parimpar,tipo) em que parimpar indica se o
número de participantes tem de ser impar, par, ou se é indiferente; e tipo indica se esse
exercício é para ser executado aos pares, ou se é singular. Note-se que no caso do exercício
dever ser executado aos pares, os argumentos minginastas e maxginastas continuam a ser
referentes ao número de participantes que, nesse caso, será o dobro do número mínimo, ou máximo,
de pares pretendidos;
• uma colecção de factos relativos ao predicado: ginasta/6 cujos argumentos são ginasta(
num,nome,sexo,idade,peso,altura )
Resultados
O seu programa deve ser activado através do predicado de aridade zero distribui/0
Como resultado, deve acrescentar à base de conhecimento uma colecção de novos factos afectacao/2 em que
o primeiro argumento identifica um exercício e o segundo argumento é a lista de ginastas que o irão executar
ou a palavra retirar, caso não seja possível arranjar uma lista de ginastas que satisfaça todos os seus
requisitos.
Faculdade de Engenharia da Universidade do Porto 2003/2004
LEIC
Licenciatura em Engenharia Informática e Computação
(3º Ano)
Programação em Lógica 1º Sem
Docentes: Luís Paulo Reis e Eugénio da Costa Oliveira
Exercício CNPL98/F – Concurso Nacional de Programação em Lógica 1998

Lavandaria
Introdução
Uma dona de casa chegou de férias tendo trazido vários sacos de diferentes tipos de roupas (de corpo e de
casa) para lavar. Embora a sua máquina permita seleccionar várias temperaturas e escolher se se quer
centrifugar ou não, só tem capacidade para lavar 5kg de roupa de cada vez. Como fez grandes gastos nas
férias, pretende minimizar o número de lavagens, para poupar em água, detergente e energia eléctrica. Porém,
sente-se atrapalhada pois não sabe a melhor forma de combinar as roupas de modo a não ultrapassar a
capacidade da máquina e a garantir que fique bem lavada sem correr o risco de estragar alguma peça (o que
acontece se não respeitar as condições de lavagem próprias de cada artigo).
Tarefa
A sua tarefa consiste em desenvolver um programa em Prolog que determine uma solução possível para o
problema da dona de casa, ou seja, que proponha um agrupamento das peças de modo a respeitar as
restrições (da máquina e da roupa) anteriormente referidas.
Restrições:
O seu programa deve fazer o agrupamento tendo em consideração as seguintes regras:
• roupa branca não pode ser misturada com roupa de cor;
• cada lote não pode ultrapassar os 5kg;
• um tipo de roupa não pode ser lavado a uma temperatura mais alta que a indicada, nem pode ser
centrifugado se tal estiver interdito nas suas características;
• de preferência e sempre que tal não seja contra-indicado, as peças devem ser centrifugadas;
• quando a temperatura recomendada não é classificada como máxima, esse artigo terá de ser lavado
exactamente a essa temperatura.
Os Dados
A base de conhecimentos usada pelo programa reside num ficheiro de nome "lavanda.dat", o qual deve
ser consultado no início. Esse ficheiro contém uma colecção de factos referentes ao predicado:
roupaSuja/7
cujos argumentos são os descritos a seguir
roupaSuja(nomePecas,quantidade,pesoUnit,temperatura,tipo,centrifuga,cor)
em que tipo especifica se a temperatura indicada é a maxima ou a recomendada e centrifuga diz se pode (sim)
ou nao ser centrifugada. O argumento cor, toma o valor sim ou nao, conforme se trata de roupa de cor que
pode tingir ou de roupa branca.
Os Resultados
O seu programa deve ser activado através do predicado de aridade zero lava/0
Como resultado, deve acrescentar à base de conhecimento uma colecção de novos factos loteLavagem/3 em
que os argumentos terão o seguinte significado loteLavagem( temperatura,centrifuga,carga ) onde carga é
uma lista de pares que descreve o nome das peças e a quantidade que irão constituir o lote em causa.