Haskell/Soluções

Fonte: testwiki
Revisão em 16h43min de 16 de abril de 2020 por imported>DannyS712 (<source> -> <syntaxhighlight> (phab:T237267))
(dif) ← Revisão anterior | Revisão atual (dif) | Revisão seguinte → (dif)
Saltar para a navegação Saltar para a pesquisa

Básico

Predefinição:HaskellExercícios

  1. quadruplicar x = dobrar (dobrar x)
    quadruplicar 5 = dobrar (dobrar 5)
    quadruplicar 5 = dobrar 10
    quadruplicar 5 = 20
    
  2. subtrairMetade x = x / 2 - 12
    

Predefinição:Exercícios

  1. volumeCaixa b a l = b * a * l
    

  2. Uma possível solução para o problema da pirâmide seria: calcular o volume da pirâmide como se ela fosse ideal e dividir tal valor pelo volume estimado de um bloco de pedra. Por exemplo, no GHCi:

Predefinição:HaskellGHCi

Predefinição:Exercícios

  1. area r = pi * r ^ 2
    volumeCil r h = h * area r
    

Predefinição:Exercício

  1. O tipo é [Char]. Trata-se de um string de um elemento só.
  2. GHCi retorna um erro. Aspas simples são usadas para delimitar um único caractere, sendo que Olá, mundo é um string, ou seja, uma cadeia com mais de um caractere.


Predefinição:Exercícios

  1. negativo :: Int -> Int
    
  2. (||) :: Bool -> Bool
    
  3. diasNoMes :: Bool -> Int -> Int
    
  4. f :: Int -> Int -> Bool
    
  5. g :: Int -> Int
    

Criando listas

Predefinição:Exercício

  1. Não. Todos os elementos de uma lista devem ser do mesmo tipo. Em 3:[rue,False] tenta-se adicionar um número a uma lista de Bools, o que não é permitido.
  2. cons8 lista = 8:lista
    
  3. cons8' lista = lista ++ [8]
    
  4. let meuCons lista elemento = elemento : lista

Lista de listas

Predefinição:Exercícios

  1. Apenas o item 3 é válido.
    1. 1:2:3:[]:[]. 1, 2 e 3 são números, enquanto [] é uma lista.
    2. 1:(2:3:[]):4:[]. O mesmo caso de antes
    3. (1:2:3:[]):[]:[]. É válido porque 1:2:3:[] é uma lista de números e [] é uma lista vazia (de qualquer tipo).
  2. Apenas o item 5 é inválido.
    1. [[],[1,2,3],[4,5,6]]. [1,2,3] e [4,5,6] são listas de números, portanto, temos uma lista de listas de números. Assim sendo, pode-se adicionar uma lista vazia a uma lista de listas.
    2. [[]]. Não é uma lista vazia!. Na verdade, é uma lista que contem um único elemento, o qual é uma lista vazia.
    3. [[],[]]. Uma lista contendo duas lista, as quais estão vazias.
    4. [[1],[]]. O mesmo que o caso anterior. Entretanto, uma das duas listas vazias agora contem um elemento. Mesmo assim, continua sendo uma lista de listas.
    5. [["hi"], [1]]. ["hi"] é uma lista de Chars, enquanto [1] é uma lista de Strings, enquanto [1] é uma lista de números.
  3. Sim, é possível. Podemos criar lista em quantos níveis quisermos em Haskell, desde que todos os elementos tenham todos a menas quantidade de níveis. Eis um exemplo: [[[1],[2],[3]],[[4],[5],[6]],[[7],[8],[9]]].
  4. Não é válido porque 3 é um número apenas, enquanto que [1,2] e [4,5]] são listas de números. Uma lista válida seria [[1,2],[3],[4,5]].

N-uplas

Predefinição:Exercícios


  1. (4, "ola", True)
  2. Todos os itens são válidos. Diferente de uma lista, uma n-upla não tem restrições quanto ao tipo de seus elementos.
  3. É uma restrição da própria linguagem. É possível que os desenvolvedores de Haskell quisessem limitar o uso de n-uplas para que tivessem comprimento pequeno, mantendo as listas como a principal estrutura para armazenar grande volume de dados.
  4. Uma nova n-upla, cujo tipo seria diferente do tipo original.


N-uplas dentro de n-uplas e outras combinações

Predefinição:Exercícios

  1. Apenas o terceiro e o último item são válidos.
    • A operação de cons não funciona com duplas.
    • O mesmo problema que o caso de antes.
    • É válido pois aqui adiciona-se uma dupla a uma lista vazia.
    • A lista é inválida porque seus elementos não são do mesmo tipo: (2,4) e (5,5) são duplas de números, enquanto que ('a','b') é uma dupla de Char.
    • É válido porque é uma dupla de listas. E a listas são válidas porque em cada uma delas, todos os elementos são do mesmo tipo.

