Uma relação transitiva é aquela em que, se r(x,y) e r(y,z) são verdadeiras, então r(x,z) também é verdadeira. Em Prolog, podemos modelar relações transitivas de forma elegante usando recursão, permitindo navegar por cadeias de relacionamentos como ancestralidade, hierarquias e conexões em grafos.
O que é uma Relação Transitiva?
Formalmente, uma relação R é transitiva se:
|
1 |
R(x, y) e R(y, z) → R(x, z) |
Por exemplo, se “x é pai de y” e “y é pai de z”, então “x é avô de z”. A relação “ancestral” é o fecho transitivo da relação “pai”.
Programa 4.3: Ancestral
Vamos construir uma base de fatos sobre relações familiares e definir a regra ancestral/2:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
% Fatos: pai(Pai, Filho) pai(adao, seth). pai(adao, caim). pai(seth, enos). pai(enos, cainã). pai(cainã, malalel). pai(malalel, jared). pai(jared, enoque). pai(enoque, matusalém). pai(matusalém, lameque). pai(lameque, noé). % Regra: ancestral(X, Y) - X é ancestral de Y % Caso base: X é pai direto de Y ancestral(X, Y) :- pai(X, Y). % Passo recursivo: X é ancestral de Y se X é pai de Z e Z é ancestral de Y ancestral(X, Y) :- pai(X, Z), ancestral(Z, Y). |
Estrutura da Regra Ancestral
A regra ancestral/2 tem dois componentes:
- Caso base:
ancestral(X, Y) :- pai(X, Y).— X é ancestral direto de Y (pai). - Passo recursivo:
ancestral(X, Y) :- pai(X, Z), ancestral(Z, Y).— X é ancestral de Y se existe um intermediário Z, onde X é pai de Z e Z é ancestral de Y.
Diagrama da Árvore Genealógica
Visualize a estrutura familiar:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
adão / \ seth caim | enos | cainã | malalel | jared | enoque | matusalém | lameque | noé |
Fluxo de Execução: ancestral(X, enos)
Quando consultamos ?- ancestral(X, enos)., o Prolog busca todos os ancestrais de Enos:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[PASSO 1] Procurando pai(X, enos) → pai(seth, enos) é verdadeiro → X = seth (primeira solução) [PASSO 2] Usuário pede mais soluções (;) → Tentando pai(X, Z), ancestral(Z, enos) → pai(adao, seth) é verdadeiro → ancestral(seth, enos) é verdadeiro (encontrado no passo 1) → X = adao (segunda solução) [PASSO 3] Usuário pede mais soluções (;) → Não há mais pais que sejam ancestrais de enos → false (fim) |
Resultado: X = seth ; X = adao ; false.
Navegando pela Árvore
O Prolog navega pela árvore genealógica da seguinte forma:
|
1 2 3 4 5 6 7 8 9 10 11 |
ancestral(X, enos) │ ├─ pai(X, enos) → X = seth ✅ (1ª solução) │ └─ pai(X, Z), ancestral(Z, enos) │ ├─ pai(adao, seth), ancestral(seth, enos) → X = adao ✅ (2ª solução) │ ├─ pai(adao, caim), ancestral(caim, enos) → ancestral(caim, enos) falha │ └─ (outras tentativas falham) |
Programa 4.4: Acima (Relação Espacial)
Agora, um exemplo diferente: objetos empilhados uns sobre os outros.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
% Fatos: sobre(X, Y) - X está sobre Y sobre(b, a). sobre(d, b). sobre(d, c). % Regra: acima(X, Y) - X está acima de Y % Caso base: X está diretamente sobre Y acima(X, Y) :- sobre(X, Y). % Passo recursivo: X está acima de Y se X está sobre Z e Z está acima de Y acima(X, Y) :- sobre(X, Z), acima(Z, Y). |
Diagrama da Relação “Acima”
Visualize a pilha de objetos:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
d / \ b c | a Relações diretas (sobre): - b está sobre a - d está sobre b - d está sobre c Relações transitivas (acima): - d está acima de a (via b) - d está acima de b (direto) - d está acima de c (direto) - b está acima de a (direto) |
Consultas com Acima
1. Quem está acima de a?
|
1 2 3 4 5 6 7 |
?- acima(X, a). [PASSO 1] sobre(X, a) → X = b ✅ [PASSO 2] sobre(X, Z), acima(Z, a) → sobre(d, b), acima(b, a) → X = d ✅ Resultado: X = b ; X = d ; false. |
2. Quem está abaixo de d?
|
1 2 3 4 5 6 7 8 |
?- acima(d, X). [PASSO 1] sobre(d, X) → X = b ; X = c ✅ [PASSO 2] sobre(d, Z), acima(Z, X) → sobre(d, b), acima(b, X) → X = a ✅ → sobre(d, c), acima(c, X) → falha Resultado: X = b ; X = c ; X = a ; false. |
Recursão “Para Frente” e “Para Trás”
Observe a diferença na direção da recursão:
-
Ancestral (para cima):
ancestral(X, Y) :- pai(X, Z), ancestral(Z, Y).
Percorre a árvore para cima (dos mais novos para os mais velhos). -
Descendente (para baixo): Se quiséssemos encontrar descendentes, faríamos:
descendente(X, Y) :- pai(Y, X).(base)
descendente(X, Y) :- pai(Y, Z), descendente(X, Z).(passo)
Percorre a árvore para baixo (dos mais velhos para os mais novos).
A direção da recursão é determinada por onde colocamos a variável fixa na chamada recursiva.
Exercícios Propostos
-
Descendente: Implemente
descendente(X, Y)que é verdadeiro se X é descendente de Y (filho, neto, bisneto, etc.). Use a base de fatospai/2. -
Irmãos: Implemente
irmao(X, Y)que é verdadeiro se X e Y têm o mesmo pai. -
Primos: Implemente
primo(X, Y)que é verdadeiro se X e Y são primos (têm um ancestral em comum, mas não são irmãos). -
Caminho em grafo: Dada uma base de fatos
aresta(X, Y), implementecaminho(X, Y)que é verdadeiro se existe um caminho de X até Y. -
Desafio: Implemente
parente(X, Y)que é verdadeiro se X é parente de Y (ancestral ou descendente). Dica: use a união de duas regras.
Conclusão
As relações transitivas são um dos usos mais poderosos da recursão em Prolog. Com apenas algumas linhas de código, podemos modelar:
- Árvores genealógicas (ancestralidade, descendência)
- Hierarquias espaciais (acima, abaixo, dentro)
- Grafos (caminhos, conexões)
- Redes de dependência (pré-requisitos, requisitos)
A elegância da solução está na simplicidade da definição: uma regra base e uma regra recursiva que “anda” pela relação. O Prolog cuida do resto — navegando pela árvore, encontrando todos os caminhos e retornando as soluções uma a uma.
A chave para dominar relações transitivas é entender a direção da recursão: a posição da variável fixa determina se você está subindo ou descendo na hierarquia. Pratique com os exercícios e explore as infinitas possibilidades que essa técnica oferece!