domingo, 31 de outubro de 2010

Problema 4.15

Este problema coloca basicamente a questão de saber como é que simulamos os lançamentos. A resposta, simples, consiste em usar o método randint do módulo random. O resto é o padrão básico de repetição um número de vezes fixo e conhecido, que se traduz pelo uso de um ciclo for.

def moeda_ao_ar(n):
"""Avalia o número de caras e coroas após n lançamentos de uma moeda. """
conta_caras = 0
conta_coroas = 0
for i in range(n):
valor = random.randint(0,1)
if valor == 0:
conta_caras = conta_caras + 1
else:
conta_coroas = conta_coroas + 1
return (conta_caras, conta_coroas)

Existe uma coisa que todos sabemos. Se a moeda não estiver enviesada, então podemos contar com 50% de probabilidades para num lançamento sair caras. Mas será mesmo assim? Para o saber façamos uma pequena experiência. Lançamos a moeda ao ar um certo número de vezes e contamos quantas vezes saiu caras. Mas repetimos esta experiência várias vezes e, de cada vez, anotamos o número de caras que saiu.

Com estes valores guardados podemos fazer um gráfico com a frequência dos valores obtidos. Se tudo bater certo com a teoria, esperamos obter uma distribuição em que o valor médio é o número que a teoria nos indica. Assim, se atiramos a moeda ao ar 10 vezes, repetindo a experiência 100 vezes, esperamos ter visto no final de toda a experiência 5 caras a maior parte das vezes. É claro que algumas vezes será menos e noutras mais. Vamos ver.

Caso de 100 experiências com 10 lançamentos em cada uma.



Agora 5000 experiências com 1000 lançamentos em cada uma.




Como se vê claramente, no primeiro caso temos um valor médio de 5 e, no segundo, de 500. Além disso, reparamos na forma da curva da distribuição, a forma de um sino. É isso mesmo, estamos perante uma distribuição normal, ou gaussiana!

Mas como obtivemos as imagens? Com o Python, claro! Para já mostro o código. Nas próximas aulas vão perceber o que ele significa e como podem começar a resolver questões semelhantes.

from operator import itemgetter
from pylab import *
import random


def moeda(vezes, numero):
# calcula
caras = {}
for i in range(vezes):
ca, co = moeda_ao_ar(numero)
caras[ca]= caras.get(ca,0) + 1
# visualiza
chaves, valores = split_dic(caras)
ver(chaves,valores)

def moeda_ao_ar(n):
"""Avalia o número de caras e coroas após n lançamentos de uma moeda. """
conta_caras = 0
conta_coroas = 0
for i in range(n):
valor = random.randint(0,1)
if valor == 0:
conta_caras = conta_caras + 1
else:
conta_coroas = conta_coroas + 1
return (conta_caras, conta_coroas)

def split_dic(dicio):
"""
Separa um dicionário numa lista com duas listas: chaves e valores.
A separação é feita de modo a ter as listas ordenadas pelas chaves.
"""
lista = dicio.items()
lista.sort(key=itemgetter(0))
chaves = [ chave for chave,valor in lista]
valores = [ valor for chave,valor in lista]
return chaves,valores


def ver(x,y):
"""Faz um plot simples."""
title('Distrib. Caras')
xlabel('Valor')
ylabel('Freq.')
plot(x,y)
show()

Problema 4.12