Outros problemas

Predefinição:Exercícios

  1. Uma possível solução seria: snd (fst (("Ola", 4), True)).
  2. Sim, pois uma dupla permite que seus elementos sejam de qualquer tipo, ao contrário de uma lista que exige que todos os elementos sejam do mesmo tipo.
  3. cabecaCauda lista = (head lista, tail lista)
    
  4. quintoElemento lista = head (tail (tail (tail (tail lista))))
    
Para criar a função quintoElemento precisamos repetir tail quatro vezes. Toda essa repetição deixa o código mais propenso a erros e mais difícil de ler.


Tipos polimórficos

Predefinição:Exercícios

  1. cabecaCauda :: [a] -> (a, [a])
    
  2. quintoElemento :: [a] -> a
    
  3. h :: Int -> a -> b -> Char -- y e z não são usados, portanto, seus tipos não importam e nem precisam ser iguais
    

Predefinição:Exercício Numa expressão if, é dentro de then e else que são definidos os possíveis resultados finais. Em Haskell, toda expressão deve ter um tipo fixo, imutável. Como não sabemos qual caso será avaliado, se then ou se else, ambos devem possuir o mesmo tipo final para satisfazer o prerequisito de imutabilidade de tipos de Haskell.

Predefinição:Exercício

pts :: Int -> Int
pts x
  | x == 1    = 10
  | x == 2    = 6
  | x == 3    = 4
  | x == 4    = 3
  | x == 5    = 2
  | x == 6    = 1
  | otherwise = 0

Predefinição:Exercício

A condição implícita de pts(x) é que x deve ser maior que ou igual a 1 sempre: não há posição 0 ou posições negativas.

A diferença acontece quando o argumento de pts é menor que 1. Na versão com casamento de padrões, se o argumento for -1, por exemplo, o padrão casado será o último (_) e o resultado seria 0. Na versão mista, entraríamos no padrão com guardas, a primeira guarda seria verdadeira (pois (-1) <= 6 é verdadeiro), e o resultado final seria 7 - (-1), que é 8, um resultado errado.

Uma possível solução seria escrever:

pts :: Int -> Int
pts 1 = 10
pts 2 = 6
pts x
    | x < 1 || x > 7 = 0
    | otherwise      = 7 - x

Predefinição:Exercício

quarto :: (a,b,c,d,e,f,g,h,i,j) -> d
quarto (_,_,_,x,_,_,_,_,_,_) = x

Predefinição:Exercício

Como abrir uma janela implica numa ação com consequências no ambiente externo, a saída de abrirJanela deve ser IO:

abrirJanela :: JanelaTitulo -> JanelaTamanho -> IO Janela

Predefinição:Exercício

A função putStrLn recebe apenas argumentos do tipo String. Em ambos os casos, 2 e (2 > 3) não são String. Eles são um Num e um Bool, respectivamente. A função show converte ambos numa representação do tipo String que pode então ser usada como argumento de putStrLn.

Predefinição:Exercício

main :: IO ()
main =
  do putStrLn "A base?"
     base <- getLine
     putStrLn "A altura?"
     altura <- getLine
     putStrLn ("A área do triângulo é " ++ show (area base altura) ++ ".")
       where area b a = (read b :: Float) * (read a :: Float) / 2

Predefinição:Exercício

main :: IO ()
main =
  do putStrLn "Olá! Qual o seu nome?"
     nome <- getLine
     if nome == "Carlos" || nome == "Pedro"
        then putStrLn "Haskell é uma ótima linguagem de programação, não é mesmo?"
        else if nome == "Maria"
                then putStrLn "Acredito que Haskell pode ser aprendido por qualquer pessoa!"
                else putStrLn "Desculpe-me, não te conheço."

Uma possível solução usando casamento de padrões:

main :: IO ()
main =
  do putStrLn "Olá! Qual o seu nome?"
     nome <- getLine
     putStrLn (resposta nome)
       where otimaLinguagem    = "Haskell é uma ótima linguagem de programação, não é mesmo?"
             resposta "Carlos" = otimaLinguagem
             resposta "Pedro"  = otimaLinguagem
             resposta "Maria"  = "Acredito que Haskell pode ser aprendido por qualquer pessoa!"
             resposta _        = "Desculpe-me, não te conheço."

Introdutório

Recursividade numérica

