by Marcelo Parrela - marcelo.parrela@gmail.com
Ideia Inicial
Deparei-me com uma ideia interessante: representar os elementos químicos, da tabela periódica, em Haskell. Pensei em fazer uma aplicação que carregue os dados de um arquivoCSV (com campos separados por ';'),
que converta os dados para uma estrutura de dados do tipo tupla
e, finalmente, que processe os dados dessas tuplas.
Não me prenderei aos tipos de processamentos que serão possíveis - até mesmo porque não tenho isso claro, em mente, ainda. Mas, gostaria de falar sobre a carga do arquivo e de como serão feitas as transformações dos dados.
Linguagens funcionais trabalham a transformação de dados de uma maneira muito direta; chega a ser impressionante a facilidade de se lidar com esse tipo de problema - usando Haskell (também,
Lisp).
Nota: Não conheço outras linguagens funcionais, mas, acredito que
em Scala e em Clojure teremos resultados muito interessantes também.Estrutura de Dados
Consegui informações básicas sobre todos os elementos da tabela periódica - em sites, na Intenet. São elas: sigla, nome, número atômico, massa atômica, número do grupo e número do período. Reuní tudo e montei um arquivoCSV, 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. Nota: Para quem não está acostumado com programação funcional, tupla é uma estrutura de dados muito simples, assemelhando-se ao
record do Pascal ou à struct de C.Essa tupla possuirá a seguinte estrutura:
( String, String, Int, Double, Int, Int )
O Processamento
- Observações:
- Caro leitor, procurei descrever a carga e o processamento do
CSVde forma didática - para que as pessoas que não possuem familiaridade com a Linguagem Haskell consigam compreender a solução proposta. - Não me preocupei com tratamento de exceção. O processamento é feito levando-se em consideração que tudo irá bem.
- Você poderá baixar os códigos e o
CSVna 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.A função
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.Retornando ao problema proposto neste artigo: para cada elemento da lista de
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.Curiosamente, em Haskell, existem diversas funções que executam algum tipo de split. Tais como
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! :-(Foi exatamente por esse motivo que tive de escrever minha própria funçã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.A partir daqui, possuímos os dados em formatos de fácil processamento e poderemos elaborar cálculos diversos.
Montando o Quebra-Cabeças
Agora, vamos ver como fica tudo junto: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.Downloads
- Arquivos para baixar:
- ElementoQuimico.hs
- elementos-quimicos.csv
- Split.hs
Nenhum comentário:
Postar um comentário