Ir para o conteúdo

Problema da árvore

Este é um problema bastante simples de se resolver em linguagens imperativas, a utilização de alguns ciclos for resolve bem esta questão. Contudo, numa linguagem funcional como Haskell, em que os ciclos não existem, o pensamento a seguir deverá ser outro.

Há várias soluções possíveis, usando recursividade ou, como será apresentado de seguida, com recurso a listas por compreensão.

O código

Compactado

Esta solução não será a melhor, pois repete bastante a estrutura take $ repeat e é mais obscura (não é tão fácil interpretar a lista por compreensão).

import qualified Data.Char as Char

mainarv :: IO()
mainarv = do
  putStr "Quantos ramos (0-40) (<=0 para terminar)? "
  n <- readLn :: IO Int
  if n > 0 && n <= 40 then do
    mapM_ putStrLn $ arvore n
    mainarv
  else if n <= 0 then
    putStrLn "FIM."
  else
    putStrLn "Erro: ramos > 40!"

arvore :: Int -> [String]
arvore 0 = []
arvore ramos | ramos > 0 && ramos <= 40 = [(take (ramos-i) $ repeat ' ') ++ (take (i*2-1) $ repeat '*') | i <- [1..ramos]] ++ [(take (ramos-1) $ repeat ' ') ++ "*" | _ <- [1..3]]
             | otherwise                = []

Extendido

Vamos portanto substituir a função arvore pela seguinte:

arvore :: Int -> [String]
arvore 0 = []
arvore ramos | ramos > 0 && ramos <= 40 = copa ramos ++ tronco ramos 3
             | otherwise                = []
  where
    tronco r l = [repeatChar ' ' (ramos-1) ++ "*" | _ <- [1..l]]
    copa r = [repeatChar ' ' (ramos-i) ++ repeatChar '*' (i*2-1) | i <- [1..ramos]]
    repeatChar ch n = take n $ repeat ch

Breve explicação

A função arvore vai devolver uma lista de Strings, cada uma das quais representa uma linha da árvore. É por isto que, na função main, fazemos um mapM_ putStrLn (desta forma cada string da lista será colocada numa linha).

Esta função arvore recebe tão-somente o número de ramos que deverá desenhar, e com esse número calcula tudo o que é necessário para a árvore.

Consideremos uma árvore com 5 ramos:

*Main> main
Quantos ramos (0-40) (<=0 para terminar)? 5
    *
   ***
  *****
 *******
*********
    *
    *
    *
Quantos ramos (0-40) (<=0 para terminar)? 0
FIM.

Para cada ramo o númeor de espaços será dado por "ramos-i", sendo "i" o númeor do ramo, e o número de asteriscos será dado por "i*2-1". Vejamos este teste no GHCi para entender melhor:

Prelude> map (subtract 1 . (*2)) [1..5]
[1,3,5,7,9]

A lista devolvida pelo GHCi corresponde ao númeor de asteriscos em cada ramo.

Por fim, a posição do tronco será igual a "ramos-1" (é só um asterisco na mesma posição do ramo 1). Neste ramo, i=1, pelo que "ramos-i" será "ramos-1".

Desta forma:

-- espaços no ramo "i"
repeatChar ' ' (ramos-i)

-- asteriscos no ramo "i"
repeatChar '*' (i*2-1)

-- tronco
repeatChar ' ' (ramos-1) ++ "*"

Criámos, por fim, funções que criam estas sequências através de listas por compreensão, localizadas no bloco where da função arvore. Assim a leitura da função simplificou imenso.