Este problema tão simples dá-nos a oportunidade de falar uma vez mais sobre como chegar ao programa a partir do enunciado do problema. Em primeiro lugar, temos que identificar os dados e o resultado. Como o que se pretende saber é se um dado número é perfeito, fica claro que o dado é o número e o resultado é um indicador (True ou False) que nos sinaliza se o número é perfeito ou não. Em segundo lugar, temos que perceber o modo de comunicação entre o programa e o seu ambiente. O enunciado por ou não impor um tipo específico de comunicação. Por exemplo, pode dizer “Escreva um programa em que é pedido ao utilizador...”, ou “... e imprima o resultado”. Nestes casos ficava claro que os dados eram introduzidos pelo utilizador (input, raw_input) e os resultados seriam impressos (print). Nado nos é dito, pelo que vamos supor que os dados são comunicados via parâmetros formais e os resultados são devolvidos através de return. Em terceiro lugar, devemos preocupar-mo-nos com a representação dos objectos. Aqui é trivial: são números e booleanos. Em quarto lugar, vamos responder à questão de como ligar os dados ao resultado, isto é, qual o processo (ou algoritmo) que nos vai permitir resolver o problema. Uma abordagem metodológica simples consiste na divisão do problema principal em sub problemas:

1. Calcular os divisores
2. Calcular a soma dos divisores
3. Testar se o número é igual à soma dos seus divisores (excluindo o próprio número!)

Com o que sabemos ainda não estamos em condições de resolver as questões 1. e 2. em separado. Para tal teríamos que guardar os divisores nalgum lado! Por isso, vamos juntar os dois passos em apenas um, calculando os divisores e mantendo associado a uma variável a soma dos divisores já encontrados.

def perfeito(n):
"""Determina se um número inteiro positivo é perfeito, isto é,
igual à soma dos seus divisores próprios."""
# 1. 2.: divisores e soma
soma = 0
for i in range(n):
if (n % i) == 0:
soma = soma + i
# 3. Testa e devolve resultado
return soma == n

Problema 4.11

O desafio é calcular o valor aproximado da série harmónica, valor esse controlado pelo erro máximo admissível. A solução é semelhante à do problema 4.10, mas agora vamos ter que usar como critério o erro. É-nos dito que esse erro é calculado pela diferença entre dois valores consecutivos da soma. De tudo isso decorre uma solução simples.

def harmonica_erro(erro):
""" Calcula valor da série harmónica com erro inferior ao dado."""
soma = 0
denominador = 1.0
while True:
nova_soma = soma + 1.0/denominador
if nova_soma - soma < erro:
return nova_soma
soma = nova_soma
denominador = denominador + 1

A solução apresentada tem por base um ciclo que vai sendo executado até se atingir o objectivo do erro máximo. Na realidade criámos um aparente ciclo infinito (a condição de saída nunca é falsa). Aparente porque o comado return permite sair do ciclo. Mais ainda: permite sair do programa! Notar também que sendo o numerador fixo (vale sempre 1) optou-se por gerar apenas o denominador. Por se tratar de uma divisão, tivemos que ter o cuidado de usar um float (1.0). Finalmente deve-se analisar o modo como temos sempre dois calores consecutivos associados às variáveis soma e nova_soma.

sábado, 30 de outubro de 2010

Problema 4.9

Uma resposta simples a esta pergunta consiste em ir gerando os números entre ind e sup, testando se são ímpares e imprimir os que forem. Ou seja, o que temos a fazer é repetir as duas operações seguintes:

- gerar próximo candidato
- se for ímpar, imprime

Duas versões possíveis de solução:

def impar_3(inf, sup):
"""Imprime os números impares entre inf e sup."""
while inf <= sup:
if (inf % 2) != 0:
print inf
inf = inf + 1

def impar_4(inf, sup):
"""Imprime os números impares entre inf e sup."""
for i in range(inf,sup+1):
if (i % 2) != 0:
print i


Como se nota, num casio usamos um ciclo while e no outro um ciclo for. Mas podemos ter ainda uma outra alternativa. Neste caso usamos o range para gerar os ímpares. Só temos que garantir que o começo é o primeiro ímpar maior ou igual a inf.

def impar_2(inf,sup):
"""Imprime os números impares entre inf e sup."""
inf = (inf - 1) % 2 + inf
for i in range(inf,sup + 1, 2):
print i


Reparar que no caso em que usamos o ciclo for o range vai até sup+1. Porquê?

domingo, 17 de outubro de 2010

