Ficha OCaml 3. Cálculo e recursividade.

 

 

Ficha OCaml 3. Cálculo e recursividade.Nível BásicoEx. Contas simples.Ex. Distância de Manhattan.Ex. Um algoritmo histórico.Ex. a melhor forma de chegar ao infinito e mais além.Ex. Aproximar .Ex. Algarismos.Ex. As sequências de Hofstadter-Huber Qr,s(n).Ex. As sequência fêmeas e machos de Hofstadter. Nível IntermédioEx. Somatórios.Ex. Tribonacci.Ex. recursividade terminal.Ex. Exponenciação rápida.Ex. Catalã: um povo, um creme e uns números.Ex. Dicotomia.Ex. A função 91 de McCarthy.Ex. Contar triângulosEx. A conjectura de Collatz ou de Hailstones ou de Syracuse ou de mais uns nomes giros...Ex. Estrelinhas como fractais textuais.Ex. Números de Bell.Nível ConsolidadoEx. Tribonacci em tempo logarítmico.Ex. Quantas montanhas?Ex. A grandeza do Dragão.Ex. As Torres de Hanói.

 

Nível Básico


 

Ex. Contas simples.

 

Ex. Distância de Manhattan.

 

Considere o problema da otimização de rotas de táxi em cidades modernas como Manhattan, por exemplo. Considere o mapa seguinte em que as estradas estão assinaladas a cinzento. Considere igualmente que cada prédio da cidade, os quadrados brancos, tem por dimensão 1 (um).

(imagem retirado da wikipédia )

Qual é a menor distância de táxi entre o ponto inferior esquerdo e o ponto superior direito?

Se pudéssemos voar, a distância euclidiana seria a solução (a rota verde). Mas para a distância percorrida de carro, o cálculo deverá ser diferente. Assim sendo, qual é o comprimento da rota a vermelho? da rota azul? ou da rota amarela?

 


 

Ex. Um algoritmo histórico.

Vamos implementar neste exercício um algoritmo histórico, foi formulado pelo próprio Euclides 300 AC.

Em particular, interessa-nos a versão recursiva.


 

Ex. a melhor forma de chegar ao infinito e mais além.

Wilhelm Friedrich Ackermann, matemático alemão, contribuiu ao estudo das funções computáveis apresentando uma função com propriedades inéditas: uma função total computável que fugia, na data da sua descoberta, à classificação que se pensava ser a das funções com estas características.

Em parte porque a função... tem um comportamento explosivo!

A função originalmente proposta tinha 3 parâmetros, mas a que ficou registada para a história contempla dois parâmetros e é definida da seguinte forma:

Calcule no papel o resultado de A(4,4).

Se ler esta frase, fique sabendo que A(4,2) tem como resultado um número com mais do que 19 mil dígitos () e que a alínea anterior resulta de uma piada de mau gosto que não se espere, claro, que resolva!

Agora sim: defina a função recursiva ackermann int -> int -> int que calcula esta função.

Por exemplo ackermann 3 4 = 125


 

Ex. Aproximar .

A formula de Wallis permite uma aproximação de definida nos termos seguintes:


 

Ex. Algarismos.

 

Ex. As sequências de Hofstadter-Huber Qr,s(n).

Sejam e dois inteiros naturais positivos tais que e . Define-se por sequência de Hofstadter-Huber de família a sequência determinada pela fórmula seguinte

em que é um inteiro positivo.

Esta família de valores sofre de irregularidades. Em particular diz-se que morre quando os valores não estão definidos (i.e. quando ou ), ou quando qualquer outra condição estabelecida sobre e não é respeitada.


 

Ex. As sequência fêmeas e machos de Hofstadter.

Em "Hofstadter, D. R. Gödel, Escher, Bach: An Eternal Golden Braid. New York: Vintage Books, p. 137, 1989.", Hofstadter definiu várias sequências numérias, uma delas é o par de sequencias fêmeas e machos seguintes:


 

 

 

Nível Intermédio


 

 

Ex. Somatórios.

Considere a expressão matemática

  1. Defina a função sum3 : int -> int tal que sum3 n devolva o valor .
  2. Dê uma versão recursiva terminal.
  3. (opcional, se esta matéria for dada) Dê a versão CPS.

 

Ex. Tribonacci.

Considere a função tribonacci seguinte, sobre inteiros naturais:

  1. Define a função recursiva em OCaml.
  2. Define a versão iterativa (com ciclos e referências) em OCaml.
  3. Dê uma versão recursiva terminal em OCaml.

 

