Este documento se refere ao exercício 3 da LE1, que pode ser encontrado neste documento.
Essas são as funções expostas por esse módulo
module LE1.Data.TAD
( imprimeData
, converteData
, somaDias
, toTuple
, isVazia
, vazia
, isInvalida
, fromTuple
, mes
, ano
, dia
, show
) where
Note que não haverá implementação explicíta das funções dia
, mes
e ano
, pois as mesmas são definidas
automaticamente pelo Haskell
quando crio uma nova estrutura com a sintaxe de Record
.
Esta é a representação de um TAD Data
e alguns apelidos para o tipo Int
type Dia = Int
type Mes = Int
type Ano = Int
data Data = Invalida | Vazia | Data { dia :: Dia
, mes :: Mes
, ano :: Ano
} deriving (Eq, Ord)
Defino que meu TAD Data
possui três construtores de tipo: Invalida
, Vazia
e a própria Data
Essa é a instância da classe de tipo Show
, customizada para imprimir um Data
formatada
instance Show Data where
show (Data d m a) = intercalate "/" (map (show) [d, m, a])
show Invalida = "Data Inválida"
show Vazia = "Data Vazia"
O exercício exige apenas três funções:
Esta função precisa receber uma 3-Tupla
ou Tripla
de Int
, representando respectivamente
o dia, mês e ano de uma Data
, tenta criar um TAD Data
para validar a estrutura e devolve
uma String
envolvida na Mônada IO
que será utilizada pelo programa em CLI para imprimir na tela.
Essa Mônada é responsável por encapsular outros valores e representar que uma operação de Input/Output
pode ser executada de forma segura, sem atrapalhar a lógica e andamento do restante do algoritmo.
imprimeData :: (Int, Int, Int) -> IO String
imprimeData (d, m ,a) = (case data' of
Invalida -> return "Invalida"
Vazia -> return "Estrutura vazia"
Data {} -> return dataValida)
where data' = criaData (d, m, a)
dataValida = (printf "%d/%d/%d" d m a) :: String
Dado uma String
no formato dd/mm/AAAA
, tento criar uma Data
validada e retorno essa estrutura.
Uso fortemente a estrutura de controle case ... of
que, por correspondência de valores, retorna
determinado valor caso o argumento dê match
com uma das opções
converteData :: String -> Data -> Data
converteData [] d = d
converteData _ (Data _ _ _) = Invalida
converteData _ Invalida = Invalida
converteData d Vazia = (case criaData (d', m, a) of
Invalida -> Invalida
Vazia -> Vazia
Data {} -> Data {dia = d', mes = m, ano = a})
where d':m:a:_ = map (\x -> read x :: Int) (separa (=='/') d)
Defino a função de somar dias de uma Data
recursivamente.
O caso base é quando os dias a serem somados são 0.
Se o dia e mês da data forem 1 e os dias a serem
somados forem maior que o tamanho do ano da Data
,
aumento o ano e chamo novamente a função com os dias
a serem somados menos o tamanho do ano da Data
Para qualquer outra Data
, faço a seguinte verificação:
Sendo x os dias a serem somados, temos que
- se x é negativo? ->
Data Invalida
- se x >= os dias restantes do ano -> ano + 1 e x = x - tamanho do ano
- se x >= os dias restantes no mes ->
se o mes == 12, aumento o ano, senão aumento o mes e x = x - dias restantes do mes - caso genérico -> retorno uma
Data
com dias somados a x
somaDias :: Data -> Int -> Data
somaDias data' 0 = data'
somaDias (Data 1 1 y) d'
| d' < 0 = Invalida
| d' >= tamAno y = somaDias (Data 1 1 (y + 1)) (d' - tamAno y)
somaDias data'@(Data d m y) d'
| d' < 0 = Invalida
| d' >= faltaEmAno data' = somaDias (Data 1 1 (y + 1)) (d' - faltaEmAno data')
| d' >= faltaEmMes data' = somaDias (if m == 12 then Data 1 1 (y + 1) else Data 1 (m + 1) y) (d' - faltaEmMes data')
| otherwise = Data (d + d') m y
somaDias Invalida _ = Invalida
somaDias Vazia d = somaDias (Data 1 1 2021) d
Funções básicas para ter compatibilidade entre a estrutura de dados Tupla
e fazer algumas verificações
vazia :: Data
vazia = Vazia
isInvalida :: Data -> Bool
isInvalida Invalida = True
isInvalida _ = False
isVazia :: Data -> Bool
isVazia Vazia = True
isVazia _ = False
toTuple :: Data -> Maybe (Int, Int, Int)
toTuple (Data d m a) = Just (d, m, a)
toTuple _ = Nothing
fromTuple :: (Int, Int, Int) -> Data
fromTuple d = criaData d
Apenas crio um TAD Data
válido, caso os argumentos passem nas validações definidas por mim. Caso contrário
retorno uma Data
inválida
criaData :: (Int, Int, Int) -> Data
criaData (d, m, a)
| d < 1 || d > 31 = Invalida
| m < 1 || m > 12 = Invalida
| a < 1920 || a > 2021 = Invalida
| m == 2 && d > 29 = Invalida
| otherwise = Data {dia = d, mes = m, ano = a}
Funções para fazer pequenas contas com dias.
Verifico se um ano é bissexto, que me permite calcular quantos dias um ano vai ter;
Dado um mês e um ano, devolvo a quantidade de dias num mês, que é representado pelo valor correspondente ao índice do número do mês na lista definida;
Calculo quantos dias faltam num mês, diminuindo os dias fornecidos como argumento da quantidade de dias no mês mais um;
Para achar há quantos dias um ano começou, basta somar a quantidade de dias dos meses passados com a quantidade de dias do mês atual menos um;
Finalmente, diminuo do tamanho total (em dias) de um ano, os dias que já passaram, obtendo quantos dias ainda faltam para aquele ano acabar
anoBissexto :: Int -> Bool
anoBissexto n = (mod) n 4 == 0 && ((mod) n 100 /= 0 || (mod) n 400 == 0)
tamAno :: Int -> Int
tamAno n = if anoBissexto n then 366 else 365
-- De forma "imperativa", pego quantos dias tem um mês
tamMes :: Int -> Int -> Int
tamMes a' m' = meses !! (m' - 1) where
meses = if anoBissexto a' then meses'' else meses'
meses' = [31,28,31,30,31,30,31,31,30,31,30,31]
meses'' = [31,29,31,30,31,30,31,31,30,31,30,31]
faltaEmMes :: Data -> Int
faltaEmMes Invalida = -1
faltaEmMes Vazia = 0
faltaEmMes (Data d m y) = tamMes y m - d + 1
diasInicioAno :: Data -> Int
diasInicioAno Invalida = -1
diasInicioAno Vazia = 0
diasInicioAno (Data d m y) = mesesAnterioriores + d - 1 where
mesesAnterioriores = sum [tamMes y m' | m' <- deleta m [1..m]]
faltaEmAno :: Data -> Int
faltaEmAno data' = tamAno (ano data') - inicio
where inicio = diasInicioAno data'
Funções para manipular listas.
A primeira, recebe como argumento uma função Char -> Bool
, exemplo: (== '!')
, e uma String
;
devolve a String
separada pelo delimitador em forma de lista
A segunda apenas remove um dado elemento de uma lista, retornando outra lista sem tal elemento
separa :: (Char -> Bool) -> String -> [String]
separa p s = case dropWhile p s of
"" -> []
s' -> w : separa p s''
where (w, s'') = break p s'
deleta :: Eq a => a -> [a] -> [a]
deleta deleted xs = [ x | x <- xs, x /= deleted ]