Problema 4.7

Relembremos o enunciado:


Um ano é bissexto, isto é tem 366 dias, se for divisível por 4. Existem, no entanto excepções: se for divisível por 100 então não é bissexto. Mas também esta excepção tem uma excepção: se forem também divisíveis por 400 são bissextos. Escreva um programa que determine se um dado ano é bissexto ou não.

Mais um enunciado a requerer análise cuidada. Mas vejamos alguns exemplos.

2400: é divisível por 4 ? Sim! Então é ... Calma! Também é divisível por 100! Logo... Calma mais uma vez, não se precipite, pois também é divisível por 400. Conclusão: é bissexto!!!

A maneira mais fácil de fazer é colocar o caso inequívoco em primeiro lugar no nosso teste. E esse é ser divisível por 400. Vem depois a primeira excepção: divisível por 100. Só de seguida o caso usual da divisibilidade por 4. Se tudo isto falhar, garantidamente não é bissexto. Em programa:


def ano_bissexto(ano):
""" Determina se um ano é bissexto."""
if (ano % 400) == 0:
return True
elif (ano % 100) == 0:
return False
elif (ano % 4) == 0:
return True
else:
return False


Quando será o próximo ano bissexto??

Problema 4.5

Saber se um ponto se encontra ou não dentro de um círculo pode ser resolvido de modo rápido. Como acontece com frequência, fazer um desenho pode ajudar. Senão reparem.



Olhando para a figura fica claro que estar ou não dentro do círculo depende do comprimento do segmento que liga a origem dos eixos ao ponto e a sua relação com o tamanho do raio. Posto isto, temos a solução óbvia, amitindo que o centro está na posição (0,0)..

import math

def interior_circulo(x,y,raio):
""" Determina se o ponto está no interior do círculo.
Supõe centro no ponto (0,0)."""
distancia = math.sqrt(x ** 2 + y ** 2)
if distancia <= raio:
return True
return False

Quem estiver esquecido da trignometria ... tem que ir rever a matéria!

Problemas 4.3, 4.4 e 4.6

Problema 4.3

Os problemas da ficha 4 sobre condicionais são, em geral, muito acessíveis. Este não foge à regra.

def nota_iprp(nexame, teste,projecto):
"""Determina a nota de iprp. Exame vale 60%, teste, vale 20% e projecto 20%.
Asume que os valores introduzidos estão correctos."""
nota = 0.6 * exame + 0.2 * (teste + projecto)
if nota <9.5 :
return 0
else:
return 1


Problema 4.4

Este problema não tem solução única. Uma ideia a ter em conta pode ser o de minimizar o número de testes a efectuar.

def maior_3(n1,n2,n3):
""" Determina o maior dos três números."""
if n1 > n2:
if n1 > n3:
return n1
else:
return n3
else:
if n2 > n3:
return n2
else:
return n3

Claro que estas abordagens não são escaláveis. Imaginem se eram 100 números!! Veremos com a introdução dos ciclos que existe uma resposta a esta questão que não depende do número de números. Mas, se conhece bem Python, já sabe que também podia deixar o problema nas mãos das funções pré-definidas:

>>> max(1,6,3,9,34,98,4)
98
>>>

Problema 4.6

E agora a questão da fruta. Nada como ter um bom desconto...

def custo_fruta(peso, preco, peso_minimo):
""" Qual o custo da fruta?."""
a_mais = peso - peso_minimo
if a_mais > 0:
custo = peso_minimo * preco + a_mais * 0.75 * preco
else:
custo = peso * preco
return custo

Das aulas resultou que a maior dificuldade sentida foi a de entender o enunciado. Têm que se habituar a interpretar correctamente o texto que descreve o problema a resolver.

sábado, 9 de outubro de 2010

Problema 3.9

Problema básico, que se resolve por aplicaçção directa das fórmulas dadas.


