Programação Funcional Lucília Camarão de Figueiredo Universidade Federal de Ouro Preto [email protected] Aula 06: Funções Recursivas 1 I NTRODUÇÃO Funções odem ser definidas em termos de outras funções: fact :: Int → Int fact n = product [1..n] mapea um inteiro n no produto dos inteiros compreendidos entre 1 e n. Expressões são avaliadas passo a passo por meio da aplicação de funções a seus argumentos. Por exemplo: fact 3 B product [1..3] B product [1,2,3] B 1*2*3 B 6 I NTRODUÇÃO 2 D EFINIÇÕES RECURSIVAS Funções podem ser também definidas em termos delas próprias: fact :: Int → Int fact 0 = 1 fact n = n * fact (n-1) mapea 0 em 1 e qualquer outro valor inteiro positivo n no produto desse valor pelo fatorial do antecessor de n. Por exemplo: fact 3 B 3 * fact B 3 * (2 * B 3 * (2 * B 3 * (2 * B 6 D EFINIÇÕES RECURSIVAS 2 fact 1) (1 * fact 0)) (1 * 1)) 3 P ORQUE RECURSÃO É ÚTIL ? • Na prática, muitas funções podem ser naturalmente definidas em termos de si próprias. • Propriedades de funções recursivas podem ser provadas por meio de um princípio matemático simples e poderoso: o princípio de indução. Exemplo: rangeSum :: Int → Int → Int rangeSum m n | n < m = 0 | n == m = m | n > m = n + rangeSum m (n-1) Prove que, para quaisquer inteiros m e n tais que n ≥ m, rangeSum m n = (n(n + 1) − m(m − 1))/2 P ORQUE RECURSÃO É ÚTIL ? 4 R ECURSÃO SOBRE LISTAS product :: Num a => [a] → a product [] = 1 product (x:xs) = x * product xs Exemplo: product [1..3] B product [1,2,3] B 1 * product [2,3] B 1 * (2 * product [3]) B 1 * (2 * (3 * product [])) B 1 * (2 * (3 * 1)) B 6 R ECURSÃO SOBRE LISTAS 5 E XERCÍCIOS Defina uma função para solução de cada problema: 1. Determinar a soma dos valores de uma lista de números. 2. Dobrar todos os valores de uma lista de números inteiros. 3. Inverter uma lista de valores. 4. Determinar se um dado valor ocorre em uma lista de valores. 5. Determinar o número de ocorrências de um valor em uma lista. 6. Dadas duas listas, retornar a lista cujos elementos são pares de valores em posições correspondentes nas listas dadas. 7. Dada uma lista de pares de valores, retornar um par de listas, onde os elementos da primeira e segunda listas são, respec., o primeiro e segundo componentes de cada par da lista dada. E XERCÍCIOS 6 M AIS EXERCÍCIOS (++) :: [a] → [a] → [a] [] ++ ys = ys (x:xs) ++ ys = x:(xs++ys) count :: a → [a] → Int count y [] = 0 count y (x:xs) | y==x = 1 + count y xs | y!=x = count y xs reverse :: [a] → [a] reverse [] = [] reverse (x:xs) = reverse xs ++ [x] 1. Prove que: count x (ys++zs) = count x ys + count x zs 2. Prove que: reverse (xs++ys) = reverse ys ++ reverse xs M AIS EXERCÍCIOS 7 M AIS EXERCÍCIOS shunt :: [a] → [a] → [a] shunt [] ys = ys shunt (x:xs) ys = shunt xs (x:ys) reverse :: [a] → [a] reverse xs = shunt xs [] 1. Prove que, para toda lista finita xs: reverse (reverse xs) = xs M AIS EXERCÍCIOS 8 O RDENAÇÃO - Q UICKSORT O procedimento de ordenação conhecido como quicksort pode ser especifcado do seguinte modo: • A lista vazia já está ordenada. • Listas não vazias podem ser ordenadas do seguinte modo: ➜ ordenar todos os elementos da cauda da lista que são menores que primeiro elemento da lista; ➜ ordenar todos os elementos da cauda da lista que são maiores ou iguais ao primeiro elemento da lista; ➜ concatenar as listas resultantes, respectivamente, à esquerda e à direita do primeiro elemento da lista; O RDENAÇÃO - Q UICKSORT 9 O RDENAÇÃO - Q UICKSORT qsort :: Ord a => [a] → [a] qsort [] = [] qsort (x:xs) = qsort [ a | a ← xs, a<x ] ++ [x] ++ qsort [ b | b ← xs, b>=x ] Essa é possivelmente a implementação mais simples de quicksort em qualquer linguagem de programação! O RDENAÇÃO - Q UICKSORT 10 O RDENAÇÃO - Q UICKSORT Por exemplo (abreviando qsort como q): q [3,2,4,1,5] ⇓ q [2,1] ++ [3] ++ q [4,5] ⇓ ⇓ q [1] ++ [2] ++ q [] q [] ++ [4] ++ q [5] ⇓ ⇓ ⇓ ⇓ [1] [] [] [5] O RDENAÇÃO - Q UICKSORT 11 E XERCÍCIO 1. Defina uma função recursiva insert :: Ord a => a → [a] → [a] que insere um elemento em uma lista ordenada, de maneira que a lista resultante permaneça ordenada. Por exemplo: > insert 3 [1,5,7.8] [1,3,5,7,8] 2. Defina uma função recursiva: isort :: Ord a => [a] → [a] que implementa o método de ordenação por inserção. E XERCÍCIO 12 E XERCÍCIO 3. Defina uma função recursiva merge :: Ord a => [a] → [a] → [a] que intercala duas listas ordenadas resultando em uma lista ordenada. Por exemplo: > merge [2,5,6] [1,3,7,8] [1,2,3,5,6,7,8] 4. Defina uma função recursiva: msort :: Ord a => [a] → [a] que implementa o método de ordenação por intercalação. E XERCÍCIO 13 E XERCÍCIO 5. Teste suas três implementações paa ordenação usando o Hugs e observe como elas se comparam quanto a eficiência. Por exemplo: > :set +s > isort (reverse [1..5000]) > msort (reverse [1..5000]) O comando :set +s indica ao Hugs para fornecer algumas estatísticas úteis após cada avaliação. E XERCÍCIO 14 M AIS UM EXERCÍCIO O objetivo desse exercício é provar que qsort é correto, isto é: qsort xs é uma ordenação de xs, para qualquer lista finita xs. Como expressar formalmente essa propriedade? ➜ Devemos garantir que os elementos de qsort xs são exatamente os elementos de xs ou, em outras palavras, que qsort xs é uma permutação de xs. Isso pode ser expresso por meio do predicado: perm xs ys ⇔ ∀x. count x xs = count x ys ➜ Além disso, devemos garantir que qsort xs seja uma lista ordenada. Isso será expresso por meio do predicado: sorted :: Ord a => [a] → Bool ➜ Podemos então expressar a asserção a ser provada como: ∀n ∈ N. ∀xs::[a]. length xs=n ⇒ perm xs (qsort xs) ∧ sorted (qsort xs) M AIS UM EXERCÍCIO 15 M AIS UM EXERCÍCIO ( CONTINUAÇÃO ) O predicado sorted pode ser definido como: sorted sorted sorted sorted :: Ord a => [a] → Bool [] = True [x] = True (x:y:xs) = x <= y && sorted (y:xs) sorted pode também ser definido usando list comprehension: sorted [] = True sorted zs@(_:xs) = and [ x<=y | (x,y) ← zip zs xs ] As duas definições são igualmente eficientes? M AIS UM EXERCÍCIO ( CONTINUAÇÃO ) 16 M AIS UM EXERCÍCIO ( CONTINUAÇÃO ) Prova: por indução sobre o comprimento de xs. Caso base: length xs = 0, isto é, xs=[] perm (qsort []) [] ∧ sorted (qsort []) Caso indutivo: length xs > 0 def Sejam l1 = [ y | y ← ys, y<x ] def l2 = [ y | y ← ys, y>=x ] Devemos provar que: perm l1 (qsort l1) ∧ sorted l1 ∧ perm l2 (qsort l2) ∧ sorted l2 ⇒ perm xs (qsort l1 ++ [x] ++ qsort l2) ∧ sorted (qsort l1 ++ [x] ++ qsort l2) M AIS UM EXERCÍCIO ( CONTINUAÇÃO ) 17