Este documento se refere ao exercício 2 da LE1, que pode ser encontrado neste documento.
Essas são as possíveis funções que outros módulos e aplicações podem acessar:
module LE1.ConjuntoInt.TAD
( criaConjunto
, insereItem
, removeItem
, pertence
, minEl
, uniao
, igual
, isVazio
, contem
, fromList
, show
) where
Primeiro, defino o TAD
e seus construtores de tipos:
data Conjunto a = Conjunto [a] deriving (Eq, Read, Ord)
Um Conjunto
nada mais é que uma lista do tipo a
.
Este TAD
permite ser comparado por igualdade ou desigualdade por causa da classe de tipo Eq
, ex:
{1,2,3} ≡ {1,2,3}
ou {1,2,3} ≠ {4,5,6}
;
Ele também pode ser lido/analisado a partir de uma string, por meio da classe de tipo Read
. ex:
read "[1,2,3]" :: Conjunto Int
;
E por último, também pode ser comparado por ordem de grandeza:
{1,2,3} ≥ {3,2}
ou {3,2} ≤ {1,2,3}
Após a definição do TAD
, crio algumas instâncias nas classes de tipo Show
, Semigroup
e Monoid
:
instance Semigroup (Conjunto a) where
(<>) (Conjunto xs) (Conjunto ys) = Conjunto (xs ++ ys)
instance Monoid (Conjunto a) where
mempty = Conjunto []
instance Show a => Show (Conjunto a) where
show (Conjunto []) = "∅"
show (Conjunto xs) = "{" ++ intercalate ", " (map (show) xs) ++ "}"
Em Álgebra Abstrata, existem alguns linhagens:
Magma
, um conjunto que possui um operador binário que precisa ser fechado:
∀ a, b ∈ M : a • b ∈ M
Exemplo: Bool
e o operador &&
Já um Semigroup
é um Magma
que obedece a lei da associatividade:
∀ a, b, c ∈ S : a · (b · c) = (a · b) · c
Exemplo: String
e o operador (<>)
Com uma instância da classe de tipo Semigroup
, posso usar o operador (<>)
com Conjuntos.
Um Monoid
é um Semigroup
com a restrição de que deve existir um elemento neutro no conjunto
que quando combinado com qualquer elemento do conjunto, resulte no mesmo elemento:
e ∈ M : ∀ a ∈ M, a · e = e · a = a
Exemplo: Integer
, operador (+)
e o elemento neutro 0
No caso do Conjunto
, o elemento neutro seria um conjunto vazio.
Também crio uma instância da classe de tipo Show
, me permitindo customizar a impressão
de um Conjunto
na tela.
Essas foram as funções exigidas pelo exercício:
Apenas retorno um Conjunto
vazio, seguindo a instância da classe de tipo Monoid
criaConjunto :: Conjunto Integer
criaConjunto = mempty
Insiro um elemento no Conjunto
. Por questões de performance, escolho apenas fazer a operação
de “prepend” na lista, ou seja, apontar o cabeçalho dela para o novo elemento. Um elemento
só poderá ser inserido no Conjunto
caso ele não exista no mesmo, caso contrário, devolvo
o mesmo Conjunto
usado como argumento
insereItem :: Integer -> Conjunto Integer -> Conjunto Integer
insereItem x a@(Conjunto ys)
| not $ pertence x a = Conjunto (x:ys)
| otherwise = a
Para remover um elemento de um Conjunto
, primeiro verifico se é um Conjunto
vazio.
Caso seja, apenas retorno ele mesmo. A partir disso, filtro todos os elementos de um
Conjunto
que são diferentes do elemento usado como argumento
removeItem :: Integer -> Conjunto Integer -> Conjunto Integer
removeItem _ a@(Conjunto []) = a
removeItem x (Conjunto ys) = Conjunto (filter (/= x) ys)
Para verificar se um elemento existe num Conjunto
, itero sobre ele, afirmando que pelo menos um
dos elementos precisa ser igual ao argumento
pertence :: Integer -> Conjunto Integer -> Bool
pertence x (Conjunto xs) = any (== x) xs
Como achar o menor elemento de um Conjunto
? Uma das formas é iterar sobre ele, criar um “acumulador”
que inicia com o valor do primeiro elemento do Conjunto
e a cada “loop”, verifico se o elemento atual
é menor do que o acumulador. Se isso for verdade, esse elemento será o novo acumulador! Caso contrário
o acumulador continua o mesmo.
minEl :: Conjunto Integer -> Integer
minEl (Conjunto xs) = foldl1 (\acc x -> if x < acc then x else acc) xs
A união de A e B é definida pela junção de todos os elementos de A
e B
, ignorando os repetidos.
Para isso, uso a função nub
que remove os elementos duplicados de uma lista e também
utilizo a função de remover
como parâmetro da função foldl
, que percorre um Traversal
que neste caso é uma lista, da esquerda para a direita reduzindo a um acumulador que nessa
implementação, é uma outra lista. No final, concateno o Conjunto A
com os elementos restantes,
não repetidos de B
uniao :: Conjunto Integer -> Conjunto Integer -> Conjunto Integer
uniao xs (Conjunto []) = xs
uniao (Conjunto []) ys = ys
uniao a@(Conjunto xs) (Conjunto ys) =
(a <> (case xs of
[] -> nubbed
(x:xs') -> foldl (flip removeItem) (removeItem x nubbed) xs'))
where nubbed = Conjunto (nub ys)
Por definição um Conjunto
só será igual a outro se ambos se conterem. Ou seja:
A ⊃ B
&& B ⊃ A
igual :: Conjunto Integer -> Conjunto Integer -> Bool
igual a b = contem a b && contem b a
Por correspondência de valor, verifico se é um Conjunto
vazio ou não
isVazio :: Conjunto Integer -> Bool
isVazio (Conjunto []) = True
isVazio _ = False
Essa função é necessária para o TAD Conjunto
ter compatibilidade com a estrutura de dados “lista”
fromList :: [Integer] -> Conjunto Integer
fromList xs = Conjunto xs
Função que utiliza o foldl
com o acumulador sendo uma lista vazia e com isso, removo
elementos duplicados de uma lista
nub :: Eq a => [a] -> [a]
nub = foldl (\seen x -> if elem x seen
then seen
else seen ++ [x]) []
Recursivamente checo se cada elemento do Conjunto A
pertence a um Conjunto B
.
Caso seja verdade em todos os casos, significa que A
contém B
contem :: Conjunto Integer -> Conjunto Integer -> Bool
contem (Conjunto []) _ = True
contem (Conjunto (x:xs)) b@(Conjunto _) = pertence x b && contem (Conjunto xs) b