def polar_para_cartesiana(r, theta):
""" Converte de coordenadas polares para cartesianas."""
x = r * math.cos(theta)
y = r * math.sin(theta)
return x,y

Problema 3.8

Este é um problema trivial do ponto de vista da programação, mas que obriga a ter conhecimentos sobre o domínio (neste caso trigonometria). Não existe um modo único de encontrar as fórmulas que definem o módulo (r), e o ângulo (teta) em função de x e de y. Aqui, optámos por usar o teorema de Pitágoras para chegar ao módulo e o arco tangente para obter o ângulo. Quem tiver dúvidas vai ter que rever os seus conhecimentos sobre trigonometria...


def cartesiana_para_polar(x,y):
""" converte de coordenadas cartesianas para polares."""
r = sqrt(x**2 + y**2)
theta = math.atan(float(y)/x)
return r, theta


Nota: Usar a função atan() pode trazer problemas pois o quadrante do ângulo não é determinada. Por exemplo, se as coordenadas forem (1,1) e (-1,-1) , vamos ter sempre o mesmo resultado, 45º, e não esse, na primeira situação, e 135º na segunda. Também há problemas caso o ponto seja (1,0). A solução está em usar atan2():



def cartesiana_para_polar(x,y):
""" converte de coordenadas cartesianas para polares."""
r = sqrt(x**2 + y**2)
theta = math.atan2(y,x)
return r, theta


Notar a vírgula a separar os argumentos, em vez da divisão.

Problema 3.4

Semelhante ao problema 3.3, com a diferença de nos fazer perceber ainda que existem várias maneiras de chegar ao mesmo resultado, e que algumas vezes quem não tem cão tem que caçar com rato.


import math

print math.sqrt((x ** 2 + x ** 2) * 2) / 2

print ((x ** 2 + x ** 2) * 2) ** (1.0 / 2) / 2

Problema 3.3

Problema muito simples que apenas pretendia duas coisas: (1) treinar a passagem de um enunciado para um pedaço de código e, (2) chamar a atenção para o facto de os operadores aritméticos estarem sobrecarregados dependendo o resultado final do tipo dos objectos usados. Use as duas versões para diferentes valores e repare nos resultados. São sempre iguais?


x = 4

print (((x - 6) / 3) + 2) * 3

print (((x - 6) / 3.0) + 2) * 3

terça-feira, 5 de outubro de 2010

Ovo Estrelado

No outro dia voltei ao passado quando almocei um dos meus pratos preferidos de miúdo: bife com batatas fritas e ovo estrelado. Soube-me bem e, em particular, o ovo estrelado estava óptimo. Quis saber como é tinha sido feito e o cozinheiro, entretanto chamado, prontificou-me a revelar o seu segredo. Para que seja eternizado, disse-me, vou escrever a receita numa folha de papel. No final deu-me a folha que dizia o seguinte:

RECEITA
Ovo Estrelado

Ingredientes: 1 ovo, óleo e sal q.b.
Material: fogão, frigideira, colher, prato

1- Coloque o óleo na frigideira e leve ao lume
2- Quando o óleo estiver quente, parta o ovo e deite-o na frigideira
3- Use a colher para regularmente deitar o óleo quente por cima do ovo
4- Quando tiver a consistência desejada, use a colher para o retirar da frigideira e coloque-o no prato.

Uns dias mais tarde, apeteceu-me comer um ovo estrelado e lembrei-me de usar a receita do restaurante. Fui buscar os ingredientes, preparei o material e retirei do bolso a receita que executei. No final, claro, comi o ovo!

Mas o que é que esta história tem a ver com a programação? Muito! É que programar é uma actividade que significa fundamentalmente escrever receitas (para resolver problemas), que alguém mais tarde vai usar. Vejamos como podemos explorar a analogia.

