sexta-feira, 18 de novembro de 2011

Problemas 6.16 a 6.19

As árvores genealógicas são um bom pretexto para aprendermos algo sobre dicionários e sobre programação. No guião 6 aparece um conjunto de pequenos problemas que permitem satisfazer estes dois objectivos. Ficamos a saber alguns aspectos fundamentais sobre os dicionários:

- como das chaves chegamos aos valores
- como dos valores encontramos as chaves
- as consequências de os dicionários não serem ordenados

Do mesmo modo somos confrontados com a possibilidade de reutilizar código, o que nos leva ao conceito de programação por abstracções. Podemos chegar a estes resultados sejam por um processo da base para o topo (como quem brinca com um Lego), seja do topo para a base. Esta última observação é importante, porque nos dá um princípio fundamental da programação: construir soluções por etapas sucessivas, decompondo o problema inicial em sub-problemas mais simples, cuja solução vai sendo obtida por refinamento da solução corrente. Dizemos que usamos camadas de abstracção.

Mas comecemos as nossas soluções definindo os dois tijolos básicos: (1) a partir de um nome obtemos os seus descendentes e (2) a partir de um descendente obtemos o seu progenitor.

def filhos(dicio,progenitor):
""" Lista dos filhos/descendentes."""
return dicio.get(progenitor,[])

def progenitor(dic, nome):
""" Devolve o progenitor do nome."""
for chave, valor in dic.items():
if nome in valor:
return chave
return []

Note-se como decidimos que na ausência de resposta (um pai sem filhos, alguém que não consta na árvore genealógica) o resultado a devolver é a lista vazia. Esta decisão tem consequências. Veja-se também que é mais natural resolver problemas em que a procura vai no sentido chave --> valor. Finalmente, atente-se no modo como percorremos o dicionário através dos seus elementos. Não havendo ordem não podemos aspirar a procura por aquilo que não existe: índices.

A partir destes elementos os outros problemas têm soluções fáceis.

Irmãos = filhos do mesmo progenitor

def irmaos(dic,nome1,nome2):
""" Têm o mesmo progenitor?"""
prog1 = progenitor(dic, nome1)
prog2 = progenitor (dic,nome2)
return prog1 == prog2

O que acontece se ambos os nomes não constarem no dicionário? Bem, vamos ter listas vazias a serem devolvidas... e o resultado será True, ou seja, são irmãos. Isto resulta do modo como implementámos a função progenitor. Pode ser corrigido:
def irmaos(dic,nome1,nome2):
""" Têm o mesmo progenitor?"""
prog1 = progenitor(dic, nome1)
prog2 = progenitor (dic,nome2)
if prog1 and prog2:
return prog1 == prog2
else:
return False


Netos = filhos dos filhos
def netos(dicio,progenitor):
""" Lista netos."""
net = []
for elem in filhos(dicio,progenitor):
net.extend(filhos(dicio,elem))
return net


Veja-se a necessidade de usar o método extend. É comum o erro de usar antes append.

Avo = progenitor do progenitor
def avo(dic,nome):
""" Quem é o/a avô/avó do nome."""
return progenitor(dic,progenitor(dic,nome))

Mais palavras para quê? Talvez para dizer de novo que perante um problema, a primeira coisa a fazer é ... pensar no problema, e definir uma estratégia para o resolver. Sem ter problemas em pensar assim: se eu tivesse esta funcionalidade então era fácil. Implementar uma solução provisória que depende da existência dessa funcionalidade. Pesquisar para ver se a linguagem a oferece. Em caso de resposta negativa, criar as funções auxiliares que a permitam implementar.

Sem comentários:

Enviar um comentário