Questão 1: enumFromTo
Voltar
Apresente uma definição recursiva da função (pré-definida) enumFromTo :: Int -> Int -> [Int] que constrói a lista dos números inteiros compreendidos entre dois limites.
Exemplo
> enumFromTo 1 5
[1,2,3,4,5] Resolução
enumFromTo :: Int -> Int -> [Int]
enumFromTo start end
| start > end = []
| otherwise = start : enumFromTo (start + 1) end Explicação
Existem várias formas de definir funções recursivas. A que vou usar nesta explicação consiste em encontrar o “padrão” da recursividade, isto é, o que é que muda entre diferentes chamadas recursivas da função.
Qual deverá ser o resultado de enumFromTo 5 5? Exato, apenas [5].
E de enumFromTo 4 5? [4,5].
Ou seja, quando chamamos a função enumFromTo start end, a lista resultante começa sempre com start.
Temos assim uma definição preliminar da função: enumFromTo start end = start : ??? (: é um operador que coloca um elemento no início de uma lista).
Vamos aplicar esta nossa solução a um exemplo concreto: enumFromTo 1 5 irá resultar em 1 : ???.
Pode parecer que chegámos a um entrave, mas nós sabemos o valor de ???. Neste caso, ??? = [2,3,4,5], isto é, o resto da lista.
Agora vamos pensar ao contrário… o que é que produz o resultado [2,3,4,5]? Exatamente, enumFromTo 2 5! Por outras palavras, sabemos que enumFromTo 1 5 = 1 : enumFromTo 2 5 = 1 : enumFromTo (1 + 1) 5.
Já podemos então definir a nossa função como: enumFromTo start end = start : enumFromTo (start + 1) end.
Visto que esta função é recursiva, precisamos ainda de um caso de paragem. Para lá chegar, podemos pensar da seguinte forma: quando é que paramos de adicionar 1 a start? Aqui a resposta é “quando for igual a end”, pois se os dois valores forem iguais iremos ter uma lista de apenas um elemento.
Chegamos assim à definição (quase) final da função:
enumFromTo start end
| start == end = [start]
| otherwise = start : enumFromTo (start + 1) endOs caracteres | chamam-se “guardas”, podes aprender o que são e como as usar aqui.
Apesar desta definição funcionar para a maioria dos casos, não funciona para todos. E se start for maior que end? Se experimentássemos correr enumFromTo 5 1 com a nossa solução atual, o nosso computador nunca mais iria sair do sítio. Porquê? Porque iria estar constantemente a adicionar uma unidade a start, mas como este já é superior a end, nunca iriam ser iguais. Desta forma, uma solução mais completa para esta questão seria:
enumFromTo start end
| start > end = []
| start == end = [start]
| otherwise = start : enumFromTo (start + 1) endPor outras palavras, se o limite inferior for maior do que o limite superior, o resultado é uma lista vazia (um comportamento alternativo seria ordenar de forma decrescente neste cenário, mas nesta questão apenas pretendemos ordenar de forma crescente).
Por intuição, vemos que a segunda condição agora é redundante, por isso podemos removê-la de forma segura.
enumFromTo start end
| start > end = []
| otherwise = start : enumFromTo (start + 1) end