Em Python eu indico que vou escrever uma receita escrevendo a palavra reservada def. O nome da receita é nome da função. Os ingredientes são os parâmetros formais, isto é uma lista ordenada de nomes, separados por vírgulas e envolvidos por parêntesis. O material, são as eventuais constantes e nomes auxiliares criados pelo programa. Usar uma função significa (1) ligar os parâmetros reais aos parâmetros formais, processo esse que é feito quando chamamos (usamos) a função e, (2) executar as instruções da função. Do mesmo modo que na receita falamos em abstracto de um ovo, e quando realizamos a receita temos que usar um ovo concreto, também na programação os parâmetros formais são nomes genéricos, enquanto os parâmetros reais têm que remeter directa ou indirectamente para objectos. A ligação entre os parâmetros formais e os reais não é mais do que uma associação, ou seja uma instrução de atribuição implícita, entre o nome do parâmetro formal e o objecto para que remete o parâmetro real. Esta associação dura enquanto a função estiver a ser executada.

Um exemplo simples.

# Definição

def soma(x,y):
"""Calcula o valor da soma de x com y."""
return x + y

# Uso

a = 3
print soma(a,7)

Neste caso, o nome do programa é soma e os parâmetros formais são x e y. Chamamos a função usando o seu nome e os respectivos parâmetros reais. Neste caso, os parâmetros reais são o nome a (que está associado ao objecto 3) e o objecto 7. É como se tivesse-mos feito:

# Definição
def soma():
"""Calcula o valor da soma de x com y."""
x = a
y = 7
return x + y
# Uso
a = 3
print soma()

Porque devemos preferir sempre a primeira versão?? Pense um pouco e verá que a razão é simples!

Ao executar o código da função o resultado devolvido é, neste caso concreto, 10.

sábado, 2 de outubro de 2010

Problema 2.11

Quando chegarem a este problema, se os anteriores foram percebidos existe apenas a dificuldade de importar o módulo math e usar o método sqrt do módulo. Vejamos uma solução.

import math

def distancia(x1,y1,z1,x2,y2,z2):
"""
Distância Euclidiana entre dois pontos de coordenadas (x1,y1,z1) e (x2,y2,z2).
"""
dist= math.sqrt((x2 - x1)** 2 + (y2 - y1 ) ** 2 + (z2 - z1 ) ** 2)
return dist

Tendo em atenção o que foi dito em post anterior sobre legibilidade, modularidade e reutilização de código, acha que pode fazer melhor? Não custa nada tentar!

Problema 2.8

Este problema obedece à mesma matriz dos da ficha 2: aplicar a expressão que relaciona os dados ao resultado. Uma vez mais, começamos por identificar os dados: o raio e a altura de um cilindro e ainda a constante Pi. É-nos dito para usar como aproximação de Pi o valor 3.14159. Traduzindo o enunciado podemos apresentar a solução seguinte.

def area_cilindro(raio, altura):
"""
Calculo da área de um cilindro, conhecidos o raio da base
e a altura.
"""
return 2 * 3.14159 * raio * altura + 2 * (2 * 3.14159 * raio ** 2)

Um dos problemas que se colocam a quem programa é que os seus programas destinam-se a ser usados, lidos, modificados por outros. Muitas vezes até pelos próprios. Tudo isso leva a que os programas sejam modulares e claros. Estas propriedades estão ausentes mesmo num programa tão pequeno e simples como o anterior. Vamos tentar melhorá-lo.

def area_cilindro(raio, altura):
"""
Calculo da área de um cilindro, conhecidos o raio da base
e a altura.
"""
lateral = 2 * 3.14159 * raio * altura
base = 2 * 3.14159 * raio ** 2
return lateral + 2 * base

Nesta solução dividimos o nosso problema em dois sub-problemas. Obtemos o resultado de cada um e associamos esses objectos a nomes significativos. Depois é só combinar esses resultados para obter o resultado final. Concordarão que está mais legível!

Mas podemos ainda pensar no aspecto modularidade e reutilização de código. Para tal criamos duas definições auxiliares.


def area_lateral(raio, altura):
""" Calculo da área lateral de um cilindro."""
return 2 * 3.14159 * raio * altura

