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.

01.def filhos(dicio,progenitor):
02.   """ Lista dos filhos/descendentes."""
03.   return dicio.get(progenitor,[])
04. 
05.def progenitor(dic, nome):
06.   """ Devolve o progenitor do nome."""
07.   for chave, valor in dic.items():
08.       if nome in valor:
09.           return chave
10.   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

1.def irmaos(dic,nome1,nome2):
2.   """ Têm o mesmo progenitor?"""
3.   prog1 = progenitor(dic, nome1)
4.   prog2 = progenitor (dic,nome2)
5.   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:
1.def irmaos(dic,nome1,nome2):
2.   """ Têm o mesmo progenitor?"""
3.   prog1 = progenitor(dic, nome1)
4.   prog2 = progenitor (dic,nome2)
5.   if prog1 and prog2:
6. return prog1 == prog2
7.   else:
8. return False


Netos = filhos dos filhos
1.def netos(dicio,progenitor):
2.   """ Lista netos."""
3.   net = []
4.   for elem in filhos(dicio,progenitor):
5.     net.extend(filhos(dicio,elem))
6.   return net


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

Avo = progenitor do progenitor
1.def avo(dic,nome):
2.   """ Quem é o/a avô/avó do nome."""
3.   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