Ferramentas de Usuário

Ferramentas de Site


dev_geral:haskell:arvore

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º de ramos que deverá desenhar, e com esse nº 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º de espaços será dado por "ramos-i", sendo "i" o nº do ramo, e o nº 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 devolvido pelo GHCi corresponde ao nº 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.

dev_geral/haskell/arvore.txt · Última modificação em: 2018/05/14 21:37 (edição externa)