def area_circulo(raio):
""" Calculo da área da base de um cilindro."""
return 3.14159 * raio ** 2

def area_cilindro(raio, altura):
""" Calculo da área de um cilindro."""
area_lat = area_lateral(raio,altura)
area_base = area_circulo(raio)
return area_lat + 2 * area_base

A legibilidade mantém-se e passamos a ter duas definições autónomas que podemos usar para outros cálculos, como por exemplo a área de um círculo.

Problema 2.7

Resolver um problema quando se tem uma fórmula que liga directamente os dados ao resultado é simples. Mesmo assim podemos ter várias versões. Vejamos com o exemplo do cálculo do declive de uma recta conhecidas as coordenadas de dois dos seus pontos.


def declive(x1,y1,x2,y2):
"""
Calcula o declive de uma recta conhecidos dois dos seus pontos.
"""
return (y2 - y1) / (x2 - x1)

Mais simples não há! Com uma pequena mudança, introduzimos um nome, que associamos ao resultado.

def declive(x1,y1,x2,y2):
"""
Calcula o declive de uma recta conhecidos dois dos seus pontos.
"""
inclina = (y2 - y1) / (x2 - x1)
return inclina

Quando começamos a efectuar alguns testes, verificamos que há resultados incorrectos. Por exemplo, se tentarmos com os pontos (3,4) e (6,6) obtemos 0 e não o valor correcto 0.66. O problema está na divisão. Se os argumentos são inteiros o resultado será o quociente inteiro! Duas soluções para o problema. Uma é introduzir os dados como floats. Mas esta não é a melhor opção, pois coloca do lado do utilizador a responsabilidade de saber como foi definida a solução. Assim a melhor escolha, neste caso, é forçar uma das coordenadas a ser um float, no interior da definição.

def declive(x1,y1,x2,y2):
"""
Calcula o declive de uma recta conhecidos dois dos seus pontos.
"""
inclina = (float(y2) - y1) / (x2 - x1)
return inclina

Mas e se as abcissas forem iguais?


Evaluating ficha_2_2010_sol.py
Traceback (most recent call last):
File "/Applications/WingIDE.app/Contents/MacOS/src/debug/tserver/_sandbox.py", line 150, in
File "/Applications/WingIDE.app/Contents/MacOS/src/debug/tserver/_sandbox.py", line 83, in declive_2
ZeroDivisionError: float division


Oops! Como corrigir? Uma vez mais podemos acautelar isso aquando do uso do programa ou na própria definição. Neste último caso existem várias alternativas de que falaremos ao longo do curso. Passam todas por filtrar o caso que leva ao erro. Deixamos aqui a solução mais simples.

def declive_3(x1,y1,x2,y2):
"""
Calcula o declive de uma recta conhecidos dois dos seus pontos.
"""
if x1 == x2:
print 'Erro: as abcissas não podem ser iguais'
return False
else:
inclina = (float(y2) - y1) / (x2 - x1)
return inclina


A solução consiste em fazer um teste à condição de erro que, a verificar-se, leva à impressão de uma mensagem de erro e ao terminar do programa que devolve o objecto False.

Problema 2.1

Recordemos o enunciado.


É de madrugada, mais precisamente 6h00 da manhã. Já acordado, ouve o seu relógio de parede bater 6 badaladas. De acordo com o seu relógio de pulso passaram 30 segundos entre a primeira badalada e a última. Pensa: quando tendo demorará a bater as 12 badaladas quando for meio-dia? Sem muito reflectir, conclui que vão ser 60 segundos. Porquê? Bem se demorou 30 segundos a bater as 6 badaladas é porque cada uma demora 5 segundos. Certo? Logo 12 * 5 = 60. Piece of cake! Mas será mesmo assim? Pense um pouco e dê-me a sua opinião.


