Questão 11: group
Voltar
Apresente uma definição recursiva da função (pré-definida) group :: Eq a => [a] -> [[a]] que agrupa elementos iguais e consecutivos de uma lista.
Exemplo
> group [1,2,2,3,4,4,4,5,4]
[[1],[2,2],[3],[4,4,4],[5],[4]] Resolução
group :: Eq a => [a] -> [[a]]
group [] = []
group [x] = [[x]]
group (h:t)
| elem h (head r) = (h : (head r)) : tail r
| otherwise = [h] : r
where r = group t Explicação
Esta questão é uma das mais complicadas das 50.
Até agora, nas nossas definições recursivas, apenas temos chamado a própria função com argumentos diferentes. Contudo, nós podemos fazer mais do que isso.
Vou passar a exemplificar. Se chamarmos esta função com a lista [1,1,2,2,3,3] (o resultado esperado será [[1,1],[2,2],[3,3]]), sabemos que teremos de colocar o primeiro 1 numa sub-lista com o segundo 1. Porém, da forma como temos definido as nossas funções, tal é impossível, pois percorremos as listas elemento a elemento, não aos pares/trios/etc..
Passamos então a usar o where:
group (h:t) = ???
where r = group tAssim, podemos manipular o resultado de chamar a função para a cauda da lista. Sabemos que, para a lista mencionada acima, teremos:
group (1:[1,2,2,3,3]) = ???
where r = group [1,2,2,3,3] -- = [[1],[2,2],[3,3]]Através deste método, definir a função torna-se trivial. Se a cabeça da lista for igual aos elementos da primeira sub-lista do resultado, juntamo-la a essa sub-lista. Caso contrário, criamos uma nova sub-lista apenas com a cabeça da lista.
Convertendo esta lógica para Haskell, ficamos com:
group (h:t)
| elem h (head r) = (h : (head r)) : tail r
| otherwise = [h] : r
where r = group tVoltando ao exemplo acima, para group (1:[1,2,2,3,3]), como 1 existe na primeira sub-lista do resultado ([[1],[2,2],[3,3]]), a função irá retornar 1:[1] : [[2,2],[3,3]] = [[1,1],[2,2],[3,3]]. Por outro lado, se tivermos group [1,2,3] = group (1:[2,3]), r será igual a [[2],[3]]. Aqui, como 1 não existe na primeira sub-lista do resultado (que é [2]), ficamos com [1] : [[2],[3]] = [[1],[2],[3]].
Como estamos a assumir que a lista tem pelo menos dois elementos (h e head r), o nosso caso de paragem terá de incidir no caso em que a lista só tem um elemento. Assim, ficamos com:
group :: Eq a => [a] -> [[a]]
group [] = []
group [x] = [[x]]
group (h:t)
| elem h (head r) = (h : (head r)) : tail r
| otherwise = [h] : r
where r = group tA mesma lógica é utlizada na versão desta resolução com uma função auxiliar. Essa versão poderá ser mais simples de entender para algumas pessoas.
Resolução
group :: Eq a => [a] -> [[a]]
group [] = []
group (h:t) = insere h (group t)
insere :: Eq a => a -> [[a]] -> [[a]]
insere x [] = [[x]]
insere x (h:t)
| elem x h = (x : h) : t
| otherwise = [x] : (h : t) Explicação
Esta definição é fundamentalmente igual à definição “simples”, mas não usa o where. Aqui, manipulamos o resultado da chamada recursiva na função auxiliar, em vez de o fazer na própria função. Esta versão poderá ser mais simples de entender para algumas pessoas.
Resolução
group :: Eq a => [a] -> [[a]]
group [] = []
group (h:t) = (h:takeWhile (== h) t) : group (dropWhile (== h) t) Resolução
group :: Eq a => [a] -> [[a]]
group [] = []
group (h:t) = (h:a) : group b
where (a,b) = span (== h) t Explicação
Esta resolução é igual à resolução com funções de ordem superior, mas utiliza a função span, que junta as funções takeWhile e dropWhile numa só.