ATENÇÃO:
Este blog será descontinuado em detrimento de marceloparrela.wordpress.comAssim, por favor, acessem marceloparrela.wordpress.com para continuarem a ler meus artigos.
Grato pela compreensão,
Marcelo Parrela
CSV (com campos separados por ';'),
que converta os dados para uma estrutura de dados do tipo tupla
e, finalmente, que processe os dados dessas tuplas.
Lisp).
Nota: Não conheço outras linguagens funcionais, mas, acredito que
em Scala e em Clojure teremos resultados muito interessantes também.CSV, com os campos sendo separados por ';'. Veja o modelo abaixo:Dado:
sigla;nome;número atômico;massa atômica;grupo;período
Onde:
Sigla e Nome são do tipo String;
Número atômico, Grupo e Período são do tipo Int;
Massa atômica é do tipo Double.
Decidi utilizar apenas uma tupla para representar um elemento químico.
Os dados do arquivo CSV serão mapeados para uma lista cujos elementos são desse tipo de tupla.
É importante salientar que existem outras formas de representar dados em Haskell. record do Pascal ou à struct de C.( String, String, Int, Double, Int, Int )
CSV de forma didática - para que as pessoas que não possuem familiaridade com a Linguagem Haskell consigam compreender a solução proposta.CSV na sessão Downloads, ao final do artigo.readFile - que executa uma leitura não bufferizada do arquivo.
Esta função retorna o conteúdo do arquivo carregado em uma única String. Exemplo:# rodando em shell - criando o arquivo de exemplo echo "Ac;Actínio;89;227;3;7" > linhas.txt echo "Ag;Prata;47;107.8682;11;5" >> linhas.txt echo "Al;Alumínio;13;26.9815386;13;3" >> linhas.txt
-- em Haskell - lendo o arquivo de exemplo a <- readFile "linhas.txt" print a "Ac;Actínio;89;227;3;7\nAg;Prata;47;107.8682;11;5\nAl;Alumínio;13;26.9815386;13;3\n"Nesse ponto, nota-se que os dados estão separados pelo caractere de quebra de linha
'\n'.
Para separar os dados, utilizaremos função lines que quebra uma
String em uma lista de Strings usando o caractere de
quebra de linha como delimitador. Veja o exemplo:a <- readFile "linhas.txt" let linhas = lines a print linhas ["Ac;Actínio;89;227;3;7","Ag;Prata;47;107.8682;11;5","Al;Alumínio;13;26.9815386;13;3"]Agora, iteramos cada elemento da lista usando a função
map e aplicamos - a cada elemento - uma função de transformação.
Esta função, por sua vez, separará os campos do CSV e os aplicará à tupla.map recebe - como parâmetro - uma lista e uma função transformadora que
processará os elementos dessa lista. Daí, ela itera os elementos da lista e
executa a função para cada um deles. O resultado final é uma lista que resultou do processamento da original. Exemplo:-- A função 'odd' recebe um número e retorna True se for ímpar e False se for par. let lista = map (odd) [1,2,3,4,5,6,7,8,9,10] print lista [True,False,True,False,True,False,True,False,True,False]Observação: Note que a função
map é usada exclusivamente
para transformar uma lista. E, no exemplo acima, foi usada para transformar
uma lista de números inteiros em uma outra lista de valores booleanos -
resultantes da aplicação da função odd.Strings, devemos separar os
campos (delimitados por ';') e convertê-los para a nova estrutura. Porém, não conheço, em Haskell,
uma função que faça o split pelo caractere ';'. Ou seja, que seja parametrizável.
E, sinto muita vergonha em ter que reconhecer isso! Eu já pesquisei em várias fontes, porém, não encontrei a tal função.lines, que já foi cidada. Outra função
é a words - que recebe uma String e retorna uma lista
cujos elementos são as palavras da String original (obs.: esta função não é adequada ao nosso problema).
Mas, uma bendita função split que seja parametrizável, eu ainda não achei não! :-(split. Veja o código abaixo:
-- by Marcelo Parrela - marcelo.parrela@gmail.com
split :: (Eq a) => a -> [a] -> [[a]]
split _ [] = []
split c as = w : split c r
where
(w,r) = split' c as []
split' _ [] l1 = (l1,[])
split' c (a:as) l1
| a /= c = split' c as (l1++[a])
| otherwise = (l1,as)
Com a função split, poderemos testar a separação dos campos CSV. Exemplo:
split ';' "Ac;Actínio;89;227;3;7" ["Ac","Actínio","89","227","3","7"]Veja o resultado da função
split em conjunto com a função map:map (split ';') [ "Ac;Actínio;89;227;3;7" , "Ag;Prata;47;107.8682;11;5" , "Al;Alumínio;13;26.9815386;13;3" ] [ ["Ac","Actínio","89","227","3","7"], ["Ag","Prata","47","107.8682","11","5"], ["Al","Aluminio","13","26.9815386","13","3"] ]Bem, já possuímos os dados dos elementos químicos em um formato muito mais amigável para se trabalhar. Basta-nos, agora, aplicar uma segunda função de transformação para converter os elementos em tuplas.
let lista = [ ["Ac","Actínio","89","227","3","7"], ["Ag","Prata","47","107.8682","11","5"], ["Al","Aluminio","13","26.9815386","13","3"] ]
let toTupla = (\[si,no,nu,ma,gr,pe] -> (si, no, read nu :: Int, read ma :: Double, read gr :: Int, read pe :: Int) )
let listaFinal = map toTupla lista
print listaFinal
[ ("Ac","Actínio",89,227.0,3,7) , ("Ag","Prata",47,107.8682,11,5) , ("Al","Aluminio",13,26.9815386,13,3) ]
Para deixar mais claro, o código, informo que a função read converte um valor de String para outro tipo de dado.
E, a aplicação do cast usando :: Int e ::Double apenas especifica o tipo de dado de destino dessa conversão.toTupla [si,no,nu,ma,gr,pe] = (si, no, read nu ::Int, read ma ::Double, read gr ::Int, read pe ::Int)
main = do
dados <- readFile "./elementos-quimicos.csv"
let elementos = map toTupla $ map (split ';') $ lines dados
print elementos
...
Acredito que é possível notar a simplicidade e a clareza de código. Ficou um código limpo, pequeno e de fácil manutenção.fatorial e quicksort.
Essas duas funções fornecem uma boa idéia do que ganhamos quando descrevemos um problema (forma descritiva) ao invés de tentarmos resolvê-lo (forma imperativa).
Quando vi o quicksort (em Haskell) pela primeira vez, percebi que a lista sofria duas leituras: uma para os elementos menores ou iguais ao pivot; outra para os elementos maiores que o pivot.
-- Tradicional: lê a lista duas vezes
quicksort [] = []
quicksort (a:as) = quicksort ( [x | x <- as, x <= a] ) ++ -- primeira leitura
[ a ] ++
quicksort ( [x | x <- as, x > a] ) -- segunda leitura
Desde essa primeira vez, fiquei incomodado com isso - pois, eu acreditava que seria melhor se fizesse apenas uma leitura na lista.
Demorou muito tempo, desde então... :-) Pois, não estudei tão rapidamente quanto poderia.
Mas, finalmente, produzi a tal função.
Segue o quicksort:
-- Com separador: lê a lista uma vez
-- by Marcelo Parrela (marcelo.parrela@gmail.com)
quicksort' [] = []
quicksort' (a:as) = quicksort' l1 ++ a : quicksort' l2
where
(l1,l2) = separate as a [] []
-- lê a lista e a separa em duas: l1 para os elementos
-- menores ou iguais ao pivot e l2 para os demais
separate [] _ a b = (a,b)
separate (l:ls) a l1 l2
| l <= a = separate ls a l1' l2
| otherwise = separate ls a l1 l2'
where
l1' = l1++[l] --montando lista de elementos menores
l2' = l2++[l] --montando lista para os demais elementos
Essa primeira solução ainda não é muito boa - por causa da aplicação da função (++). Esta função, neste contexto, prejudica a performance.
Sendo assim, produzi uma segunda solução, corrigindo apenas esse ponto. Segue o código abaixo:
-- Com separador: lê a lista uma vez
-- Com performance melhorada
-- by Marcelo Parrela (marcelo.parrela@gmail.com)
quicksort' [] = []
quicksort' (a:as) = quicksort' l1 ++ a : quicksort' l2
where
(l1,l2) = separate as a [] []
-- lê a lista e a separa em duas: l1 para os elementos
-- menores ou iguais ao pivot e l2 para os demais
separate [] _ a b = (a,b)
separate (l:ls) a l1 l2
| l <= a = separate ls a l1' l2
| otherwise = separate ls a l1 l2'
where
l1' = l:l1 --montando lista de elementos menores
l2' = l:l2 --montando lista para os demais elementos
Outro benefício interessante é que esta função é polimórfica. Note que não há declaração de tipos, na definição da função. Isso que dizer que ela pode ser utilizada para ordenar qualquer tipo de dado que seja ordenável. Veja o exemplo abaixo:
quicksort' "Teste de Ordenacao"
" OTaacddeeeenorst"
quicksort' [3,4,1,0,12,0,(-1),10]
[-1,0,0,1,3,4,10,12]
quicksort' [3.25 , 4.0 , 1.2, 0.5 , 12.1 , 0.2 , (-1) , 10 ]
[-1.0,0.2,0.5,1.2,3.25,4.0,10.0,12.1]
quicksort' [True, False, True, False, False ]
[False,False,False,True,True]
Por Marcelo Parrela (marcelo.parrela@gmail.com)