Predefinição:Exercício

  1. O compilador começaria a avaliar
    (-1) * fatorial (-2)
    (-1) * (-2) * fatorial (-3)
    (-1) * (-2) * (-3) * fatorial (-4)
    e assim por diante. Como a definição de fatorial não possui nenhum caso que impeça essa expansão, o compilador começaria o processo mas nunca consegueria terminá-lo corretamente.
  2. O compilador avaliaria corretamente até 6 * 5 * 4 * 3 * 2 * 1 * fatorial 0. Neste ponto, ele deveria encerrar a recursividade, mas como um argumento 0 satisfaz a condição do primeiro caso, o caso base nunca seria avaliado e a expansão do fatorial continuaria pelos números negativos. O resultado seria o mesmo que o do exercício anterior.
  3. fatorialDuplo 1 = 1
    fatorialDuplo 2 = 2
    fatorialDuplo n = n * fatorialDuplo (n - 2)
    

Predefinição:Exercício

  1. potencia _ 0 = 1
    potencia x y = x * potencia x (y - 1)
    
  2. adicao x 0 = x
    adicao x y = adicao (maisUm x) (y - 1)
    
  3. log2 1 = 0
    log2 x = 1 + log2 (div x 2)
    

Recrusividade usando listas

Predefinição:Exercício

  1. replicar :: Int -> a -> [a]
    replicar 0 _ = []
    replicar n x = x : replicar (n - 1) x
    
  2. (!!!) :: [a] -> Int -> a
    (x:xs) !!! 0 = x
    (x:xs) !!! n = xs !!! (n - 1)
    
  3. ziper :: [a] -> [b] -> [(a,b)]
    ziper []     _      = []
    ziper _      []     = []
    ziper (x:xs) (y:ys) = (x,y) : ziper xs ys
    
  4. minhaLength :: [t] -> Int
    minhaLength l = go l 0
      where go []     n = n
            go (x:xs) n = go xs (n+1)
    

Listas infinitas

Predefinição:Exercício

repetir :: a -> [a]
repetir x = x : repetir x


Generalizando

Predefinição:Exercício

  1. tomarInt :: Int -> [a] -> [a]
    tomarInt _ []     = []
    tomarInt 0 _      = []
    tomarInt n (x:xs) = x : tomarInt (n - 1) xs
    
  2. eliminarInt :: Int -> [a] -> [a]
    eliminarInt _ []     = []
    eliminarInt 0 xs     = xs
    eliminarInt n (x:xs) = eliminar (n - 1) xs
    
  3. somarInt :: [Int] -> Int
    somarInt []     = 0
    somarInt (x:xs) = x + somarInt xs
    
  4. scanSoma :: [Int] -> [Int]
    scanSoma []       = []
    scanSoma (x:[])   = x : []
    scanSoma (x:y:ys) = x : scanSoma ((x + y) : ys)
    
    -- Usando função auxiliar
    scanSoma' ls = aux 0 ls
      where aux :: Int -> [Int] -> [Int]
            aux tot []     = []
            aux tot (x:xs) = tot' : aux tot' xs
              where tot' = x + tot
    
  5. difs :: [Int] -> [Int]
    difs []       = []
    difs (x:[])   = []
    difs (x:y:ys) = (y - x) : difs (y:ys)
    
    -- Usando função auxiliar
    difs' :: [Int] -> [Int]
    difs' (x:xs) = aux (x:xs) xs
      where aux _      []     = []
            aux (a:as) (b:bs) = (b - a) : aux as bs
    

Folds

Predefinição:Exercícios

  1. -- Versão recursiva
    eListas :: [Bool] -> Bool
    eListas []     = True
    eListas (x:xs) = x && eListas xs
    
    -- Usando foldr
    eListas' :: [Bool] -> Bool
    eListas' ls = foldr (&&) True ls
    
    -- Versão recursiva
    ouListas [Bool] -> Bool
    ouListas []     = False
    ouListas (x:xs) = x || ouListas xs
    
    -- Usando foldr
    ouListas' :: [Bool] -> Bool
    ouListas' ls = foldr (||) False ls
    
  2. maximo :: Ord a => [a] -> a
    maximo = foldl1 max
    
    minimo :: Ord a => [a] -> a
    minimo = foldl1 min
    
  3. inverter :: [a] -> [a]
    inverter ls = foldl consInvertido [] ls
      where consInvertido xs x = x : xs
    

Scans

Predefinição:Exercícios

  1. -- Versão recursiva
    scanR :: (a -> b -> b) -> b -> [a] -> [b]
    scanR f acum []     = [acum]
    scanR f acum (x:xs) = f x (head ant) : ant
      where ant = scanR f acum xs
    
    -- Usando foldr
    scanR' :: (a -> b -> b) -> b -> [a] -> [b]
    scanR' f acum []     = [acum]
    scanR' f acum (x:xs) = foldr f acum (x:xs) : scanR' f acum xs
    
    ---------------------------------
    --Versão recursiva
    scanL :: (b -> a -> b) -> b -> [a] -> [b]
    scanL f acum []     = [acum]
    scanL f acum (x:xs) = acum : scanL f (f acum x) xs
    
    -- Usando foldl
    scanL' :: (b -> a -> b) -> b -> [a] -> [b]
    scanL' f acum [] = [acum]
    scanL' f acum ls = (scanL' f acum (init ls)) ++ [foldl f acum ls]
    
    Vale notar que a scanL' (usando foldl) é menos eficiente que scanL (versão recursiva).
  2. fatLista :: Int -> [Int]
    fatLista n = scanl (*) 1 [2..n]
    

