Hoje, inspirado pelo post de ontem decidi começar a abrir alguns exemplos que venho produzindo. Este primeiro trata-se de um aplicativo que estou criando com minha namorada Tânia que é bióloga para identificação de espécies de árvores. Estamos criando uma árvore de navegação apartir de um mecanismo árduo de leitura traduzido no livro que ela usava como base. O nome do mecanismo é Chave Dicotômica. A necessidade surgiu que o trabalho de procurar e navegar no livro é chato e lento, pois é necessário entender as especificações técnicas de cada parte da árvore e saber o que é cada particularidade de cada parte do contexto. Além de folhar várias vezes pra frente e pra trás. Veja meia página das menores chaves do livro.
Estive alguns dias na floresta com ela e foi muito divertido aprender um pouco de cada detalhe para identificação de uma espécie. Tivemos poucas evoluções no código, mas já criei a parte da navegação que é o pior trabalho com o livro.
O livro que ela usa tem 156 páginas de espécies divididas em 7 chaves principais. Já estamos na chave 4. Neste exemplo irei apenas utilizar a Chave 1 e 2 que são as mesmas da foto anterior.
O desafio da linguagem natural
Para chegar ao texto acima foi um caso muito pensado. Após a Tânia me explicar a ideia de como funcionava a chave, fiquei em dúvida sobre como ela iria alimentar o sistema com estas informações. Criar um cadastro de chaves? De espécies? Ou de termos? Tudo passava pela cabeça pois os conteúdos estavam a disposição e quem iria inserir os conteúdos não era eu.
Decidi então por copiar o texto na integra e iria criar um parser com expressões regulares para criar a chave dicotômica em formato de árvore mesmo. Analisei a chave e consegui absorver todo o conteúdo nas seguintes regras:
- Definições de novas chaves, estão em letra maiúscula
- Definições de items iniciam com números que serão seus id’s
- Items terminados com …. mais um número conectam a outro item com o mesmo id
- Espécies iniciam com 4 espaços
Exemplo do livro
Esta foi a linguagem natural mais próxima ao exemplo da foto anterior.
CHAVE 1 - PLANTAS DESPROVIDAS DE FOLHAS 1. Ramos suculentos, colunares, sucados longitudinalmente, com tufos de acúleos, sem espinhos terminais 14.1 Cereus hildmannianus 1. Ramos não suculentos, comprimidos lateralmente, com espinho terminal, decussados, ocasionalmente de formato triangular 61.1 Colletia paradoxa CHAVE 2 - PALMEIRAS 1. Plantas com acúleos 9.1 Acrocomia aculeata 1. Plantas sem acúleos .... 2 2. Lâminas em forma de leque, tão largas quanto compridas 9.9 Trithrinax brasiliensis 2. Lâminas não em forma de leque, mais compridas que largas .... 3 3. Base dos pecíolos permanecendo ao longo do caule 9.2 Butiaca pitata 9.3 Butia eriospatha 9.4 Butia yatay 3. Base dos pecíolos não permanecendo nos caules .... 4 4. Segmentos da folha dispostos em mais dois planos 9.8 Syagros romanzoffiana 4. Segmentos da folha dispostos em dois planos .... 5 5. Base dos pecíolos de cor acinzentada 9.5 Butyagros x nabuannandii 5. Base dos pecíolos de cor verde .... 6 6. Bainhas foliares envolvendo completamente a porção apical do caule 9.6 Euterpe edulis 6. Bainhas foliares não envolvendo completamente a porção apical do caule 9.7 Geonoma schottiana
Expressões regulares - diversão pra noite toda
Agora chegamos as expressões. Absorvendo caso a caso vamos criar as expressões regulares separadas.
1. Definições de novas chaves, estão em letra maiúscula
Essa foi fácil, sempre inicia com a palavra CHAVE, tém um número sequencial e um traço que divide da descrição.
CHAVE = /^CHAVE (.*) - (.*)/
2. Definições de items iniciam com números que serão seus id’s
Os items como “1. Plantas com acúleos” ou que apontam para outros como “5. Base dos pecíolos de cor verde …. 6” casam nesta expressão regular. Inicia(^) com número(\d+) seguido de ponto (\.) com barra invertida pois “.” é um caractér especial nas expressões regulares. Neste caso a expressão está apenas buscando genéricamente por qualquer final, sem distinção de ter ou não o link.
ITEM = /^(\d+)\.\s(.*)$/
3. Items terminados com …. mais um número conectam a outro item com o mesmo id
Os items terminados com link para outros items são o segredo que mata o trabalho árduo de pesquisar no livro. A conecção está ligada com logo em seguida. Estes itens serão conectados a outros itens em um futuro próximo.
LINK = /\s\.{4}\s(\d+)$/
4. Espécies iniciam com 4 espaços
\s identifica um espaço e isso torna mais claro do que construir uma expressão como / {4}/
, e as chaves permitem adicionar o número de ocorrências.
SPECIE = /^\s{4}(.*)/
Chave completa
Então unindo todas estas expressões sobre o arquivo anterior, devemos criar uma árvore com nodos. Reusufruindo do mundo opensource, vamos extender a classe Tree::TreeNode da biblioteca ruby-tree.
Para instalar a biblioteca digite:
gem install ruby-tree
A biblioteca tem uma classe principal chamada TreeNode, ela tem os attributos nome e conteúdo, sendo o nome de cada nodo único. A classe Dicotomica representa a raiz de todas as chaves dicotômicas, logo irá adicionar todas as chaves a raiz e cada item dentro de cada chave conforme as regras comentadas anteriormente.
require "rubygems" require "tree" class Dicotomica < Tree::TreeNode attr_reader :last_node, :last_root def initialize name = "Chave Dicotômica", content = nil, load_from = "dicotomica.txt" @pending_replace = {} super name,content if load_from File.readlines(load_from).each do |instruction| know(instruction.chomp) end end end CHAVE = /^CHAVE (.*) - (.*)/ ITEM = /^(\d+)\.\s(.*)$/ LINK = /\s\.{4}\s(\d+)$/ SPECIE = /^\s{4}(.*)/ def know(string) if string case string when CHAVE add_root [$1,$2].join(" - ") when ITEM if replace_node = @pending_replace[$1] @last_node = replace_node << Tree::TreeNode.new(string) else add_node string end if string =~ LINK @pending_replace[$1] = @last_node end when SPECIE add_specie $1 end end end def add_root name @last_root = self << Tree::TreeNode.new(name, 'root') end def add_node name @last_node = @last_root << Tree::TreeNode.new(name, 'node') end def add_specie name @last_node << Tree::TreeNode.new(name, "specie") end end
Rodando o arquivo texto com o algorítmo acima temos uma nova árvore pronta para ser desfrutada:
O bacana de trabalhar com ruby é poder utilizar cada parte do código independente. Observe a utilização desta árvore dentro do fonte.
jonatas@jonatax:~/chave-dicotomica-android/src$ irb -r dicotomica.rb irb(main):001:0> Dicotomica.new.print_tree;nil * Chave Dicotômica |---+ 1 - PLANTAS DESPROVIDAS DE FOLHAS | |---+ 1. Ramos suculentos, colunares, sucados longitudinalmente, com tufos de acúleos, sem espinhos terminais | +---> 14.1 Cereus hildmannianus | +---+ 1. Ramos não suculentos, comprimidos lateralmente, com espinho terminal, decussados, ocasionalmente de formato triangular +--- \\> 61.1 Colletia paradoxa +---+ 2 - PALMEIRAS |---+ 1. Plantas com acúleos | +---> 9.1 Acrocomia aculeata +---+ 1. Plantas sem acúleos .... 2 |---+ 2. Lâminas em forma de leque, tão largas quanto compridas | +---> 9.9 Trithrinax brasiliensis +---+ 2. Lâminas não em forma de leque, mais compridas que largas .... 3 |---+ 3. Base dos pecíolos permanecendo ao longo do caule | |---> 9.2 Butiaca pitata | |---> 9.3 Butia eriospatha | +---> 9.4 Butia yatay +---+ 3. Base dos pecíolos não permanecendo nos caules .... 4 |---+ 4. Segmentos da folha dispostos em mais dois planos | +---> 9.8 Syagros romanzoffiana +---+ 4. Segmentos da folha dispostos em dois planos .... 5 |---+ 5. Base dos pecíolos de cor acinzentada | +---> 9.5 Butyagros x nabuannandii +---+ 5. Base dos pecíolos de cor verde .... 6 |---+ 6. Bainhas foliares envolvendo completamente a porção apical do caule | +---> 9.6 Euterpe edulis +---+ 6. Bainhas foliares não envolvendo completamente a porção apical do caule +---\\> 9.7 Geonoma schottiana => nil irb(main):002:0>
Ruboto
Ruboto é uma gem muito legal que permite portar jruby e criar aplicativos para android usando jruby ao invés de java. É bem divertido e como sou apaixonado por ruby tive que experimentar.
gem install ruboto
Ruboto é estilo rails e tem seu próprio gerador de aplicativos e outras atividades. Para criar o aplicativo usei o seguinte comando:
ruboto gen app --package=me.ideia --target 1 --name ChaveDicotomicaAndroid --path /diretorio/destino
Use ruboto gen app --help
para ver todas as opções. No site ruboto tem muitas coisas interessantes para se aprender e o melhor mesmo é baixar o projeto do ruboto-irb para testar no próprio aparelho.
E aqui está o código da aplicação que é apenas uma Activity principal:
require 'ruboto/activity' require 'ruboto/widget' require 'ruboto/menu' require 'ruboto/util/toast' require 'dicotomica' ruboto_import_widgets :LinearLayout, :EditText, :TextView, :ListView, :Button $activity.start_ruboto_activity "$dicotomica" do @dicotomica = Dicotomica.new def on_create(bundle) start_tree end def start_tree @node = @dicotomica go_to_list end def go_to_list items = @node.children.collect(&:name) setContentView(list_view :list => items, :on_item_long_click_listener => proc{|av, v, pos, item_id| @node = @node.children[pos] help_about }, :on_item_click_listener => proc{|av, v, pos, item_id| @node = @node.children[pos] if @node and @node.hasChildren? go_to_list else toast("Espécie encontrada") end } ) end def about_us setContentView(Ruboto::R::layout::about) true end def help_about toast("Help about: #{@node.name}") end handle_create_options_menu do |menu| add_menu("Chave Dicotômica") { start_tree } add_menu("Sobre nós") { about_us } add_menu("Exit") {finish} true end end
Se você tem um android baixe aqui e instale no seu próprio aparelho esse exemplo.
Abaixo seguem os printscreens do aplicativo:
Tela inicial
A tela inicial já começa com um list view, e é indicado pelo método on_create do código anterior. O list view, por sua vez vem carregado com os filhos do primeiro nível da chave dicotômica, que neste caso são as chaves.
Tela inicial com o menu
O menu foi criado no último bloco do arquivo anterior com handle_create_menu_options
Tela sobre
A tela de sobre é um caso diferente e que foi o mais difícil até encontrar a solução. Este caso está utilizando a interface de recursos gerados pelo android através do xml. Note que o resource R vem dentro do módulo Ruboto, sendo Ruboto::R ao invés do R convencional na programação android.
Navegando entre nodos
Este print screen apenas pra mostrar que está funcionando a navegação entre nodos internos da árvore.
Espécie encontrada
Exemplo do toast rodando :D
Conclusões
Aparentemente é muito simples trabalhar com jRuby no Android, no entanto ainda não parece ser tão viável pela demora do jRuby para inicializar. Estou contando que as próximas versões se superem pois atualmente demora 17 segundos para inicializar o jruby no meu aparelho (Sansung Galaxy Ace). A inicialização do app é muito rápida, quando roda sobre o irb, é muito fácil de perceber que a VM é que demora pra carregar, e tirando isso é excelente.