Ex. recursividade terminal.

Considere a função seguinte:

 


 

Ex. Exponenciação rápida.

Este exercício debruça-se sobre a função de exponenciação inteira . A definição natural e direta desta operação sugere multiplicações. Mas uma definição alternativa simples permite realizar o mesmo cálculo minorando o número de multiplicações:

Tal método de exponenciação é , as vezes, designada de exponenciação rápida.

  1. Proponha uma função recursiva em OCaml que implemente esta definição.
  2. Qual é a complexidade desta exponenciação (em número de multiplicações realizadas)?
  3. Dê uma versão recursiva terminal da exponenciação rápida

 


 

Ex. Catalã: um povo, um creme e uns números.

Uma famosa sequência de números inteiros conhecida por Números de Catalan (do matemático belga Eugène Charles Catalan, 1814–1894) é definida por:

Esta sequência está envolvida na contagem de variadíssimos fenómenos combinatórios.

 


 

Ex. Dicotomia.

Seja v um vetor de inteiros ordenado de forma crescente de tamanho n​, e x um inteiro.

Vamos definir uma função de pesquisa eficiente que tire proveito da ordenação do vetor.

Considere dois índices i, j do vetor v tal que . Vamos procurar x no vetor v entre os índices i e j, (notação )

 

 

De notar que esta pesquisa divide o espaço de procura por dois de cada vez que refina a sua pesquisa. Esta característica melhora em muito os tempos de resposta. O pior caso é quando o elemento procurado não está no vetor. Mesmo assim, o número de comparações realizadas pelo algoritmo nunca ultrapassa a ordem de sendo o tamanho do vetor.


 

Ex. A função 91 de McCarthy.

Nos anos 70, John McCarthy, um dos pais da inteligência artificial, em parceria com o Zohar Manna e Amir Pnueli, procurou definir uma função recursiva que seja um caso de escola para a verificação de programas.

Esta função é a função definida da forma seguinte:


 

Ex. Contar triângulos

Considere a seguinte sequência de triângulos equiláteros:

 

 

Se listarmos o comprimento dos lados dos triângulos desta sequência temos:

 


 

Ex. A conjectura de Collatz ou de Hailstones ou de Syracuse ou de mais uns nomes giros...

 

A conjetura de Collatz (ou Hailstones, ou de Syracuse) é da autoria do matemático alemão Lothar Collatz que desafiou a comunidade científica durante um evento na Universidade de Syracuse em 1928. A conjetura estabelece uma sequência de números (designada também de trajetória ou voo) que a partir de um número natural inicial obedece aos seguintes critérios:

Até a data de hoje, ninguém encontrou um numero inicial, n, que não gere uma trajetória que termine no número ! (sem o critério de paragem, obteríamos um ciclo que passaria ad eternum pelo valor 1)

Exemplos

Por exemplo, collatz_big 27 devolve a trajetória seguinte no ecrã:

 


 

Ex. Estrelinhas como fractais textuais.

Observe o seguinte padrão constituído de espaços e de asteriscos.

 

Descubra as regras de construção que o estrutura e escreva uma função recursiva estrelinhas : int-> unit que dado um parâmetro inteiro , potência de pertencente ao intervalo , reproduza este padrão tal que a maior quantidade em linha de asteriscos seja .

Por exemplo estrelinhas 8 imprime o padrão acima apresentado.

No caso de um argumento inválido, a exceção Invalid_argument "estrelinhas" é lançada.


 

Ex. Números de Bell.

Quantas formas temos de particionar um conjunto? Por exemplo, quantas formas temos de particionar .

Neste exemplo, temos formas de particionar:

De uma forma geral, designa-se por -ésimo número de Bell, , o número de partição de um conjunto de elementos em sub-conjuntos não vazios.

Uma definição elegante destes números é:

No caso de um argumento inválido ou da deteção de qualquer situação anómala, a exceção Failure "bell" é lançada.


 

 

Nível Consolidado

 


 

Ex. Tribonacci em tempo logarítmico.

Recordamos a definição da sequência de tribonacci.

Define a função tribonacci_fast : int -> int que calcula o n-ésimo elemento da sequencia de tribonacci. Assim tribonacci_fast 25 = 1800281, ou tribonacci_fast 42 = 6804250945 instantaneamente.

Para tal aconselha-se que adapte a técnica em uso para calcular a sequência de fibonacci em tempo logarítmico com base na exponenciação rápida de matrizes.


 