Compreensão de listas

Predefinição:Exercícios

  1. retornaDivisiveis :: Int -> [Int] -> [Int]
    retornaDivisiveis n xs = [x | x <- xs, mod x n == 0]
    
  2. selecionaCaudas :: [[Int]] -> [[Int]]
    selecionaCaudas ls = [xs | (x:xs) <- ls, x > 5]
    
  3. Sim, a ordem importa. Todas as condições devem ser satisfeitas na ordem em que aparecem na compreensão: se tivermos cinco condições e segunda for falsa, a terceira, a quarta e a quinta não serão avaliadas.
  4. mapear :: (a -> b) -> [a] -> [b]
    mapear f xs = [f x | x <- xs]
    
    filtrar :: (a -> Bool) -> [a] -> [a]
    filtrar f xs = [x | x <- xs, f x]
    
  5. fstComSndPar :: [(Int, Int)] -> [Int]
    fstComSndPar ls = (map fst . filter faux) ls
      where faux (x,y) = ePar y
    

Predefinição:Exercícios

data Data = Data Int Int Int                    -- ano, mês, dia
data Aniversario = Nascimento String Data       -- nome, data
                 | Casamento String String Data -- nome 1, nome 2, Data

mostrarData :: Data -> String
mostrarData (Data a d m) = show a ++ "-" ++ show m ++ "-" ++ show d

mostrarAniversario :: Aniversario -> String
mostrarAniversario (Nascimento n d) =
    n ++ " nasceu em " ++ mostrarData d
mostrarAniversario (Casamento n1 n2 d) =
    n1 ++ " casou-se com " ++ n2 ++ " em " ++ mostrarData d

joaoRomao :: Aniversario
joaoRomao = Nascimento "João Romão" (Data 1968 7 3)

romaoCasamento :: Aniversario
romaoCasamento = Casamento "João Romão" "Maria Romão" (Data 1987 3 4)


Predefinição:Exercícios

type Nome = String
data Data = Data Int Int Int                -- ano, mês, dia
data Aniversario = Nascimento Nome Data     -- nome, data
                 | Casamento Nome Nome Data -- nome 1, nome 2, Data


Predefinição:Exercícios

  1. data Data = Data {ano :: Int, mes :: Int, dia :: Int}
    
    mostrarData :: Data -> String
    mostrarData d = show (ano d) ++ "-" ++ show (mes d) ++ "-" ++ show (dia d)
    
  2. Cada campo torna-se uma função de acesso. Sendo estas funções realacionadas as tipos distintos, suas assinaturas de tipos serão diferentes para cada caso. Entretanto, Haskell não permite que uma função tenha mais que uma assinatura de tipo. Em outras palavras, é impossível ter campo1 :: Dado1 -> Int e campo1 :: Dado2 -> Int no mesmo lugar, por exemplo.

Predefinição:Exercícios

  • map (\x -> x * 2 + 3) xs
    
  • foldr (\x y -> read x + y) 1 xs
    

Predefinição:Exercício

    1. (\x  -> 4 + x)           :: (Num a) => a -> a
      
    2. (\xs -> elem 1 xs)       :: [a] -> Bool
      
    3. (\c  -> notElem c "abc") :: Char -> Bool
      
  1. A função norma3D possui três argumentos, portanto seu tipo é da forma a -> a -> a -> a. Como temos o efeito de currying, podemos considerar que norma3D tem dois argumentos se pensarmos seu tipo como sendo a -> a -> (a -> a), isto é, uma função de dois argumentos que retorna uma função de um argumento. Assim sendo, se usarmos a notação de ponto livre, isto é, omitindo o terceiro argumento, forçamos o currying e podemos usar a notação infixa. Portanto, nomra3D' a b é, repetindo, uma função de dois argumentos que retorna uma função de um argumento, sendo que podemos usá-la com três argumentos, pois (f m) n == f m n. Também é fácil ver que os resultados serão sempre iguais se expandirmos a aplicação de norma3D':
(norma3D' a b) c
(a `norma3D` b) c
(norma3D a b) c
norma3D a b c

Predefinição:Exercícios

Qualquer valor aplicado a h sempre retorna True. A definição de h é:

h :: Int -> Bool
h k = True
h _ = False

No primeiro caso, o padrão definido como k casa com qualquer valor de entrada. Portanto, o primeiro caso será sempre verdadeiro, o que faz com que h sempre retorne True. Assim sendo, não há nenhum valor que faça h retornar False.