Esta abordagem ignora as leis do mundo físico: (1) as badaladas têm uma duração, seguramente, mas (2) existe um intervalo de tempo que decorre entre duas badaladas.

Recorrendo à velha ideia de por um problema em equação temos então:

6 * X + 5 * Y = 30 (eq. 1)


com X a duração das badaladas, e Y a duração dos intervalos. O nosso problema é então:

12 * X + 11 * Y = ??? (eq. 2)


O que é que podemos desde já dizer? Multiplicando ambos os termos da equação 1 por 2, fica:

12 * X + 10 * Y = 60 (eq. 3)


ou ainda,

12 * X + 11 * Y = 60 + Y (eq. 4)


logo podemos concluir que vai demorar mais do que 60 segundos. Mas quanto? De (eq. 4) resulta que depende da duração dos intervalos, Y. Mas mais não podemos dizer. Na realidade, existem uma infinidade de soluções!!! Por exemplo, se admitirmos que a badalada é instantânea, isto é, X = 0, então Y = 6 e as 12 badaladas demorarão 66 segundos. Outras combinações são possíveis!

Problema 1.6

Os problemas da Ficha 1 são todos muito acessíveis. Não deixam por vezes de nos ensinar algumas coisas. Uma é que devemos testar sempre o programa na tentativa de eliminar a possibilidade de erros. Os testes devem ser feitos usando exemplos que explorem casos típicos e limites. Vamos exemplificar com o problema 1.6. Recordemos o enunciado.

Imagine uma lista telefónica em que cada página tem L linhas e cada linha C colunas. Em cada posição (Linha,Coluna) é guardada a informação pertinente. Admita que as páginas, linhas e colunas se iniciam sempre a 1. Conhecido o índice da sua informação, em que página, linha e coluna se encontra?

Trata-se de um problema semelhante ao de saber quantas horas, minutos e segundos estão contidos num certo número de segundos. Assim, a nossa solução passa por saber a quantas páginas corresponde o nosso índice. Este valor obtém-se usando uma divisão inteira do nosso índice pelo número de células numa página. No entanto, com as páginas começam a 1, temos que adicionar precisamente 1 ao valor com a divisão. Sabida a página, vamos tentar saber a linha. Para isso temos que determinar primeiro qual o índice dentro da página, valor que se obtém do resto da divisão anterior. É este elemento que agora vai ser desdobrado pelas linhas. Como cada linha tem C colunas é por C que temos que dividir o valor do resto. Também aqui, porque a linha começa a 1, temos que adicionar um ao resultado. Finalmente, falta conhecer a posição (coluna) dentro da linha. Repetimos a estratégia: primeiro usamos o valor do resto da última divisão. Calculando o resto deste valor, quando o dividimos pelo número de células numa linha, obtemos o resultado final pretendido.

Como se vê este problema resolve-se basicamente com divisões inteiras e cálculo do resto. Entendido isto, listemos o código.


# Entrada dos dados
pos = input("posição: ")
L = input("Linhas: ")
C = input("Colunas: ")
# página
pagina = (pos / (L * C)) + 1
print "pagina: ", pagina
# Linha
resto_pagina = pos % (L * C)
linha = resto_pagina / C + 1
print "linha: ", linha
# Coluna
resto_linha = resto_pagina % C
coluna = resto_linha % C
print "coluna: ", coluna

Tudo isto é muito bonito. Mas testarmos o programa em situações limite temos problema. Eis um exemplo:


posição: 15
Linhas: 5
Colunas: 3
pagina: 2
linha: 1
coluna: 0
>>>

Outro exemplo ainda:


posição: 6
Linhas: 5
Colunas: 3
pagina: 1
linha: 3
coluna: 0
>>>


Conclusão: se a célula estiver ou na última linha de uma página ou na última coluna de uma linha, temos erro! É fácil de ver que isso ocorre quando alguma das divisões dá resto zero. Em breve aprenderemos a usar estruturas de controlo condicionais que nos permitirão resolver estes casos marginais.