Ex. Quantas montanhas?

Vamos aqui considerar um problema clássico de combinatória (de contagem).

Dada um valor , imagine uma paisagem de montanha inscrita numa grelha até .

No nosso cenário, uma paisagem é constituída exclusivamente de montanhas cujo declive é uma diagonal um por um ascendente ou descendente, que começa em e acaba em .

Não se consideram também perfis de montanha que descem por baixo do eixo dos .

O problema por resolver é: dado (que define o tamanho da paisagem) e o número de picos ( sempre menor ou igual a ), quantos perfis montanhosos válidos existem?

Por exemplo para e , a resposta é . Graficamente podemos visualizar as soluções da seguinte forma:

 

 

Damos igualmente dois valores limites para , que são e

 

Assumimos igualmente que e .

Dica: considere os casos iniciais ( e , seguido de e ou , etc. ) e veja se um padrão emerge a medida que calcula os casos seguintes.


 

Ex. A grandeza do Dragão.

Imagine a seguinte situação.

Tem uma fita de papel que vai querer dobrar ao meio n vezes.

A configuração inicial é (papel sem dobras, vista de perfil):

Se dobrar uma vez e desdobar e deixar que o angulo faça 90º obtém, de perfil a seguinte figura:

Se dobrar duas vezes e desdobar e deixar que os ângulos obtidos façam 90º obtém, de perfil a seguinte figura:

Se dobrar três vezes e desdobar e deixar que os ângulos obtidos façam 90º obtém, de perfil a seguinte figura:

Muito rapidamente o exercício de dobra de papel torna-se penoso mas visualmente interessante (retirado do Wikipédia):

 

A fractal obtida chama-se de curva do dragão.

A imagem seguinte, também retirado do Wikipédia) mostra como obter uma configuração a partir da anterior.

Vamos codificar estas figuras com palavras binárias. O princípio é quando se "um ângulo para a esquerda é 1" e "um ângulo a direita é 0". Assim:

 

Neste exercício estamos interessado em responder a duas perguntas: qual é a palavra obtida apos n dobras? qual é o m-ésima letra da palavra? Para responder, vamos programar.

 

  1. Define uma função dragon_size: int -> int que devolve o tamanho da palavra apos (dado em parâmetro, inteiro positivo, eventualmente nulo) dobras no meio. Por exemplo dragon_size 4 = 15.

    No caso de um argumento inválido, a exceção Invalid_argument "dragon_size" é lançada.

  2. Define uma função dragon: int -> bool list que devolve a lista dos booleanos que formam a palavra da curva do dragão para n (em parâmetro) dobras.

    Por exemplo dragon 3 = [false;false;true;false;false;true;true].

    No caso de um argumento inválido, a exceção Invalid_argument "dragon" é lançada.

  3. define a função dragon_bit : int -> bool que para um parâmetro inteiro positivo não nulo devolve o -ésimo bit da sequência do dragão. Por exemplo dragon_bit 11 = true.

    No caso de um argumento inválido, a exceção Invalid_argument "dragon_bit" é lançada.


 

Ex. As Torres de Hanói.

Exploremos aqui outro exemplo clássico dos jogos recursivos, as famosas torres de Hanói.

O jogo apresenta-se desta forma:

o tabuleiro tem 3 tores de madeira. Na configuração inicial, a torre da esquerda tem vários discos empilhados ordenadamente do maior ao menor. O jogo consiste em deslocar um a um estes discos seguindo escrupulosamente regras simples que enunciamos a seguir por forma a obter a mesma pilha de discos do que a configuração inicial, mas na torre à direita. Por exemplo, a imagem mostra a configuração inicial, uma configuração intermédia e a configuração final, que significa a vitória. A torre do meio poderá ser usada como recurso intermédio para a estratégia de deslocação dos discos de um lado para o outro.

 

As regras do jogo são:

Por exemplo na configuração intermédia apresentada na imagem, se quisermos mexer no empilhamento a esquerda, pela primeira regra, só poderemos tirar o circulo de cima. Pela segunda regra ele não poderá ser colocado em nenhuma das duas outras torres. Porque é maior do que os discos que estão no topo de cada uma das torres em questão.

 

 

Dica: poderá fazer sentido definir uma função auxiliar que tem por assinatura hannoi_aux : int -> int int -> int -> int tal que hannoi_aux n t1 t2 t3 significa deslocar o disco n de t1 para t2 usando como torre intermédia a torre t3.