Universidade Federal de São Carlos Departamento de Computação Programação Lógica Prof. Dr. Antonio Francisco do Prado e-mail: [email protected] Outros Exemplos Problema dos Missionários e Canibais Na margem esquerda de um rio há 5 missionários e 5 canibais, que querem atravessá-lo para a outra margem . Há um bote que pode transportar no máximo 3 pessoas ao mesmo tempo. Os missionários não podem ficar em menor número que os canibais em qualquer margem, caso contrário serão devorados. Como fazer para que todos cheguem a salvo na outra margem do rio? Resolução: Consideraremos a seguinte descrição para o conjunto de estados possíveis utilizados para representar o problema (Me, Ce, Bte) onde, Me = número de missionários na margem esquerda Ce = número de canibais na margem esquerda Bte = 1 se o bote está na margem esquerda, 0 na direita Logo, o estado inicial é (5, 5, 1) e o final (0, 0, 0) Representação do Espaço de Estados do Problema (5,5,1) (5,4,0) (5,3,0) (5,2,0) (5,4,1) (4,4,0) (5,3,1) (3,3,0) (5,1,0) (5,0,0) (4,4,1) (5,2,1) (5,1,1) (2,2,0) (3,3,1) (0,3,0) (0,4,1) (0,1,0) (1,1,1) (0,5,1) (0,2,0) (0,3,1) (0,0,0) (0,4,0) (2,2,1) (0,2,1) O programa prolog que encontra a solução para este problema usando busca em largura é mostrado abaixo. largura([ ],_,_):- write('Solução não encontrada!'),nl. largura([G|T],_,G):- write('Solução encontrada!'),nl. largura([S|T],C,G):write('Listas a Percorrer: '),write([S|T]),nl, write('Listas Percorridas: '), write(C),nl, bagof(X,movelargura( S, T, C, X ),Lista), append(T,Lista,O ),!, largura(O, [S|C],G ). largura([S|T],C,G):- largura(T,[S|C],G). movelargura(S,T,C,X):- move(S,X), X \== S, not member(X,T), not member(X,C). % representação do espaço de estados move([5,5,1],[5,2,0]). move([5,5,1],[5,4,0]). move([5,5,1],[4,4,0]). move([5,5,1],[5,3,0]). move([5,2,0],[5,3,1]). move([5,2,0],[5,4,1]). move([4,4,0],[5,4,1]). move([0,1,0],[0,2,1]). move([5,3,0],[5,4,1]). move([5,3,1],[3,3,0]). move([5,3,1],[5,0,0]). move([5,3,1],[5,1,0]). move([5,4,1],[3,3,0]). move([5,4,1],[5,1,0]). move([5,0,0],[5,1,1]). move([5,1,0],[5,2,1]). move([2,2,0],[3,3,1]). move([0,3,0],[0,4,1]). move([0,4,1],[0,2,0]). move([0,5,1],[0,2,0]). move([0,2,0],[2,2,1]). move([0,1,0],[0,3,1]). move([0,1,0],[1,1,1]). move([0,3,1],[0,0,0]). move([1,1,1],[0,0,0]). move([0,2,1],[0,0,0]). move([5,0,0],[5,2,1]). move([3,3,0],[4,4,1]). move([5,2,1],[2,2,0]). move([3,3,1],[0,3,0]). move([0,3,0],[0,5,1]). move([0,4,1],[0,1,0]). move([0,5,1],[0,4,0]). move([0,2,0],[0,3,1]). ?- largura([[5,5,1]],[ ],[0,0,0]). Neste caso partiu-se com o Estado Inicial, a Lista dos Percorridos e o Estado Final. Organização de uma Lista Bubblesort bubblesort(List,Sorted):-swap(List,List1),!, bubblesort(List1,Sorted). bubblesort(Sorted,Sorted). swap([X,Y|Rest],[Y,X|Rest]):-gt(X,Y). swap([Z|Rest],[Z|Rest1]):-swap(Rest,Rest1). gt(X,Y):-X>Y. Questão: ?- bubblesort([9,5,3,8],L). L = [3,5,8,9] Quicksort quicksort([],[]). quicksort([X|Tail],Sorted):-split(X,Tail,Small,Big), quicksort(Small,Sortedsmall),quicksort(Big,Sortedbig), conc(Sortedsmall,[X|Sortedbig],Sorted). split(X,[],[],[]). split(X,[Y|Tail],[Y|Small],Big):-gt(X,Y),!, split(X,Tail,Small,Big). split(X,[Y|Tail],Small,[Y|Big]):-split(X,Tail,Small,Big). conc([],L,L). conc([X|L1],L2,[X|L3]):-conc(L1,L2,L3). gt(X,Y):-X>Y. Questão: ?- quicksort([5,8,0,2,7],L). L = [0,2,5,7,8] ; no Permutation Sort psort(Xs,Ys) :- permutation(Xs,Ys), ordenado(Ys). permutation(Xs,[Z|Zs]) :- select(Z,Xs,Ys), permutation(Ys,Zs). permutation([],[]). ordenado([]). ordenado([X]). ordenado([X,Y|Ys]) :- X <= Y, ordenado([Y|Ys]). Questão: ?- psort([3,4,2,1],Y). Y = [1,2,3,4] Insertion Sort isort([X|Xs],Ys) :- isort(Xs,Zs), insert(X,Zs,Ys). isort([],[]). insert(X,[],[X]). insert(X,[Y|Ys],[Y|Zs]) :- X > Y, insert(X,Ys,Zs). insert(X,[Y|Ys],[X,Y|Ys]) :- X <= Y. Questão: ?- isort([3,2,1],A). A = [1,2,3] Investigando um Crime Definição de uma base de dados: possivel_suspeito(fred). possivel_suspeito(mary). possivel_suspeito(jane). possivel_suspeito(george). crime(roubo,john,terca,parque). crime(roubo,robin,quinta,bar). crime(assalto,jim,quarta,bar). estava(fred,terca,parque). inveja(fred,john). principal_suspeito(Pessoa,Crime):crime(Crime,Vitima,Dia,Lugar), possivel_suspeito(Pessoa), estava(Pessoa,Dia,Lugar), tem_motivo_contra(Pessoa,Vitima). principal_suspeito(desconhecido,Crime). tem_motivo_contra(Pessoa,Vitima):inveja(Pessoa,Vitima). Questões: ?- principal_suspeito(Quem,roubo). Quem = fred ; Quem = desconhecido ?- crime(Crime,Vitima,Dia,bar). Crime = roubo , Vitima = robin , Dia = quinta ; Crime = assalto , Vitima = jim , Dia = quarta ; Calculando o M.D.C. de uma Lista de Números Uma das formas de calcularmos o m.d.c de dois números é fatorarmos os dois e selecionarmos os fatores primos com os menores expoentes em cada um deles, por exemplo: 18 = 2^1 * 3^2 12 = 2^2 * 3^1 mdc(12, 18) = 2^1 * 3^1 = 6 Outra forma de calcularmos o m.d.c. de dois números é seguindo a seguinte regra: 1) Se a divisão do maior número pelo menor for exata então o mdc é igual ao menor número. 2) Senão o processo é repetido usando o menor número e o resto da divisão do maior pelo menor. Ex: mdc(18, 12) 18 | 12 01 --06 mdc(12, 06) 12 | 06 12 02 --00 mdc(18, 12) = 6 Nosso programa PROLOG utiliza o segundo método com a diferença que o estendemos para uma lista de números e não apenas para dois números: mdc([A|[]], A) :- !. mdc([A|Calda], N) :- mdc(Calda, B), mdc2(A, B, N), !. mdc2(A, B, B) :- X is A mod B, X is 0, !. mdc2(A, B, N) :- X is A mod B, mdc2(B, X, N). O predicado mdc possui duas clausulas, a primeira existe para determinarmos qual o mdc de uma lista com apenas um elemento, e a segunda clausula determina o mdc de uma lista com dois ou mais elementos, calculando recursivamente o mdc da calda desta lista e em seguida o mdc da cabeca da lista com o mdc da calda da lista já calculado. As clausulas do predicado mdc2 aplicam os passos 1 e 2 do segundo método mostrado acima. Abaixo mostramos alguns exemplos de execução do programa: ?- mdc([18,30,12,60], R). R=6 ?- mdc([18,30,10,60], R). R=2 ?- mdc([15,6,12,9], R). R=3 ?- mdc([15,30,20,12], R). R=1 ?- mdc([16,8,32,64], R). R=8 O Problema das Damas no Tabuleiro de Xadrez O enunciado do problema é o seguinte: “Em um tabuleiro de xadrez, ou de um modo mais geral um tabuleiro de N*N casas, queremos posicionar N damas de modo que nenhuma das damas ataca qualquer outra”. A resolução do problema está diretamente relacionada à movimentação exercida pela dama; No jogo de xadrez a dama caminha nas verticais, horizontais e diagonais, deste modo já eliminamos a possibilidade de haver duas ou mais damas numa mesma coluna ou numa mesma linha, restando apenas verificar se todas elas não se atacam nas diagonais. Podemos mapear o problema da seguinte maneira: Para um tabuleiro de N linhas por N colunas colocamos uma dama em cada coluna, cada uma em uma linha diferente da outra e então começamos a permutar cada dama de uma certa coluna com cada outra dama de outra coluna, desta forma realizaremos N! permutações, entre estas permutações eventualmente encontraremos situações onde nenhuma dama ataca nenhuma outra, esta situação representa uma solução. A figura abaixo representa uma das 82 soluções encontrada pelo programa PROLOG para um tabuleiro de 8*8 com 8 damas: ?- damas(8,R). R = [1,5,8,6,3,7,2,4] Na figura contamos as linhas de baixo para cima, por exemplo o número 5 da resposta corresponde a dama da segunda coluna colocada na quinta linha. A baixo mostramos o código em PROLOG. damas(NumDamas,Solucao) :- intervalo(1,NumDamas,Lista), permuta(Lista,Solucao), seguro(Solucao). intervalo(A,A,[A]). intervalo(A,B,[A|Calda]) :- A < B, Proximo is A+1, intervalo(Proximo,B,Calda). permuta([],[]). permuta(Lista,[Elemento|Calda]) :retira(Elemento,Lista,NovaLista), permuta(NovaLista,Calda). retira(X,[X|Calda],Calda). retira(X,[Y|Calda],[Y|NovaCalda]) :- retira(X,Calda,NovaCalda). seguro([]). seguro([Cabeca|Calda]) :- seguro(Calda), not ataca(Cabeca,Calda). ataca(Cabeca,Calda) :- ataca(Cabeca,1,Calda). ataca(X,Distancia,[Y|Calda]) :- X is Y+Distancia; X is Y-Distancia. ataca(X,Distancia,[Y|Calda]) :- N1 is Distancia+1, ataca(X,N1,Calda). O predicado damas é composto de dois argumentos NumDamas (número de damas) e Solucao (lista que contém a solução), e dos predicados intervalo (que cria uma lista do tipo [1, 2 ,3 , 4 ,5 ,6 ,7 ,8]), permuta (que realiza todas as permutações na lista que criamos) e seguro (que testa se uma dada situação gerada a partir de uma permutação é uma situação aceitável, ou seja nenhuma dama ataca nenhuma outra). Calculando Números Primos Os números primos são aqueles divisíveis por si e pelo número um, desta forma poderíamos determinar se um número é primo dividindo-o por todos os números anteriores a ele e contando quantos números conseguiram dividi-lo sem deixar resto. Este processo seria muito demorado, principalmente se desejássemos calcular uma lista de números primos. Podemos resolver este problema de uma maneira diferente: 1) Criamos uma lista de números consecutivos que começa em 2 e vai até o maior número do intervalo no qual queremos encontrar os números primos. 2) Pegamos o primeiro número da lista e cortamos todos os seus múltiplos. 3) Repetimos o processo para cada novo elemento na lista. No final do processo a lista restante contém apenas elementos que não possuem divisores diferente do número 1, ou seja, apenas números primos. A figura seguir ilustra o processo para os números de 2 a 100. O código PROLOG é mostrado a seguir: primos(Limite, Primos) :- geraLista(2, Limite, Lista), geraPrimos(Lista, Primos). geraLista(Menor, Maior, [Menor|Lista]) :- Menor =< Maior, N is Menor+1, geraLista(N, Maior, Lista), !. geraLista(_, _, []). geraPrimos([], []) :- !. geraPrimos([Primo|Lista], [Primo|MaisPrimos]) :removeMultiplos(Primo, Lista, NovaLista), geraPrimos(NovaLista, MaisPrimos). removeMultiplos(Num, [], []) :- !. removeMultiplos(Num, [Cabeca|Lista], [Cabeca|NovaLista]) :not(0 is Cabeca mod Num), removeMultiplos(Num, Lista, NovaLista), !. removeMultiplos(Num, [Cabeca|Lista], NovaLista) :0 is Cabeca mod Num, removeMultiplos(Num, Lista, NovaLista). O predicado removeMultiplos gera uma lista que possui apenas números que não são múltiplos de um certo número dado. O predicado geraLista constrói uma lista com todos os números entre um número menor e um maior. O predicado geraPrimos pega o primeiro número de uma lista e arranca todos os múltiplos deste elemento, então pega a esta nova lista e repete o processo. O predicado primos gera uma lista inicialmente com todos os números num intervalo dado e após isto chama o predicado geraPrimos. A seguir mostramos uma execução: ?- primo(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] Encontrando o Menor Caminho Entre Duas Cidades Quando desejamos encontrar o menor caminho entre duas cidades devemos primeiramente conhecer todos os caminhos que nos levam da cidade origem até a cidade destino, para isso devemos montar um grafo onde os nós são as cidades e os arcos são as distâncias entre uma cidade e outra. O grafo abaixo representa um exemplo: Nosso programa PROLOG deve encontrar todos os caminhos possíveis entre a cidade origem e a cidade destino e escolher aquele cuja a soma das distâncias correspondesnte às viagens de uma cidade para outra é a menor. O programa é o seguinte: distancia(a, d, 200). distancia(a, e, 500). distancia(b, e, 100). distancia(b, f, 100). distancia(d, e, 100). distancia(e, g, 300). distancia(f, i, 100). distancia(g, h, 300). vizinho(X, Y, Dist) :- distancia(X, Y, Dist). vizinho(X, Y, Dist) :- distancia(Y, X, Dist). pertence(X, [X|_]). pertence(X, [_|L]) :- pertence(X,L). distancia(b, c, 100). distancia(c, f, 100). distancia(e, i, 400). distancia(h, i, 200). caminho(Origem, Origem, _, [], 0). caminho(Origem, Destino, CidadesVisitadas, [Intermediario|Caminho], Distancia) :vizinho(Origem, Intermediario, Dist1), not(pertence(Intermediario, CidadesVisitadas)), caminho(Intermediario, Destino, [Intermediario|CidadesVisitadas], Caminho, Dist2), Distancia is Dist1 + Dist2. primeiro([[Caminho,Distancia]|Lista], Caminho, Distancia). resolve(Origem, Destino, [Origem|Caminho], Distancia) :bagof([Camin,Dist], caminho(Origem, Destino, [Origem], Camin, Dist), Lista), sort(Lista,ListaOrdenada,[2]), primeiro(ListaOrdenada, Caminho, Distancia). O predicado distancia determina as distâncias entre as cidades, o predicado vizinho determina que se uma cidade dista x de outra então esta segunda dista x da primeira, o predicado pertence determina se uma elemento já pertence a uma lista ou não (no nosso caso se uma cidade já pertence a uma lista de cidades), o predicado caminho encontra o caminho de uma cidade até outra bem como a distância total do percurso, o predicado primeiro pega o primeiro elemento de uma lista e o predicado resolve é o responsável por pegar todos os caminhos encontrados e escolher o de menor distância. A seguir mostramos um exemplo para o grafo anterior: ?- resolve(a, h, Caminho, Distancia). Caminho = [a,d,e,b,f,i,h] , Distancia = 800 Busca em Largura em Árvore Binária Freqüentemente necessitamos realizar buscas em árvores para solucionar problemas, como por exemplo quando queremos verificar todos os estados de um jogo de dama para chegar a uma posição vitoriosa. O backtracking de PROLOG nos permite realizar buscas em profundidade com certa facilidade, no entanto, para alguns problemas necessitamos realizar buscas em largura, o que não é uma tarefa tão simples. A busca é feita da seguinte forma: 1) Coloca-se o nó raiz em uma lista 2) Expande-se (gera-se todos os seus filhos) o primeiro elemento da lista e coloca-se o resultado no final da lista 3) Repete-se o processo para o segundo da lista, terceiro da lista, etc..., até que não haja mais elementos para expandir. No final teremos uma lista de nós cuja ordem é a ordem da busca em largura. A figura abaixo mostra uma árvore exemplo e os passos do processo descrito acima. O código PROLOG é mostrado a seguir: buscaLargura([], []). buscaLargura([tree(No, Esq, Dir)|Calda], [No|Resultado]) :bagof(X, filho(tree(No, Esq, Dir), X), Lista), append(Calda, Lista, NovaLista), buscaLargura(NovaLista, Resultado), !. buscaLargura([tree(No, Esq, Dir)|Calda], [No|Resultado]) :buscaLargura(Calda, Resultado), !. filho(tree(No, Esq, Dir), Esq) :- Esq \== null. filho(tree(No, Esq, Dir), Dir) :- Dir \== null. O predicado buscaLargura realiza o processo de expandir o primeiro nó da lista, criar uma nova lista que contém também os nós expandidos e repetir o processo para cada nó sucessivo da lista. O predicado filho é usado para retornar cada filho de um certo nó. A seguir mostramos a execução: ?- buscaLargura( [tree(3, tree(1, null, tree(2, null, null)), tree(7, tree(5, tree(4, null, null), tree(6, null, null)), tree(8, null, null)))],R). R = [3,1,7,2,5,8,4,6] Verificação de Árvores AVL Neste exemplo mostraremos como verificar se uma dada árvore é uma árvore AVL. Árvores AVL são árvores binárias de busca que possuem altura mínima, ou seja, em uma árvore de N nós a distância máxima da raiz a qualquer folha é no máximo log(N). Árvores de altura mínima são chamadas árvores balanceadas. Portanto devemos verificar duas condições: 1)A árvore binária deve ser uma árvore de busca. 2)A árvore binária deve estar balanceada. Dizemos que uma árvore binária é de busca se todos os elementos da subárvore esquerda de qualquer nó são menores que este nó e todos os elementos da subárvore direita de qualquer nó são maiores que este nó. Dizemos que uma árvore é balanceada se a diferença de altura da subárvore esquerda para a subárvore direita não exceder em módulo a unidade. Desta forma o teste de árvores AVL fica como mostrado a seguir: ehAVL(Arvore) :- arvoreBusca(Arvore), balanceada(Arvore). arvoreBusca(null). arvoreBusca(tree(Raiz, ArvoreEsq, ArvoreDir)) :- maior(Raiz, ArvoreEsq), menor(Raiz, ArvoreDir). maior(_, null). maior(Maior, tree(Raiz, ArvoreEsq, ArvoreDir)) :- Maior > Raiz, maior(Raiz, ArvoreEsq), menor(Raiz, ArvoreDir), maior(Maior, ArvoreDir). menor(_, null). menor(Maior, tree(Raiz, ArvoreEsq, ArvoreDir)) :Maior < Raiz, maior(Raiz, ArvoreEsq), menor(Raiz, ArvoreDir), menor(Maior, ArvoreEsq). balanceada(null). balanceada(tree(_, ArvoreEsq, ArvoreDir)) :balanceada(ArvoreEsq), balanceada(ArvoreDir), altura(ArvoreEsq, HE), altura(ArvoreDir, HD), DIF is HE - HD, abs(DIF) =< 1. altura(null, 0). altura(tree(_, ArvoreEsq, ArvoreDir), Altura) :altura(ArvoreEsq, HE), altura(ArvoreDir, HD), Altura is max(HE, HD) + 1. Na chamada ao predicado ehAVL devemos passar uma estrutura de árvore como a mostrada nos exemplos a seguir: ?- ehAVL(tree(3, tree(1, null, tree(2, null, null)), tree(7, tree(5, tree(4, null, null), tree(6, null, null)), tree(8, null, null)))). yes ?- ehAVL(tree(3, tree(1, null, tree(2, null, null)), tree(7, tree(5, tree(0, null, null), tree(6, null, null)), tree(8, null, null)))). no ?- ehAVL(tree(3, tree(1, null, tree(2, null, null)), tree(7, tree(5, tree(4, null, null), tree(6, null, null)), null))). no Reconhecendo Cadeias de Símbolos Através de MEFs Máquinas de Estados Finita (MEF) podem ser representadas de modo simplificado por um conjunto de estados e um conjunto de transições de estados, além dos estados finais e do estado inicial. Freqüentemente desejamos verificar se uma certa cadeia de símbolos pertence a linguagem determinada por uma MEF, para isso devemos aplicar uma seqüência de transições correspondentes aos símbolos dessa seqüência que nos levarão de um estado inicial a um estado final. Caso o estado final pertença ao conjunto de estados finais dizemos que a seqüência de entrada pertence a linguagem determinada pela MEF. Existem ainda MEFs que possuem transições de um determinado estado para mais de um estado, estas MEFs são chamadas MEF indeterminísticas (MEF_ind), diferentemente da primeira conhecida como MEF determinística (MEF_det). Quando trabalhamos com MEFs_ind devemos ser capazes de determinar qual o conjunto de estados resultante da aplicação de uma transição sobre vários estados da MEF e não apenas um único estado resultante da aplicação de uma única transição sobre um único estado. O programa que implementamos em PROLOG aceita MEFs_ind embora o exemplo escolhido represente uma MEF_det. terminal(q8). transicao(q0, a, q3). transicao(q1, b, q2). transicao(q3, b, q4). transicao(q5, a, q8). transicao(q7, a, q4). transicao(q0, b, q1). transicao(q2, a, q5). transicao(q4, a, q7). transicao(q6, a, q3). transicao(q7, b, q8). transicao(q1, a, q4). transicao(q3, a, q6). transicao(q4, b, q5). transicao(q6, b, q7). transicao(q8, a, q5). verifica(Estados, Entradas, []) :- aplicaTransicoes(Estados, Entradas, []), write('Nao ha transicao prevista, a MEF para: '), !. verifica(Estados, Entradas, Resposta) :aplicaTransicoes(Estados, Entradas, Resposta), temTerminal(Resposta), write('Sequencia valida: '), !. verifica(Estados, Entradas, Resposta) :aplicaTransicoes(Estados, Entradas, Resposta), write('Sequencia invalida: '), !. aplicaTransicoes([], Entradas, []) :- !. aplicaTransicoes([Estado|[]], [], [Estado]) :- !. aplicaTransicoes([Estado|[]], [Entrada|Sequencia], EstadosFinais) :- transicoes(Estado, Entrada, ListaEstados), aplicaTransicoes(ListaEstados, Sequencia, EstadosFinais), !. aplicaTransicoes([Estado|MaisEstados], Entradas, EstadosFinais) :- aplicaTransicoes([Estado], Entradas, ListaA), aplicaTransicoes(MaisEstados, Entradas, ListaB), uniao(ListaA, ListaB, EstadosFinais). temTerminal([Estado|_]) :- terminal(Estado), !. temTerminal([_|Estados]) :- temTerminal(Estados). transicoes(Estado, Entrada, ListaEstados) :bagof(NovoEstado, transicao(Estado, Entrada, NovoEstado), ListaEstados), !. transicoes(Estado, Entrada, []). uniao([], [], []) :- !. uniao([], Lista, NovaLista) :- tiraRepetidos(Lista, NovaLista), !. uniao(Lista, [], NovaLista) :- tiraRepetidos(Lista, NovaLista), !. uniao([Elem|ListaA], ListaB, NovaLista) :- pertence(Elem, ListaB), uniao(ListaA, ListaB, NovaLista), !. uniao([Elem|ListaA], ListaB, NovaLista) :- pertence(Elem, ListaA), uniao(ListaA, ListaB, NovaLista), !. uniao([Elem|ListaA], ListaB, [Elem|NovaLista]) :- uniao(ListaA, ListaB, NovaLista). tiraRepetidos([], []) :- !. tiraRepetidos([Elem|[]], [Elem]) :- !. tiraRepetidos([Elem|Calda], Saida) :- pertence(Elem, Calda), tiraRepetidos(Calda, Saida), !. tiraRepetidos([Elem|Calda], [Elem|Saida]) :tiraRepetidos(Calda, Saida). pertence(Elem, [Elem|_]) :- !. pertence(Elem, [_|Lista]) :- pertence(Elem, Lista). Os fatos terminal e transicao determinam a estrutura da MEF (estados, transições e estado final). O predicado verifica possui três argumentos: uma lista de estados, uma lista representando a seqüência de símbolos de entrada e uma lista de estados finais da MEF correspondente às transições realizadas após a verificação. No processo de verificação devemos aplicar transições correspondentes aos símbolos de entrada, esta tarefa é realizada pelo predicado aplicaTransicoes, neste predicado encontramos um conjunto de estados resultantes das transições correspondentes a uma determinada entrada e repetimos este processo até que se esgotem os símbolos de entrada. No final do processo de aplicação de transições teremos duas listas com estados finais da MEF (uma gerada a partir do primeiro estado da lista de estados e outra gerada a partir dos estados restantes). Devemos agora unir estas duas listas em uma (tarefa realizada pelo predicado uniao) retirando as possíveis repetições de estados através do predicado tiraRepetidos. Por fim determinamos se o conjunto de estados finais possui ao menos um elemento terminal, esta tarefa é realizada pelo predicado temTerminal. A estrutura da MEF é mostrada na figura abaixo e aceita as cadeias de ‘a’s e ‘b’s que possuem número par de ‘a’s (2, 4, ..) e exatamente 2 ‘b’s em qualquer ordem. Representação gráfica da MEF Algumas possíveis execuções seriam: ?- verifica([q0], [a,b,a,a,b,a,a,a], R). Sequencia valida: R = [q8] ?- verifica([q0], [b,a,a,b,b,a,a], R). Nao ha transicao prevista, a MEF para: R = [] ?- verifica([q0], [a,b,a,a], R). Sequencia invalida: R = [q4] Poderiamos ainda iniciar a MEF a partir de mais de um estado como por exemplo q0 e q4, isso acarretaria no reconhecimento de seqüências com um único ‘b’ e um número impar de ‘a’s (1, 3, ...) além das seqüências reconhecidas anteriormente a partir de q0. ?- verifica([q0,q4], [a,b,a,a], R). Sequencia valida: R = [q4,q8] ?- verifica([q0,q4], [a,b,b,a,a], R). Sequencia invalida: R = [q5] ?- verifica([q0,q4], [a,a,b,b,a,a], R). Sequencia valida: R = [q8] O Problema das Jarras D’água O problema das jarras d’água tradicional consiste de duas jarras de capacidades 3 e 5 litros (inicialmente vazias) e uma fonte d’água para encher as jarras. O objetivo do problema é deixar 4 litros na jarra de capacidade 5 litros, para isso podemos realizar as seguintes ações: encher uma jarra, esvaziar uma jarra ou despejar o conteúdo de uma jarra na outra. Poderíamos representar a seqüência de transições que nos conduzem à resposta representando os estados das jarras (através de pares) entre cada ação tomada. A solução seria a seguinte: (0,0), (0,5), (3,2), (0,2), (2,0), (2,5), (3,4). Outra maneira de representarmos a solução seria reportando a seqüência de passos a serem tomados: encher a 2o. jarra, virar a 2o. jarra na 1o., esvaziar a 1o. jarra, virar a 2o. jarra na 1o., encher a 2o. jarra, virar a 2o. jarra na primeira. Esquema das transições que levam o estado inicial a um estado final O problema que mostramos a seguir estende o problema tradicional das jarras d’água para qualquer número de jarras, com qualquer configuração inicial, qualquer capacidade e qualquer configuração final da jarras. Além disso impomos um limite de transições para alcançarmos o estado final, isto significa que eventualmente podemos não encontrar uma solução, não porque esta solução não existe, mas sim porque ela necessita de mais transições do que o limite que colocamos. move(JarraA, CapacA, JarraB, CapacB, NovoA, NovoB) :NovoA is CapacA, NovoB is JarraB, JarraA\=CapacA. move(JarraA, CapacA, JarraB, CapacB, NovoA, NovoB) :NovoA is 0, NovoB is JarraB, JarraA\=0. move(JarraA, CapacA, JarraB, CapacB, NovoA, NovoB) :NovoA is 0, NovoB is JarraA+JarraB, NovoB<CapacB, JarraA\=0. move(JarraA, CapacA, JarraB, CapacB, NovoA, NovoB) :NovoA is JarraA-(CapacB-JarraB), NovoB is CapacB, CapacB-JarraB=<JarraA, JarraB\=CapacB. altera([Cabeca|Calda], 0, Valor, [Valor|Calda]). altera([Cabeca|Calda], Posicao, Valor, [Cabeca|NovaCalda]) :NovaPosicao is Posicao-1, altera(Calda, NovaPosicao, Valor, NovaCalda). pegaItem([Cabeca|Calda], Cabeca, 0). pegaItem([_|Calda], Elemento, NovoContador) :pegaItem(Calda, Elemento, Contador), NovoContador is Contador+1. pegaJarra(Configuracao, Jarra, Posicao) :pegaItem(Configuracao, Jarra, Posicao). pegaCapacidade(Capacidades, Capac, Posicao) :pegaItem(Capacidades, Capac, Posicao). pertence(X, [X|_]) :- !. pertence(X, [_|Z]) :- pertence(X, Z). transicao(ConfFinal, ConfFinal, Capacidades, Lista, Contador) :- reverse(Lista, NovaLista), write(NovaLista), !. transicao(ConfInicial, ConfFinal, Capacidades, Lista, Contador) :- Contador>0, NovoContador is Contador-1, pegaJarra(ConfInicial, JarraA, PosicaoA), pegaCapacidade(Capacidades, CapacA, PosicaoA), pegaJarra(ConfInicial, JarraB, PosicaoB), pegaCapacidade(Capacidades, CapacB, PosicaoB), PosicaoA\=PosicaoB, move(JarraA, CapacA, JarraB, CapacB, NovoA, NovoB), altera(ConfInicial, PosicaoA, NovoA, ConfTemp), altera(ConfTemp, PosicaoB, NovoB, NovaConf), not(pertence(NovaConf, Lista)), transicao(NovaConf, ConfFinal, Capacidades, [NovaConf|Lista], NovoContador), !. resolve(Inicial, Final, Capacidade, Contador) :caminho(Inicial, Final, Capacidade, [Inicial], Contador). Os predicados move representam as transições que podemos realizar entre duas jarras: encher, esvaziar e virar uma em outra (virando até encher ou virando todo o conteúdo). O predicado altera recebe uma lista, uma posição e um valor e retorna uma nova lista igual, exceto na posição recebida, onde o valor será alterado para o valor recebido. O predicado pegaItem recebe uma lista e devolve um valor e sua posição correspondente na lista, caso a posição já esteja definida pegaItem retorna apenas o valor associado à posição. Os predicados pegaJarra e pegaCapacidade apesar de possuírem nomes diferentes realizam a mesma tarefa, selecionam um valor e uma posição em uma lista dada. O predicado transicao é o coração do algoritmo, no primeiro predicado representamos um estado solução, ou seja, a configuração atual do problema é igual a configuração final do problema. Neste caso mostramos na tela a seqüência de estados que representam a solução. No segundo predicado, primeiramente testamos e decrementamos um contador para que possamos garantir que não iremos procurar soluções que possuam mais que um determinado número de transições, após isso selecionamos duas jarras (na verdade todas as combinações de duas jarras) e suas respectivas capacidades, então conferimos se não pegamos duas vezes a mesma jarra. Caso não tenhamos pego, criamos uma nova lista parecida com a lista que possui a configuração atual, mas com os conteúdos das duas jarras alterados, após isso verificamos se a nova configuração que construímos não é um estado que já faz parte de nossa lista de transições. Caso afirmativo, continuamos o processo procurando agora chegar à configuração final à partir do novo estado que geramos (com a possibilidade de realizarmos uma transição a menos e com um estado a mais na lista de estados que constituem o caminho solução). O predicado resolve recebe uma lista que representa a configuração inicial das jarras, uma lista que representa o estado final, uma lista que representa a capacidade das jarras e o número máximo de transições que desejamos realizar. A seguir mostramos algumas execuções. ?- resolve( [0,1,2], [2,2,4], [2,3,5], 4 ). [ [0,1,2], [1,0,2], [1,2,0], [1,2,5], [2,2,4] ] yes ?- resolve( [3,4], [1,0], [5,7], 4 ). no Note que no último exemplo não conseguimos encontrar uma solução com 4 transições ou menos. No entanto, se estipularmos 5 transições o problema já passa a ser solúvel. ?- resolve( [3,4], [1,0], [5,7], 5 ). [ [3,4], [3,0], [0,3], [5,3], [1,7], [1,0] ] yes Outro aspecto importante do problema é que podemos omitir partes do estado solução quando não estamos interessados em alguma jarra em específico: ?- resolve( [0,1], [3,2], [6,4], 10 ). no ?- resolve( [0,1], [3,_], [6,4], 5 ). [ [0,1], [6,1], [3,4] ] yes Também podemos estabelecer relações de igualdade entre os conteúdos finais das jarras: ?- resolve( [1,1,3,2], [X,2,X,1], [2,4,5,3], 3 ). [ [1,1,3,2], [0,2,3,2], [0,2,2,3], [2,2,2,1] ] X = 2 O Problema da Coloração de Mapas Quando estamos colorindo um mapa queremos que cada país, ou estado, possua uma cor diferente de qualquer um de seus vizinhos para que possa ser bem delimitado. Matematicamente está provado que em um plano não podemos interconectar totalmente mais que 4 elementos, ou seja, teremos no máximo 4 países que fazem fronteira um com o outro sem possibilidade de repetirmos cores. Se inserirmos um quinto país, onde quer que seja, haverá sempre uma cor que já usamos e assim poderemos repeti-la. Desta forma nosso programa de coloração de mapas necessita apenas 4 cores. Uma das diversas resoluções é a que se segue: 1) Definimos os países e as fronteiras entre eles. 2) Escolhemos um país e damos uma cor a ele temporariamente. 3) Preenchemos os seus vizinhos com as cores restantes, também temporariamente. 4) Pegamos outro país e repetimos o processo. 5) Caso não seja possível distribuir as cores para o país e seus vizinhos, voltamos no Backtracking de PROLOG e escolhemos outras combinações de cores. OBS: Como sempre pintamos um país com uma cor e os vizinhos com as restantes, sempre que conseguirmos pintar o último país teremos certeza que ele não tem a mesma cor que nenhum de seus vizinhos, e portanto todos ou outros países já estão pintados corretamente. O código PROLOG é o seguinte: cores([vermelho, amarelo, azul, verde]). mapa([ fronteira(paisA, A, [B, C, D, F]), fronteira(paisB, B, [A, C, D, E, F]), fronteira(paisC, C, [A, B, D]), fronteira(paisD, D, [A, B, C, E, F]), fronteira(paisE, E, [B, D, F]), fronteira(paisF, F, [A, B, D, E]) ] ). pintaMapa(Solucao) :- mapa(Solucao), coloreMapa(Solucao). coloreMapa([Pais|PaisesRestantes]) :- coloreRegiao(Pais), coloreMapa(PaisesRestantes). coloreMapa([]). coloreRegiao(fronteira(Pais, Cor, Vizinhos)) :- cores(Cores), seleciona(Cor, Cores, CoresRestantes), preenche(Vizinhos, CoresRestantes). seleciona(Elem, [Elem|Calda], Calda). seleciona(Elem, [ElemDifer|Calda], [ElemDifer|NovaLista]) :seleciona(Elem, Calda, NovaLista). preenche([Elem|Calda], Lista) :- member(Elem, Lista), preenche(Calda, Lista). preenche([], Lista). O predicado mapa possui informações sobre os nomes dos países, suas cores e as cores de seus vizinhos, servindo com uma base de dados de países. Note que a cor de cada país bem como a cor de seus vizinhos são representadas por variáveis (letras maiúsculas), isto é, não estão instanciadas e portanto poderão assumir diversos valores durante a execução do programa. O predicado pintaMapa lê este mapa que definimos e o colore. O predicado coloreMapa pega o primeiro país e atribui uma cor a ele e aos seus vizinhos, após isso repete o processo aos países restantes. O predicado coloreRegiao seleciona uma cor para um país e utiliza as cores restantes para preencher os países restantes. O predicado seleciona pega uma cor entre as 4 cores e retorna uma lista com as cores restantes, e o predicado preenche atribui cores aos países não permitindo que um país possua uma cor diferente das cores passadas no segundo argumento deste predicado. A saída do programa contém os nomes dos países, suas respectivas cores e a listas de cores correspondentes aos países vizinhos. Note que para cada saída do programa a cor do país nunca pertence a sua lista de cores de países vizinhos. A seguir mostramos 3 dos 24 modos diferentes de colorir o mapa do nosso exemplo, e um mapa ilustrativo para cada caso. ?- pintaMapa(R). R = [ regiao(paisA, vermelho, [amarelo, azul, verde, azul]), regiao(paisB, amarelo, [vermelho, azul, verde, vermelho, azul]), regiao(paisC, azul, [vermelho, amarelo, verde]), regiao(paisD, verde, [vermelho, amarelo, azul, vermelho, azul]), regiao(paisE, vermelho, [amarelo, verde, azul]), regiao(paisF, azul, [vermelho, amarelo, verde, vermelho]) ] ; R = [ regiao(paisA, amarelo, [azul, verde, vermelho, verde]), regiao(paisB, azul, [amarelo, verde, vermelho, amarelo, verde]), regiao(paisC, verde, [amarelo, azul, vermelho]), regiao(paisD, vermelho, [amarelo, azul, verde, amarelo, verde]), regiao(paisE, amarelo, [azul, vermelho, verde]), regiao(paisF, verde, [amarelo, azul, vermelho, amarelo]) ] ; R = [ regiao(paisA, verde ,[amarelo, vermelho, azul, vermelho]), regiao(paisB, amarelo ,[verde, vermelho, azul, verde, vermelho]), regiao(paisC, vermelho, [verde, amarelo, azul]), regiao(paisD, azul, [verde, amarelo, vermelho, verde, vermelho]), regiao(paisE, verde, [amarelo, azul, vermelho]), regiao(paisF, vermelho, [verde, amarelo, azul, verde]) ] ; Sistema Especialista no Planejamento Empresarial Os sistemas especialistas são aplicações computacionais capazes de simular o comportamento de um especialista humano, em um determinado assunto, a fim de descobrir ou resolver um determinado problema. Os sistemas especialistas estão presentes em vários ramos da computação como: OCRs, reconhecimento de voz, diagnóstico médico, jogo de xadrez, sistema de computação de bordo de carros, sistema de controle de robôs, etc. Nosso exemplo de sistema especialista identifica alguns dos vários problemas que uma empresa tem que resolver do ponto de vista estratégico. No exemplo colhemos do usuário informações sobre o estado da empresa bem como as condições gerais do mercado e, através destas informações, inferimos outras coisas (assim como um especialista humano faria). As informações que colhemos devem estar relacionadas, ou encadeadas, de forma a podermos construir grafos de causa-conseqüência. O grafo abaixo representa três possíveis problemas que devemos verificar e seus relacionamentos de causa-conseqüência. Podemos dizer por exemplo que se: Funcionarios fazem hora-extra frequentemente for verdadeiro e Projetos estao atrasados for verdadeiro então concluímos (inferimos) que Servico esta sobrecarregado é verdadeiro. Nosso programa deve verificar cada um dos três problemas que representamos fazendo perguntas quando necessário (texto em preto), tirando conclusões (texto em azul) e identificando o problema (texto em vermelho). O código PROLOG é o seguinte: conclusao(‘Ha necessidade de contratar pessoal’, [[‘Servico esta sobrecarregado’, 1], [‘As vendas aumentaram’, 1], [‘Nivel de informatizacao e bom’, 1]]). conclusao(‘Ha necessidade de informatizar o setor’, [[‘Servico esta sobrecarregado’, 1], [‘As vendas aumentaram’, 1], [‘Nivel de informatizacao e bom’, 0]]). conclusao(‘Ha necessidade de terceirizar o servico’, [[‘Ha viabilidade em terceirizar o servico’, 1], [‘Determinado servico esta custando caro para a empresa’, 1], [‘Investir em tecnologia ao inves de terceirizar o servico nao e viavel’, 1]]). regra(‘Servico esta sobrecarregado’, [[‘Funcionarios fazem horaextra frequentemente’, 1], [‘Projetos estao atrasados’, 1]]). regra(‘Nivel de informatizacao e bom’, [[‘Hardware e software estao atualizados’, 1], [‘Numero de computadores e satisfatorio’, 1]]). regra(‘Ha viabilidade em terceirizar o servico’, [[‘Existe uma empresa especializada no servico a ser terceirizado’, 1], [‘Transporte e comunicacao com a empresa de terceirizacao e caro’, 0], [‘O servico da empresa de terceirizacao e caro’, 0]]). regra(‘Investir em tecnologia ao inves de terceirizar o servico nao e viavel’, [[‘Novas tecnologias para o setor a ser terceirizado sao caras’, 1], [‘Pesquisa com os funcionarios aponta rejeicao com a tecnologia da empresa de terceirizacao’, 1]]). pergunta(Frase, Resposta) :- write(Frase), display(' ? (s ou n) : '), read(SimOuNao), trata_resposta(Frase, SimOuNao, Resposta). trata_resposta(Frase, s, 1) :- assert(fato(Frase, 1)), !. trata_resposta(Frase, _, 0) :- assert(fato(Frase, 0)). conhecido(Frase) :- fato(Frase, _). nao_perguntavel(Frase) :- conclusao(Frase, _). nao_perguntavel(Frase) :- regra(Frase, _). calcula_regra([], 1) :- !. calcula_regra([[Frase, Esperado]|Calda], Resultado) :fato(Frase, Esperado), calcula_regra(Calda, Resultado), !. calcula_regra([[Frase, Esperado]|Calda], Resultado) :regra(Frase, Condicoes), calcula_regra(Condicoes, PreResultado), Esperado==PreResultado, calcula_regra(Calda, Resultado), !. calcula_regra([[Frase, Esperado]|Calda], Resultado) :not(conhecido(Frase)), not(nao_perguntavel(Frase)), pergunta(Frase, PreResultado), Esperado==PreResultado, calcula_regra(Calda, Resultado), !. calcula_regra(_, 0). resolve(Problema) :- retractall(fato(_, _)), conclusao(Problema, Condicoes), calcula_regra(Condicoes, 1), !. resolve('Não consegui chegar a nenhuma conclusão') :- !. O predicado conclusao relaciona as informações que podemos inferir para chegar a uma conclusão final do problema bem como os fatores que determinam esta inferência. O predicado regra relaciona todos as informações que podemos inferir, exceto as inferências que nos levam a uma conclusão. O predicado pergunta é utilizado quando precisamos de uma informação mas não a temos, neste caso, o predicado pergunta lê a resposta do usuário e trata esta resposta. O predicado trata_resposta utiliza a predicado assert de PROLOG para inserir uma nova clausula chamada fato em tempo de execução, esta clausula fato relaciona uma pergunta e uma resposta dada pelo usuário. O predicado conhecido verifica se já existe um fato (uma resposta do usuário) relacionado a uma determinada pergunta. O predicado nao_perguntavel verifica se uma certa pergunta não é do tipo que o usuário pode responder, ou seja, se ela não é uma pergunta e sim o resultado de uma inferência. O predicado calcula_regra retorna o valor de uma certa inferência feita através de um fato existente, do processamento de outras regras ou de uma pergunta ao usuário. O predicado resolve busca cada um dos problemas possíveis e verifica se algum está ocorrendo. A seguir algumas execuções do programa: ?- resolve(Problema). Funcionarios fazem hora-extra frequentemente ? (s ou n) : ? s. Projetos estao atrasados ? (s ou n) : ? s. As vendas aumentaram ? (s ou n) : ? s. Hardware e software estao atualizados ? (s ou n) : ? s. Numero de computadores e satisfatorio ? (s ou n) : ? n. Problema = 'Ha necessidade de informatizar o setor’ Podemos verificar também se um determinado problema está ocorrendo, a resposta será então do tipo yes ou no. ?- resolve('Ha necessidade de terceirizar o servico'). Existe uma empresa especializada no servico a ser terceirizado ? (s ou n) : ? s. Transporte e comunicacao com a empresa de terceirizacao e caro ? (s ou n) : ? n. O servico da empresa de terceirizacao e caro ? (s ou n) : ? s. no