sexta-feira, 17 de dezembro de 2010

Retrospectiva 2030

O Brasil tornou-se a primeira grande nação a abolir o transporte rodoviário baseado em combustíveis fósseis. Todos os carros, caminhões, ônibus e motocicletas novos são movidos a álcool, a hidrogênio, a eletricidade ou a ar comprimido. A despoluição da região metropolitana de São Paulo foi um dos efeitos mais claros dessa mudança. Antecederam ao Brasil a Nova Zelândia, a Dinamarca e o Uruguai.

O país continua crescendo fortemente e absorve mão-de-obra dos países vizinhos, como Paraguai e Bolívia, deixando grandes espaços despovoados no centro do continente. Essa concentração economica no Brasil tem favorecido a recomposiçao ambiental da região. O Paraguai transformou toda a região do Chaco em um parque nacional.

O Mercosul agora abrange todos os países da América do Sul, exceto as Guianas e o Suriname. As negociações por uma moeda única prosseguem e o nome deve ser Sol, Bolívar, Colombo ou Peso. O BNDES foi transformado em Banco de Desarollo del Cono Sur.

A Suíça agora é conhecida como o Uruguai da Europa. Refletindo o movimento do século passado, mais italianos rumam à Argentina para fugir da estagnação de seu país.

O presidente Rodriguez, dos Estêites, finalmente declarou a moratória da dívida. No Brasil, o dólar vinha sendo cotado a US$17 por real e, após a moratória, terminou o ano estável a US$45. A maior parte das reservas internacionais agora está guardada em Euros e Yuans. A indifindável crise estadounidense, com raízes na crise financeira de 2008, tem levado alguns estados do sul a cogitar a união com o México. Os maiores interessados são o Novo México, a Califórnia, o Arizona e o Texas.

A Europa sofre com uma crise aguda de falta de mão-de-obra. Como resultado, há uma grande entrada de profissionais da China e do Oriente Médio; essas são atualmente as únicas regiões com excesso de mão-de-obra qualificada. A Turquia abandonou definitivamente as esperanças de participar da zona do Euro e iniciou conversas para a criação de um bloco com Argélia, Tunísia e Líbia.

Israel continua em conflito com seus vizinhos que exigem o fim de novos assentamentos na Jordânia, no Líbano e na Síria para iniciar tratativas de paz.

Nos esportes, a CBF impôs um limite ao número de jogadores europeus e africanos. As seleções de vôlei continuam invictas há 10 anos e o país agora é conhecido como o país do vôlei. O país também tem 3 jogadores entre os 10 melhores da ATP e 5 entre os 20 melhores. Um brasileiro ganhou pela primeira vez um grande torneio de golfe e na F1 persiste uma rivalidade intensa com os alemães com 5 campeonatos para cada país na última década.

segunda-feira, 29 de novembro de 2010

Explorações fractais V

Depois de experimentar algumas pequenas alterações no Mandelbrot, resolvi colocar a CPU a trabalhar de verdade. Experimentei mudar a recursão para z=z200+c e o resultado foi melhor que o esperado.

As imagens mostram ampliações sucessivas do Mandelbrot modificado.

Explorações fractais IV

O fractal original de Mandelbrot usa a recursão z=z2+c, mas outras potências são igualmente interessantes. Resolvi desenhar uma seqüência (de 2 a 13), para compará-los.

O zoom no de potência 6 mostra detalhes novos.


Clique nas imagens para vê-las no tamanho original.

sexta-feira, 12 de novembro de 2010

Democracia evolutiva

A maior polêmica das últimas eleições (apesar dos esforços de Dilma e Serra), foi a do palhaço Tiririca. Gastou-se muito papel de jornal para o que será apenas um dentre 513 deputados federais. Tenho certeza que há muitos outros deputados ainda mais interessantes.

A indignação, creio, não foi proporcional ao tamanho do problema, se é que existe um. Não está sequer provado que Tiririca será um mau deputado, analfabeto ou não. Pode ser que um analfabeto dê mais valor à educação que os doutores que normalmente habitam o planalto central.

As pessoas acreditam ter o discernimento necessário para saber qual o melhor candidato, mesmo quando sequer lembram em quem votaram nas eleições anteriores! Além disso, como não existe um sistema de avaliação, não há como decidir, ao fim do mandato, se um deputado fez um bom trabalho. E os jornais dedicam mais tempo às aberrações que ao trabalho sendo conduzido seriamente. As escolhas para o legislativo acabam sendo, em grande parte, ideológicas ou de protesto (como parece ser o caso do Tiririca).

Por isso, sugiro algumas mudanças no sistema eleitoral (relativas ao legislativo, somente). Em primeiro lugar, o voto não deveria ser obrigatório. Assim, evitamos os votos de protesto e economizamos uns reais. Admito não ter nenhuma prova de que os revoltados não sairiam para votar mesmo assim, nem de que o voto de protesto tenha conseqüências nefastas para a nação. De qualquer forma, ele parece incomodar as pessoas e ocupar demasiado espaço nos jornais. Em segundo lugar, supondo que apenas as pessoas que se consideram politizadas (o que não significa que são) compareçam para votar, deveríamos ter um pouco mais de aleatoriedade nas eleições. Por exemplo, 10% das cadeiras do Congresso Nacional poderiam ser sorteadas. Aceitando que não sabemos sempre fazer as melhores escolhas, inserir um fator de aleatoriedade nos ajudará a enxergar possibilidades que normalmente não consideraríamos.

O sorteio não é uma novidade na democracia. Os gregos o usavam para selecionar todos os cargos, exceto o de general. Eles temiam que os cidadãos mais ricos ou conhecidos dominassem as eleições. Outra instituição daqueles tempos que poderíamos reviver é a do ostracismo. Os eleitores podiam, uma vez por ano, anotar o nome de qualquer cidadão e enviar o mais votado para o exílio por dez anos. A cada eleição, poderíamos também eleger alguns deputados para o ostracismo; assim os mais desprezados ganhariam um descanso por alguns anos (e nós deles).

Acredito que essas alterações seriam muito mais úteis e educativas que o voto obrigatório. Agregar um elemento de aleatoriedade tiraria o peso da necessidade de controle que os eleitores com inclinações menos democráticas ainda exibem e também permitiria à sociedade analisar seus próprios preconceitos. O voto pelo ostracismo seria um mecanismo mais construtivo de manifestação de instatisfação que o voto de protesto.

Pior do que está, garanto, não fica.

segunda-feira, 8 de novembro de 2010

Explorações fractais III

As imagens geradas pelo algoritmo inicial tinham faixas de cores, porque ele usa o número de passos necessários para a função z=z2+c fugir para o infinito.

A ação toda dá-se na função mandel().


function mandel(x,y) {
var c=0;
var r=0;
var i=0;
var MAXITER=1000;
while(r*r+i*i<4 && c<MAXITER) {
var nr=r*r-i*i+x;
var ni=2*r*i+y;
r=nr;
i=ni;
c+=1;
}
return c;
}



Cada coordenada da tela (x,y) vira um número imaginário (r,i). A função tira o quadrado desse número e soma o valor inicial até que o resultado seja maior que 4 (a conta tendeu ao infinito) ou o número de iterações passou de 10.000 (indicando que ele não vai tender ao infinito).

O número de iterações (c) é usado para escolher a cor e como muitos pontos caem entre uma iteração e outra, as faixas acabam aparendo.

Uma solução simples que encontrei para eliminar as faixas é normalizar com o MAXITER. A figura abaixo mostra o resultado.



A única mudança necessária no código é trocar o último comando da função mandel():


return (c/MAXITER)*256;



Uma alternativa é apresentada na Wikipédia: Continuous Smooth Coloring. O código e a imagem abaixo mostram a diferença. A imagem, ademais, foi gerada com uma paleta um pouco diferente (os componentes verde e vermelho são iguais).


return c + 1 - Math.log(Math.log(r*r+i*i))/Math.log(2);




Para terminar, como bônus, uma alteração descoberta por acaso que parece criar camadas na imagem.


return (Math.log(c)/Math.log(MAXITER))*256;


quarta-feira, 3 de novembro de 2010

Explorações fractais II

Como o primeiro explorador não guardava a seqüência de passos para encontrar um ponto específico do Mandelbrot, resolvi fazer umas pequenas alterações. Agora, cada zoom é desenhado num Canvas novo.


<html>
<head>
<style>canvas {border:2px solid black; margin: 1px};</style>
</head>
<body>
<script>
function mandel(x,y) {
var c=0;
var r=0;
var i=0;
var MAXITER=1000;
while(r*r+i*i<4 && c<MAXITER) {
var nr=r*r-i*i+x;
var ni=2*r*i+y;
r=nr;
i=ni;
c+=1;
}
return c;
}

var body=document.getElementsByTagName("body")[0];
var canvas, ctx, imageData;
var minx=-2;
var miny=-2;
var maxx=2;
var maxy=2;

function prepare() {
if(canvas) {
canvas.onclick=null;
}
canvas=document.createElement("CANVAS");
canvas.setAttribute("width", 256);
canvas.setAttribute("height", 256);
body.appendChild(canvas);
ctx=canvas.getContext("2d");
imageData=ctx.getImageData(0,0,256,256);
canvas.onclick=zoom;
}

function plot() {
var deltax=(maxx-minx)/256;
var deltay=(maxy-miny)/256;
var deltay=(maxy-miny)/256;

prepare();
for(var x=0; x<256; x++) {
for(var y=0; y<256; y++) {
var c=mandel(minx+x*deltax, miny+y*deltay);
var red=(c*8)%255;
var green=(c*9)%255;
var blue=(255-c*4)%255;
var index=(x+y*imageData.width)*4;
imageData.data[index+0]=red;
imageData.data[index+1]=green;
imageData.data[index+2]=blue;
imageData.data[index+3]=0xff;
}
}
ctx.putImageData(imageData,0,0);
}
plot();

function zoom(e) {
var x=minx+(maxx-minx)*((e.clientX-canvas.offsetLeft)/256);
var y=miny+(maxy-miny)*((e.clientY-canvas.offsetTop)/256);
var dx=(maxx-minx)/4;
var dy=(maxy-miny)/4;

minx=x-dx;
maxx=x+dx;
miny=y-dy;
maxy=y+dy;
plot();
}
</script>
</body>
</html>

O resultado é o que se vê na figura abaixo.


Clique na imagem para vê-la no tamanho original.

Explorações fractais

Resolvi investigar o novo elemento Canvas do HTML5. Tenho visto alguns efeitos interessantes e estava na hora de aprender a usá-lo. Tendo em vista a recente partida do Mandelbrot, resolvi criar uns fractais em sua homenagem.

Com poucas linhas, consegui criar um navegador de Mandelbrot. Basta clicar num ponto, que o programa amplia a região em volta dele. Para voltar ao início, basta recarregar a página.

O código abaixo funciona no Firefox. Não o testei em outros navegadores, exceto pelo IE8, no qual ele não funciona.


<html>
<head>
</head>
<body>
<canvas id="canvas" width="256" height="256"></canvas>
<script>

function mandel(x,y) {
var c=0;
var r=0;
var i=0;
var MAXITER=1000;
while(r*r+i*i<4 && c<MAXITER) {
var nr=r*r-i*i+x;
var ni=2*r*i+y;
r=nr;
i=ni;
c+=1;
}
return c;
}

var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var imageData=ctx.getImageData(0,0,256,256);

var minx=-2;
var miny=-2;
var maxx=2
var maxy=2;


function plot() {
var deltax=(maxx-minx)/256;
var deltay=(maxy-miny)/256;

for(var x=0; x<256; x++) {
for(var y=0; y<256; y++) {
var c=mandel(minx+x*deltax, miny+y*deltay);
var red=(c*8)%255;
var green=(c*9)%255;
var blue=(255-c*4)%255;
var index=(x+y*imageData.width)*4;
imageData.data[index+0]=red;
imageData.data[index+1]=green;
imageData.data[index+2]=blue;
imageData.data[index+3]=0xff;
}
}
ctx.putImageData(imageData,0,0);
}
plot();

function zoom(e) {
var x=minx+(maxx-minx)*((e.clientX-canvas.offsetLeft)/256);
var y=miny+(maxy-miny)*((e.clientY-canvas.offsetTop)/256);
var dx=(maxx-minx)/4;
var dy=(maxy-miny)/4;

minx=x-dx;
maxx=x+dx;
miny=y-dy;
maxy=y+dy;
plot();
}
canvas.onclick=zoom;

</script>
</body>
</html>

Não é muito código para o que o programa faz. Percebi que o navegador vai se tornar uma plataforma interessante para a criação de jogos.



O número máximo de iterações está fixo em 1000. No início, 64 são suficientes, mas com zoom muito grande mesmo 1000 são poucas. Então, é preciso achar uma maneira de ir aumentando aos poucos o número de iterações. Deixo isso como exercício para o leitor.

sábado, 30 de outubro de 2010

Analogia estapafúrdia

O problema do Tiririca nas eleições gerou uma polêmica e tanto. E revelou como as pessoas não entendem o nosso sistema político. Vou complicar, digo, esclarecer a situação.

Digamos que queiras um iPhone de natal. Papai Noel não acha um iPhone para comprar, mas encontra um Motorola F3. Pelo menos é um telefone, que é o que precisavas. Pior teria sido ganhar uma garrafa de whisky, que não gostas.

Pois bem, agora imagina que o Papai Noel só pode comprar um presente para todo mundo. A maior parte das pessoas pede um telefone, mas cada uma pede uma marca ou modelo diferente. Um grupinho pequeno, por outro lado, pede uma mochila das Meninas Superpoderosas. Como não pode agradar a todos os desejos, Papai Noel decide comprar mochilas para todos.

Então, vamos ter todo mundo de mochila cor-de-rosa, quando a maior parte queria mesmo era um telefone. Para isso serve o sistema de lista aberta (o da eleição para o Congresso): que as pessoas ao menos ganhem algo parecido com o que votaram.

Pronto, esta foi minha contribuição para a democracia brasileira. De lambuja, ganhei links para Tiririca e Meninas Superpoderosas no meu blog.

terça-feira, 26 de outubro de 2010

Anglicismo tupiniquim

Quando escutei minha sogra pronunciar a palavra, soube que o estrago estava feito. Agora vai entrar para o dicionário o tal do anderlaine. Ou será ãderlaine?

Em inglês, o traço baixo chama-se underscore. Ele também é conhecido como understike, low line ou low dash. O anderlaine, portanto, só pode ser invenção local.

E o bom e velho traço baixo entrou para a história. Daqui a vinte anos, quando eu mencionar o traço baixo, meu filho certamente vai rir do meu vocabulário antigo. Não importa, seguirei teimosamente apoiando nossa língua.

domingo, 24 de outubro de 2010

Campeonato mundial de jogos

Os jogos de computador estão ficando realistas demais. Acho que estão deixando de ser jogos para se tornarem simulações. E a criatividade está diminuindo; imagino que os desenvolvedores gastem tanto tempo aperfeiçoando os detalhes e efeitos realistas, que não têm tempo para a criatividade.

Então, proponho separar os jogos por categoria, a fim de realmente aferir a habilidade técnica e a criatividade dos programadores. Eu imaginei algumas e já aproveito para premiar os melhores jogos em cada uma.

A primeira é a Mosca. Nessa categoria, o cidadão tem que ter menos memória que a necessária para armazenar uma tela do jogo, não importa a resolução. E o ganhador é Thrust para o Atari 2600. O Atari tem 128 bytes de memória e os programas normalmente têm 4KB ou menos, mas podem ser maiores se o ROM for paginado (porque o Atari só usava 12 linhas de endereçamento). Esse jogo foi originalmente escrito para o BBC Micro e rodava sobre 32KB. Ele simula a física muito bem e isso já seria impressionante se, adicionalmente, um alemão chamado Thomas Jentzsch não o tivesse portado para o Atari. O jogo, ademais, foi portado apenas em 2000, muito depois do fim do sucesso comercial da plataforma.


A segunda categoria é a Galo. Nessa categoria incluo todos os jogos escrito para micros de 8 bits. Eu escolhi um grande campeão: Elite. Esse jogo necessitava de apenas 32KB e foi uma revolução porque, além dos gráficos em 3D incríveis (com eliminação de faces ocultas), foi dos primeiros a ter conteúdo proceduralmente gerado. O universo de Elite consiste de 8 galáxias com 256 planetas cada uma. O jogo foi planejado com um número muito maior de planetas, mas a editora achou que as pessoas não iam acreditar que um micro de 8 bits pudesse gerar tanta complexidade. Ele foi lançado em 1984 (depois de dois anos de desenvolvimento) e portado para quase todos os micros da época.


A categoria Pena abarca os jogos escritos para micros de 16 bits. E o campeão é o Another World do Amiga. Ele já representa um grande salto em relação aos jogos de 8 bits, porque o Amiga tinha hardware gráfico avançado e muito mais memória (512KB, pelo menos). A grande inovação foi o uso de efeitos cinematográficos; era quase como participar de um desenho animado. O jogo foi lançado em 1991.


Uma prova de que esses jogos eram realmente bons é que os três foram portados para várias outras plataformas. Nenhum deles foi tão copiado como o Elite, mas todos tiveram muito sucesso.

A programação de jogos costumava ser feita por uma ou duas pessoas. Hoje em dia, os estúdios têm dezenas de pessoas e os lançamentos custam milhões. Com tanta responsabilidade, talvez as empresas não queiram correr riscos e por isso relançam os mesmos jogos ano após ano (quantos jogos de futebol ainda precisamos?). Por sorte, temos os emuladores para reviver os bons tempos!

sábado, 16 de outubro de 2010

Benoit Mandelbrot (1924-2010)

Benoit Mandelbrot faleceu no dia 14, aos 85 anos. O trabalho dele ajudou a gastar um incontável número de ciclos por todo o mundo. Não existe uma plataforma para a qual alguém não tenha escrito um gerador de fractais e isso deve ter tido alguma influência no clima global.

Meu primeiro gerador de fractais foi copiado de uma revista, era escrito em BASIC e demorava cerca de três horas para completar uma tela de 160 x 256. Mais tarde, com Assembly, o tempo diminuiu para minutos. Como o micro suportava apenas 8 cores, para simular mais era preciso entrelaçar pontos de cores diferentes (mas isso só funciona bem em regiões homogêneas). Mais tarde, escrevi o gerador em Pascal.

A figura acima mostra a versão Assembly (para o 6502, que sequer tem instrução de multiplicação) que foi copiada da revista Acorn User. Cada imagem ocupava 20KB, mas com um pouco de compressão (usando Run-length encoding) dava para deixá-las com 8KB ou menos. O único porém é que era mais fácil carregar uma imagem descompactada: bastava carregar do disco direto para a memória mapeada para o vídeo.


Mandelbrot escreveu que "nuvens não são esferas, montanhas não são cones, costas não são círculos, árvores não são lisas e raios não andam em linha reta". Acho que ele provou também que os computadores e a matemática podem gerar muita beleza.

quinta-feira, 7 de outubro de 2010

Combinações de dígitos

Encontrar todas as combinações dentro de um conjunto é tão fácil como contar em binário. Digamos que eu tenha um conjunto de quatro itens; se eu contar até 15 (4 bits), vou ter 0000, 0001, 0010, 0011, etc.

Eu decidi que um bit 1 indica que o item está no subconjunto e que 0 indica que não está. Então, se o meu conjunto for (A, B, C, D), 0000 vai representar o conjunto vazio, 0001 vai representar (D), 0010 vai representar (C) e assim por diante. O último subconjunto é o próprio conjunto, representado por 1111.

O fato de que eu preciso contar até 2n-1 me lembrou do triângulo de Pascal. A soma dos números de cada linha do triângulo é 2n e isso me fez perceber que os números, individualmente, devem representar alguma coisa também.

1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1
1 7 21 35 35 21 7 1
1 8 28 56 70 56 28 8 1

Como cada número do triângulo representa a quantidade de subconjuntos com uma certa quantidade de itens, eles têm que representar também a quantidade de números que têm um determinado número de dígitos 1.

Por exemplo, a quarta linha é "1 3 3 1" e equivale a um número de 3 bits. Com 3 bits, podemos representar os seguintes números:

000 = 0
001 = 1
010 = 2
011 = 3
100 = 4
101 = 5
110 = 6
111 = 7

Temos, então, 1 número só de zeros, 3 com apenas 1 dígito 1, 3 com 2 dígitos 1 e apenas 1 com 3 dígitos 1.

Alternativamente, se eu quiser saber quantos números de 1 byte possuem 3 dígitos 1, posso rapidamente descobrir no triângulo que são 56.

Certamente, esconde-se aí alguma verdade capaz de trazer fama, dinheiro e mulheres.

terça-feira, 5 de outubro de 2010

Como enriquecer com informática

Eu já escutei mais de uma vez que não se pode ganhar dinheiro com informática. Obviamente, os desiludidos referiam-se a muito dinheiro.

Eu tenho certeza de que estão errados, porque dentre os mais ricos do mundo estão vários programadores. Bill Gates escreveu um BASIC e Larry Ellison escreveu um banco de dados, por exemplo.

Talvez essas pessoas simplesmente não saibam ganhar muito dinheiro e, portanto, mudar de profissão não vai ajudar em nada. Ou talvez não tenham realmente tentado. Parece-me que, para ser realmente rico, é preciso fazer algo diferente do que todo mundo está fazendo.

Atualmente, estou colocando muita fé na loteria. Sim, eu sei que todo mundo está fazendo isso. Então, para fazer algo diferente, resolvi aplicar minhas habilidades: escrevi um programa para procurar as combinações mais freqüentes nos números ganhadores. Eu procurei os números, as duplas, os trios, etc.

Para achar as combinações escrevi uma pequena função que recebe uma referência a uma lista e uma referência a uma função. Ela conta de 1 a 2n-1 e monta uma sub-lista conforme os bits que estiverem ativados no número corrente.

Então, se eu tiver uma lista de 6 números, a função vai contar de 1 a 63. Em binário, é de 1 (o primeiro item selecionado) a 111111 (todos os itens selecionados). A cada iteração, ela invoca a função passada como parâmetro para que essa contabilize a ocorrência.

sub combinations {
my ($array, $callback)=@_;
my $n=scalar(@$array);

for my $i (1..(2**$n)-1) {
my @t=();
for my $index (0..$n-1) {
push(@t, $$array[$index]) if $i & 2**$index;
}
&$callback(@t);
}
}

Quem quiser o código fonte completo e os números ganhadores deve me enviar R$100.

segunda-feira, 27 de setembro de 2010

Desapego material

Eu acho fantástica a maneira como o nosso sistema educacional forma pessoas desapegadas da realidade material. O que acontece no mundo real não afeta a maneira como as pessoas formam suas convicções políticas, econômicas e cotidianas.

As eleições deste ano seguem o padrão exasperador de ignorância militante. De tanto escutar falar mal do Bolsa Família e sobre como ela é culpada pela carga tributária, resolvi partir para minha atividade favorita: conferir os números.

Conforme o Ministério do Desenvolvimento Social, os benefícios variam de R$22 a R$200 para famílias com renda de até R$140 por pessoa. Considerando que o salário mínimo é de R$510, já vai ser dificil me convencer de que alguém vai deixar de trabalhar por isso. Lendo um pouco mais, descobri que o maior benefício seria concedido a uma família com pelo menos 3 crianças e 2 adolescentes. São 6 pessoas, no mínimo, porque se não tivessem mãe, os menores estariam numa instituição (ou na rua). Então, são R$33 por pessoa por mês.

Se uma criança passar toda a vida ganhando o benefício, pode esperar ganhar R$3.960 até os 15 anos e mais R$972 até os 17. São R$22 por mês antes dos 15 anos de idade e R$33 até os 17.

Segundo o IPEA (Instituto de Pesquisa Econômica Aplicada), a carga tributária para uma família que ganha até dois salários mínimos é de 48,9%. Então, uma criança que tenha passado toda a vida (204 meses) ganhando o benefício e, ao completar 17 anos, conseguir um trabalho com carteira assinada, pagará de volta o que recebeu em 19 meses.

Faixa de RendaCarga Tributária
Até 2 Salários Mínimos48,9%
De 2 a 5 Salários Mínimos35,9%
De 5 a 10 Salários Mínimos31,8%
De 10 a 15 Salários Mínimos30,5%
De 15 a 20 Salários Mínimos28,5%
De 20 a 30 Salários Mínimos28,7%
Acima de 30 Salários Mínimos26,%


Pensando bem, nem precisa ter carteira assinada, porque vai pagar apenas impostos sobre o consumo, já que não vai ter renda suficiente para pagar imposto de renda.

Enquanto isso, o orçamento anual da UFRGS é de R$874.369.261,58. São 42.054 alunos e, portanto, o custo anual de cada um é de R$20.791,58.

Um curso de 5 anos custa, então, R$103.957,90. Digamos que o recém-formado seja muito sortudo e consiga um emprego com salário de R$10.000,00. Segundo o IPEA, a carga tributária dele será de 28,7%. Serão 36 meses até que ele consiga pagar em impostos o que recebeu. Acho que R$3.000 ainda seria uma expectativa otimista de salário médio para um recém-formado e com isso seriam precisos 108 meses para retornar aos cofres públicos o custo do curso (sua carga tributária seria de 31,8%).

Talvez a universidade, mesmo sendo tão cara, esteja nos falhando, porque escuto muita gente que melhorou de vida graças a ela reclamar do Bolsa Família com argumentos falaciosos. Não sei bem se é falta de sensibilidade social ou falta de intimidade com os números e a lógica.

De qualquer forma, não excluo a possibilidade de que existam argumentos inteligentes contra os programas sociais vigentes e que, a qualquer momento, alguém sacará o trunfo da manga.

sexta-feira, 24 de setembro de 2010

O original

La Paz é uma cratera lunar no coração da América do Sul. É uma cidade improvável criada para lembrar aos sul-americanos o seu verdadeiro caráter.

Os índios têm pouco e com isso eram ricos. Os espaços enormes, a abundância de água, comida e árvores são a riqueza do sul-americano. O homem branco trouxe a propriedade particular e com ela a miséria.

Uma gringa com muito esforço subiu uma ladeira de La Paz para fazer compras num mercado a céu aberto. Encontrou um aparelho de DVD da Sony por uma pechincha, mas o aparelho não estava lacrado. Do Japão até a Bolívia, ela queria que o lacre garantisse a pureza tecnológica do aparelho.

Ela pediu, um amigo traduziu e o índio deu de ombros. Ele olhou ao redor e logo enfiou a mão numa caixa; dela tirou uma aparelho de lacrar com uma fita que monotonicamente repetia "SONY SONY SONY SONY".

A gringa não deve ter percebido, porque não poderia conceber, mas o aparelho fora produzido na Zona Franca de Manaus. Do meio da selva, surgiu uma aparelho japonês que um índio boliviano vendeu a uma loira americana desconfiada.

Esta noite vou ter que assistir ao correspondente internacional da Globo comentar as últimas da Venezuela desde Nova Iorque. Os gringos nacionais querem que vejamos e ouçamos com os olhos e ouvidos dos gringos originais as notícias que chegam lacradas desde a fonte. Mas será que no meio do caminho não há um lacrador espúrio?

segunda-feira, 20 de setembro de 2010

Otimizando a niqueleira II

Tendo já descoberto que uma moeda de dois centavos ajudaria a reduzir a quantidade de moedas em circulação em 20%, resolvi procurar as configurações ótimas para a niqueleira.

Estendi o programa para testar todas as possibilidades de n moedas. Comecei com quatro e fui até seis. A única restrição foi que todos os conjuntos tivessem a moeda de um centavo, para que todos os valores entre 1 e 99 pudessem ser representados.

Se o Banco Central produzisse apenas quatro moedas, a combinção ótima teria as seguintes moedas:
  • 1, 3, 11 e 37 ou
  • 1, 3, 11 e 38
Essas duas opções requerem apenas 410 moedas para representar todos os valores. Isso já é melhor que o produzido pelas cinco moedas em circulação (50, 25, 10, 5 e 1). Aposto que haveria inúmeras reuniões noite adentro até que o comitê decidisse por 37 ou 38.

Se produzisse 5 moedas, o Banco Central teria mais opções e o comitê nunca ia achar uma solução que agradasse a todos:
  • 45, 20, 8, 3 e 1
  • 45, 18, 7, 3 e 1
  • 44, 20, 8, 3 e 1
  • 44, 18, 7, 3 e 1
  • 41, 16, 7, 3 e 1
  • 40, 16, 7, 3 e 1
Essas seis combinações produzem uma soma agregada de 346 moedas para representar todos os valores entre 1 e 99. Em média, portanto, seriam precisas 3,49 moedas.

Finalmente, a busca da melhor combinação de seis moedas levou quase um dia inteiro. São 67 milhões de combinações. As campeãs, com uma soma agregada de 313 são:
  • 65, 29, 13, 5, 2 e 1
  • 64, 29, 13, 5, 2 e 1
  • 63, 25, 11, 5, 2 e 1
  • 62, 25, 11, 5, 2 e 1
Nenhuma combinação de seis moedas, portanto, produz uma média menor que três. Gostei da moeda de 65 centavos, porque com ela só me faltaria uma nota de R$3 para pagar a lotação.

quinta-feira, 16 de setembro de 2010

Otimizando a niqueleira

Sinto falta de uma nota de R$3 e de uma moeda de 15 centavos. Uma tarifa de lotação custa R$3,65 e a melhor combinação de valores é R$2+R$1+R$0,50+R$0,10+R$0,05.

Reparei que a seqüência de valores das moedas é diferente da de notas. Vou fazer de conta que a moeda de R$1 é uma nota.

Então, temos as seguintes moedas: 1, 5, 10, 25, 50. Mas as notas são 1, 2, 5, 10, 20, 50 e 100. Se existe uma combinação ótima, ela deveria ser usada nas duas seqüências, não?

Resolvi descobrir qual seqüência produz a menor contagem agregada para todos os valores entre 1 e 99. Para as moedas, seriam os valores entre 1 centavo e 99 centavos. Para as cédulas, seriam os valores entre R$1 e R$99.


my @moedas=(50,25,10,5,1);

my $total=0;
for my $valor (1..99) {
print "\n";
my $soma=$valor;
print "$valor =>";
for my $moeda (@moedas) {
if($soma>=$moeda) {
$n=int($soma/$moeda);
$total+=$n;
$soma-=$n*$moeda;
print "$n*$moeda; ";
}
}
}

print "\n$total\n";

O programa imprime na tela as melhores combinações para cada valor. O algoritmo é simples: ele vai da maior moeda para a menor, tentado maximimar o uso de cada uma.

Encontrei que a seqüência das notas tem uma contagem agregada de 340 (em média, são necessárias 3,43 notas) e a das moedas precisa de 420 (em média são necessárias 4,24 moedas). No entanto, a seqüência das notas tem a vantagem de ter um elemento a mais. A nota de R$2 faz toda a diferença e trocar 25 por 20 não altera em nada a contagem.

Então, sugiro que o Banco Central comece a emitir moedas de 2 centavos.

Brincando um pouco com os valores, descobri que uma nota de R$35 (ou R$30) e outra de R$15 baixariam a contagem agregada das notas para 300. Será que há uma combinação que tenha uma contagem menor que 300?

sábado, 11 de setembro de 2010

O olho do camelo

Amina riu quando finalmente encontrou o templo. Um neon vermelho iluminava uma pequena porta com as palavras "Digital Guru". Mesmo para a Índia, era demais.

Seguindo o som de música, ela subiu uma escada estreita que desembocava imediatamente num quarto. Quando seus olhos finalmente alcançaram o nível do chão, viu um homem magro e sem camisa flutuando a cinco centímetros do chão. Ela gritou um grito muito curto e agudo e o homem imediatamente abriu os olhos de uma maneira ainda mais assutada.

Quando finalmente chegou perto do guru, Amina percebeu a madeira que surgia da parede e o sustentava, aparententemente, no ar.

-Você é um charlatão! Que truque barato!
-Eu coloquei uma madeira num buraco na parede e sentei sobre ela. Os suecos venderiam isso por muito dinheiro numa loja IKEA. Porque eu sou indiano, sou charlatão?

Amina desculpou-se e logo apresentou sua dúvida:

-Guru digital, por que não há soluções inteiras para a enésima raiz da soma de inteiros elevados à enésima potência?

O guru mostrou-se impaciente:

-Você quer uma solução simples para um problema que precisa de tantas palavras? Vocês filhos de Abraão sempre procuram uma singularidade. Na Índia, temos 36 milhões de Deuses e todo dia nasce um novo Deus. Para cada problema, temos um Deus. Temos tanto de tudo, que não nos interessa a simplicidade. A beleza, a procuramos na complexidade. Olhe no olho do camelo e veja todos os Deuses que existem entre 2 e 3.

Amina compreendeu imediatamente. Voltando ao hotel, escreveu o seguinte em seu computador:


use GD;

my $img=new GD::Image(255,255);

my @colours=();

for my $c (0..255) {
$colours[$c]=$img->colorAllocate($c, $c, $c),
};

my $index=0;
for(my $i=2; $i<=3; $i+=0.05) {
for my $a (1..255) {
for my $b (1..255) {

my $c=($a**$i+$b**$i)**(0.5);

my $d=int(255*($c-int($c)));
$img->setPixel($a, $b, $colours[$d]);
}
}
open(IMAGE, sprintf(">still%02d.gif", $index));
binmode IMAGE;
print IMAGE $img->gif;
close IMAGE;
$index++;
}

Esse programa gerou 20 imagens, com tons de cinza indicando a diferença entre cada c e sua parte inteira.

Usando gifsicle, ela juntou as imagens numa animação.


gifsicle --loop --delay 20 still*.gif > anim.gif


Existem muitos Deuses, mas como na Índia, nenhuma solução. Amina contentou-se em admirar a complexidade.

domingo, 5 de setembro de 2010

Ciclos à toa

Ontem escutei que a "natureza é sábia" e imediatamente retruquei que não é, porque faz tudo por tentativa e erro. Só vemos os sucessos; os erros não sobrevivem por muito tempo.

Resolvi procurar algum problema simples que possa ser resolvido por força bruta, mas nada tão sofisticado como programação genética. Então, montei um pequeno programa em Perl para procurar as melhores frações para determinado número. Para tornar o exercício um pouco mais interessante, o programa produz as melhores frações de até n dígitos.

my $n=$ARGV[0];

for my $d (1..5) {
my $v=0;
my $vx, $vy;

for my $x (1..(10**$d-1)) {
for my $y (1..(10**$d-1)) {
my $t=$x/$y;
($v,$vx,$vy)=($t,$x,$y) if abs($n-$t)<abs($n-$v);
print "$x/$y ($v)\r";
last if $t<$n-0.1;
}
}
print "Melhor opção de $d dígitos: $vx/$vy ($v)\n";
}

O programa recebe um número $n pela linha de comando e compara cada tentativa $t com o vitorioso $v. Para otimizar um pouco a busca, o laço do $y termina quando o número estiver ficando menor que o alvo.

Não procurei otimizar mais, porque preciso de tempo para o café e o computador não reclama mesmo.

Para cada tentativa, o programa imprime os valores de $x e $y testados e a melhor fração até o momento.

Com o número e obtive o seguinte:

>fracoes.pl 2.71828183
Melhor opção de 1 dígitos: 8/3 (2.66666666666667)
Melhor opção de 2 dígitos: 87/32 (2.71875)
Melhor opção de 3 dígitos: 878/323 (2.71826625386997)
Melhor opção de 4 dígitos: 2721/1001 (2.71828171828172)
Melhor opção de 5 dígitos: 72396/26633 (2.7182818308114)

E com pi, antes de terminar a execução:

>fracoes.pl 3.1415926535
Melhor opção de 1 dígitos: 3/1 (3)
Melhor opção de 2 dígitos: 22/7 (3.14285714285714)
Melhor opção de 3 dígitos: 355/113 (3.14159292035398)
Melhor opção de 4 dígitos: 355/113 (3.14159292035398)
18425/1295 (3.14159292035398)

Nenhuma opção de 4 dígitos é melhor que a de 3, então o 355/113 saiu repetido.

Buscar os 5 dígitos é um pouco demorado, mas ainda é melhor do que procurá-los com nanquim e papel. E uma fração de 6 dígitos seria difícil de memorizar, logo teria pouca utilidade prática.

domingo, 29 de agosto de 2010

Manuais, mentiras e telefonemas

O manual que eu escrevi, onde estará? O usuário não o leu, com certeza. Eu tento dar a dica:

-Isso está na segunda página do manual.

Mas a sutileza perde-se. Isso eu até suporto, mas não consigo entender por que o usuário mente para mim.

-Nunca fiz isso.

E o registro, brilhando na minha cara, indicando que fez. Ou ainda pior, diz que fez, mas nunca entrou no sistema.

Não se pode confiar no usuário. O primeiro passo de um atendimento é coletar todas as informações necessárias, porque nada do que ele diz é confiável. Nada mesmo. Ele pode dizer que usa IE, mas estar num Firefox. Pode dizer que usa XP, mas estar num Vista. Pode nem ser ele o usuário.

O usuário mente até no cadastro:

-Não recebi a senha.
-A senha foi para o email xyz@abc.com.
-Não uso esse email.
-Agora seria um bom momento para reativá-la, porque ainda não implementamos o subsistema de localização de emails secretos de usuários.

Está bem, não posso responder isso. Então, escrevo aqui. O usuário não tem senso de humor.

Atender por telefone é uma arte. Há um jogo muito sutil que precisa ser jogado. Tenho que fazer o usuário entender o que ele não quer entender (se quisesse, teria lido o manual). Quero que ele perceba que é um estorvo, mas não posso dizer diretamente. Minhas frases têm que continuar depois do ponto final. Lá no fim da frase mais polida, o usuário tem que perceber que paira no éter um "sua besta".

-De nada. Se quiseres trocar a senha outra vez, basta clicar no primeiro botão da primeira tela - aquele que diz "Trocar senha" - e seguir os passos que estão na terceira página do manual. É simples; são só três campos. Tenha uma boa semana.

Viste?

Todos os anos sai a triste estatística de quanto livros em média cada brasileiro lê. Eu adicionaria a estatística de quantos manuais o brasileiro não lê.

segunda-feira, 23 de agosto de 2010

Mais regressões lineares em SQL

Vasculhando o manual do Oracle, encontrei umas funções estatísticas e entre elas algumas funções para calcular regressões lineares. A função REGR_SLOPE() logo revelou-se a mais útil. Ela retorna a inclinação da reta que indica a tendência dos dados.

Para repetir o cálculo do artigo anterior, fiz o seguinte:

select min(idhm)+(2010-min(ano))*regr_slope(idhm, ano)
from indices_municipais
where municipio=130

Tenho a tendência de idhm sobre ano, então adiciono o menor valor de idhm ao produto da tendência pela diferença de anos e obtenho o resultado esperado para 2010 (a base tem dados de 1991 e 2000).

Depois, resolvi classificar os municípios pelo IDHM futuro:

select trunc(min(idhm)+(2010-min(ano))*regr_slope(idhm, ano),3), nome
from indices_municipais natural join municipio
group by nome
order by 1 desc

Essa consulta tem um join com a tabela de municpios, porque eu ainda não decorei todos os códigos. E adicionei um TRUNC(), porque resultados com 18 dígitos cansam os olhos.

A consulta é muito mais simples que a do primeiro artigo, mas será mais rápida? Reescrevi a consulta original para poder classificar todos os municípios.

select trunc(a+2010*b,3), nome from (
select (x2*y-xy*x)/(n*x2-x*x) a, (n*xy-x*y)/(n*x2-x*x) b, nome from (
select count(1) n, sum(ano) x, sum(ano*ano) x2,
sum(idhm) y, sum(idhm*idhm) y2, sum(ano*idhm) xy,
nome
from indices_municipais natural join municipio
group by nome
)
) order by 1 desc

Tabulei os tempos:
Consulta com REGR_SLOPEConsulta complicada
0,015s0,014s
0,015s0,014s
0,016s0,013s
0,014s0,017s
0,021s0,015s
0,013s0,014s
0,016s0,015s
0,023s0,015s
0,015s0,021s
0,015s0,014s

Então, a consulta complicada foi mais rápida em 8 das 10 execuções. Ambas produzem 467 linhas (o município campeão é Ibiaçá).

segunda-feira, 16 de agosto de 2010

Regressão linear em SQL

Uma das tarefas mais divertidas e inúteis para economistas amadores (como eu) passarem o tempo é procurar maneiras de prever o futuro. E uma das maneiras mais simples de se fazer isso é usando a regressão linear (finalmente uma entrada da Wikipédia cuja versão em português é melhor que a versão inglesa!).

O objetivo é simples: dada uma série, encontrar uma reta que descreva a tendência. Com essa reta, pode-se imaginar para que lado as medidas estão indo.

Decidi usar uma tabela de IDHs municipais para testar meu SQL. A tabela tem, entre outras colunas, ANO, IDHM (indíce de desenvolvimento municipal) e MUNICIPIO.

Conforme o artigo da Wikipédia, o objetivo é encontrar os termos a e b da equação y=a+bx. As equações para cada termo são trabalhosas, então decidi montar a consulta em 3 partes. Na primeira, calculo os pedaços das equações; na segunda calculo a e b; na última, prevejo o futuro.


select a+2010*b from (
select (x2*y-xy*x)/(n*x2-x*x) a, (n*xy-x*y)/(n*x2-x*x) b from (
select count(1) n, sum(ano) x, sum(ano*ano) x2,
sum(idhm) y, sum(idhm*idhm) y2, sum(ano*idhm) xy
from indices_municipais
where municipio=130
)
)

Eu tenho os dados de 1991 e 2001. Com essa pequena consulta, projeto a reta e descubro qual a tendência para 2010. Trocando o 2010 por 1991 e 2001 pude verificar que estava tudo certo; a consulta produziu os valores que já estavam na base. Como eu só tinha duas medidas, a reta tinha que passar exatamente por elas!

A consulta não é tão complicada e só o select mais de dentro muda, então resolvi escrever uma função parametrizada para poder repetir o experimento em outras tabelas com mais facilidade.


create or replace function regressao_linear (
valor number,
x varchar2,
y varchar2,
tabela varchar2,
restricao varchar2)
return number is
resultado number;
begin
execute immediate '
select a+'||valor||'*b from (
select (x2*y-xy*x)/(n*x2-x*x) a, (n*xy-x*y)/(n*x2-x*x) b from (
select count(1) n, sum('||x||') x, sum('||x||'*'||x||') x2,
sum('||y||') y, sum('||y||'*'||y||') y2, sum('||x||'*'||y||') xy
from '||tabela||'
where '||restricao||'
)
)
' into resultado;

return resultado;
end;


Os parâmetros x e y são os nomes das colunas; tabela não preciso explicar e restricao é tudo que vai na cláusula where. E valor é o x que se quer projetar além dos dados. A interpolação de strings no Oracle é mesmo pavorosa e essa multiplicação de x e y não ajuda. Mas agora posso usar a função e fazer de conta que é trivial; a sujeira foi toda para debaixo do tapete.

Para executar a consulta inicial usando a nova função, basta fazer o seguinte:

select regressao_linear(2010, 'ano', 'idhm',
'indices_municipais', 'municipio=130')
from dual


Esse município tem IDHs 0,746 e 0,822 para 1991 e 2001. O valor retornado pela função é 0,906. Quando sair o censo eu vou conferir e cobrar do prefeito se não estiver conforme o esperado.

quarta-feira, 11 de agosto de 2010

Truques com calculadoras

Um amigo de infância poucos dias lembrou de "quantas tardes jogando Elite nessa calculadora" passamos. A tal calculadora podia fazer, na melhor das hipóteses, uma adição de 8 bits em 2 ciclos (e em 6 na pior). Rodando a 2MHz, era possível, no máximo, 1 milhão de somas por segundo. Isto se o computador não tivesse mais nada para fazer.

Os computadores hoje estão nos gigahertz e agora os truques ficaram mais interessantes. Mesmo assim, o mecanismo básico é o mesmo: pegar um número, executar uma operação muito simples e guardar o resultado.

A primeira calculadora foi inventada por Pascal. O pai dele era coletor de impostos e o incumbiu de resolver uma papelada infindável. Para diminuir a carga e evitar que o tédio se prolongasse, ele inventou um aparelho mecânico que somava e subtraía. O primeiro processador foi inventado pela Intel para simplificar e baratear o projeto de uma calculadora e é, em essência, uma pequena calculadora programável.

Camadas e camadas de abstração mais tarde e temos comunicações, internet, som, vídeo e jogos rodando em cima de calculadoras. Calculadoras muito rápidas, mas calculadoras mesmo assim.

Por isso, não sei se o melhor nome para o curso seria Ciências da Computação. Nas outras ciências, as pessoas partem da observação da complexidade do mundo e tentam encontrar os princípios subjacentes. Na Computação, partimos de princípios básicos muito simples e rígidos e tentamos criar a maior complexidade possível. Acho que o curso deveria chamar-se Arte da Computação ou
Mágica com Números ou Truques com Calculadoras.

quinta-feira, 5 de agosto de 2010

IT nightmares

Um dos programas de TV mais divertidos dos últimos tempos é o Kitchen Nightmares, no qual o chef Gordon Ramsay visita restaurantes à beira do precipício e tenta retorná-los à lucratividade.

Invariavelmente, os problemas são causados pelo dono, pelo cozinheiro-chefe ou por ambos. Os temas mais comuns são a falta de higiene, a falta de coordenação entre os cozinheiros e pratos desnecessariamente complicados. Com freqüência, há um funcionário promissor sendo ofuscado pela teimosia dos líderes.

Eu sonho em ver um IT Nightmares! Um CIO experiente vai visitar CPDs e colocá-los no rumo. Haverá uma série de embates até o chefe convencer-se de que está complicando as coisas e impedindo sua talentosa equipe de brilhar.

O papel do cozinheiro-chefe teimoso e complicado vai ser preenchido pelo arquiteto multi-certificado e suas infinitas camadas, frameworks e arquivos de configuração.

O chef Ramsay insiste em criar pratos "simples e honestos" que, quase sempre, são mais baratos e, por isso, mais lucrativos. Pois, eu torço por soluções simples e honestas. Os sistemas não precisam resolver todos os problemas presentes e futuros; eles precisam resolver com eficiência os problemas de hoje. Quem sabe quais serão os problemas de amanhã?!

Na informática, já vi muitos finais desastrosos. Uma vez dentro de uma situação complicada, é difícil achar tempo e meios de resolvê-la com inteligência. Refletindo sobre projetos passados, pude ver claramente os erros, mas somente meses depois. Quem olha de fora, sem estresse e sem pressão, pode analisar melhor a situação.

Em alguns casos, teria sido ótimo ver o Gordon dar uns tabefes no chefe!

terça-feira, 27 de julho de 2010

Upgrade histórico

Eu queria ter instalado drives de 3.5" no meu BBC B+ há 20 anos. Infelizmente, naquela época, os drives eram caros, eu pouco sabia sobre o assunto e os drives de 5.25" ainda funcionavam bem. As circunstâncias não eram propícias.

A foto abaixo (estrelada pela linda Maria Alice) mostra a configuração campeã. É um BBC B+ com 128KB de RAM e um processador 65C12 a 2MHz. Há também um monitor com conector TTL RGB e um par de drives de 5.25" que funcionam mal.


A controladora dos drives é uma Western Digital 1770. Ela é capaz de suportar densidade simples e densidade dupla, mas o sistema operacional só suporta densidade simples (256 bytes por setor). Os drives suportam as duas densidades e 40 ou 80 trilhas. Então, num único disquete de 5.25", gravam-se 400KB (80 trilhas x 10 setores x 256 bytes x 2 lados). Num drive de 3.5" moderno pode-se usar o mesmo formato; o computador não tem idéia do que está ligado nele.

No fim do ano passado, eu já tinha descoberto todas as questões a resolver para ligar um drive novo e as anotei num artigo. Naquele momento, eu já havia conectado um drive e gravado com sucesso um "Hello, World!" escrito em BASIC. No entanto, eu só consegui que o drive se comportasse como o segundo. Para conectar o drive como o primeiro, foi preciso adaptar um cabo.

Os drives modernos são todos configurados para serem o drive B. E para economizar uns centavos, eles nem têm um mecanismo de reconfiguração. Afinal, eles estão custando R$15 agora! No PC, as linhas 12 e 14 acionam, respectivamente, os drives B e A; as linhas 16 e 10 acionam o motor, conforme o drive selecionado. No padrão Shugart, que o BBC usa, as linhas 10, 12 e 14 acionam os drives 0, 1 e 2. E apenas a linha 16 aciona o motor. O BBC só usa os drives 0 e 1.

A tabela abaixo mostra as diferenças:

LinhaPCShugart
10Motor drive A:Aciona drive 0
12Aciona drive B:Aciona drive 1
14Aciona drive A:Aciona drive 2
16Motor drive B:Aciona motor

Os cabos novos têm uma voltinha para que quando o PC envie sinal para o drive A, ele o receba mesmo estando configurado como B. Para ligar o BBC, basta inverter as linhas 10 e 12. Ou seja, para acionar o drive 1, o BBC liga as linhas 12 e 16, exatamente como o PC faz para o drive B. Para acionar o drive 0, é preciso fazer o sinal da linha 10 chegar na linha 12. É simples, mas fabricar o cabo é difícil, porque as linhas são pequenas e os conectores menores ainda. A foto abaixo mostra os dois tipos de cabo (o da direita é o comum de PC).

Não dá para disfarçar o caráter artesanal do cabo! Eu cortei a ponta de um cabo normal, desfiz a volta e troquei as linhas 10 e 12. Bastam um estilete e muita paciência.

Na primeira tentativa, o BBC lia os disquetes, mas não gravava. A linha de proteção de gravação (linha 28 ou /WPT para os íntimos) provavelmente estava encostada numa vizinha. Na segunda tentativa, funcionou, mas o cabo não ficou firme e acabou se desmanchando. Na terceira, sucesso! O disquete gravado há 7 meses também funcionou muito bem. Como os disquetes modernos são fabricados para gravar em densidade alta, tive medo de que não funcionariam bem com densidade simples. A foto abaixo mostra que é preciso tapar o buraco oposto ao de proteção de gravação para enganar o drive a pensar que trata-se de um disquete de densidade simples ou dupla (repare no da direita; o furo está tapado com fita isolante):

As fotos a seguir mostram a instalação em funcionamento.


Na tela está o conteúdo do disquete: um arquivo BASIC chamado HELLO e um arquivo texto chamado TEXT.

No canto, vê-se o terminador da fonte que seria ligado ao computador. Sem aquele cabinho vermelho, a fonte não liga.

O próximo passo é escrever um programa para ler disquetes de PC. Com um pouco de código de máquina é possível ler disquetes com FAT de 360KB ou 720KB. E não estou exagerando, porque o sistema de disco (DFS ou Disc Filing System) cabe todo numa ROM de 16KB.

sexta-feira, 23 de julho de 2010

Hierarquias legíveis no SQL

A maneira mais comum de modelar hierarquias em tabelas é usar uma coluna para referenciar a própria tabela. Isso funciona muito bem no Oracle, porque a sintaxe tem umas facilidades para tratar esse tipo de construção. No entanto, não creio que seja a melhor maneira de resolver esse problema.

Joe Celko difundiu largamente a solução dos conjuntos aninhados. Eles já são um grande avanço, mas ainda não resolvem o problema que eu quero resolver: tornar os dados mais legíveis, sem precisar usar consultas complicadas. Eu queria executar um simples select com order by e pronto.

Uma maneira simples de fazer isso é colocar a hierarquia num texto usando uma notação posicional. Por exemplo, para representar um nodo "3.2.4" usa-se um texto "030204". O pai desse nodo (3.2) seria o "0302". Usando apenas números, tem-se 99 elementos por nível. Com o alfabeto, pode-se mais. Para todas as aplicações que vi até hoje, basta.

Criei uma tabela de testes assim (a chave é a coluna POSICAO):

Posição (POSICAO)Nome (NOME)
011
01011.1
01021.2
0101011.1.1
022
02012.1
02022.2
0202012.2.1

Para enumerar a hierarquia toda de uma única vez, basta a seguinte consulta:

select posicao, nome
from hierarquia
order by 1

Dá até mesmo para usar a sintaxe especial do Oracle e com isso, achatar a hierarquia e mostrar em cada registro todos os ascendentes:

select posicao, SYS_CONNECT_BY_PATH(nome,'>') nodo
from hierarquia
connect by prior posicao = substr(posicao,1,length(posicao)-2)
start with length(posicao)=2
order siblings by posicao

Isso vai mostrar algo assim:

POSICAONODO
01>1
0101>1>1.1
010101>1>1.1>1.1.1
0102>1>1.2
02>2
0201>2>2.1
0202>2>2.2
020201>2>2.2>2.2.1

Não é preciso completar os espaços vazios com zeros; isso dificulta muito as consultas. Por exemplo, na primeira tentativa, usei "010000" para o nível 1. Os zeros adicionais não só não ajudam, como tornam a consulta anterior muito mais complicada; sem eles, basta tirar dois caracteres do fim para achar o nodo pai; com eles, é preciso encontrar onde os pares de zeros terminam (ou começam, para quem vem da esquerda).

Se for preciso representar mais nodos por nível, basta usar mais posições. Com 3 por nível, já são possíveis 999 nodos. E para tornar a hierarquia mais alta, basta ir adicionando caracteres .

Esse tipo de construção, além de tornar os dados mais legíveis, facilita sobremaneira algumas pesquisas. Por exemplo, encontrar todos os nodos de terceiro nível com uma tabela auto-referenciada seria muito mais complicado.

segunda-feira, 19 de julho de 2010

Império dos 8 bits III

Finalmente, o Império chega ao Oriente. Essa região não produziu muitos modelos, mas produziu o melhor micro de 8 bits.

Apenas 5 países dessa região produziram computadores de 8 bits: Austrália, Coréia do Sul, Japão e Nova Zelândia. Hong Kong projetou um e o produziu noutros países.

Na Austrália, a Microbee produziu uma série de computadores baseada no Z80. O primeiro modelo, lançado em 1982, tinha 16KB ou 32KB de RAM, com 16KB de ROM contendo o BASIC e mais 12KB para funções opcionais (como editor de textos ou software de comunicação). Ele rodava a 3,375MHz e tinha a capacidade de exibir gráficos de até 512x256 pixeis (com duas cores), o que era impressionante para a época. Os modelos subsequentes foram adicionando memória (56KB, 64KB e 128KB) e usavam o CP/M, baseado em disco.

Também foi muito popular o VTech VZ200, baseado no Z80. Era muito mais simples, mas também era muito barato. O projeto era de Hong Kong, mas era produzido e vendido na Austrália e Nova Zelândia pela Dick Smith.

Na Nova Zelândia, o Poly 1 foi produzido de 1981 a 1989. Ele usava o 6809 e era extremamente caro. É provável que tenha sido o primeiro micro com função de rede.

Na Coréia do Sul, a Samsung projetou o SPC-1000. Ele foi fabricado, no entanto, no Japão. Era mais um micro baseado no Z80 e se não fosse o único projetado pela Coréia, nem valeria a pena ser mencionado; ele não tinha nenhuma característica marcante.

Por fim, o Japão. Deste país saiu o melhor micro de 8 bits. A Fujitsu lançou várias versões do FM-7 entre 1982 e 1988. Estranhamente, esse micro teve pouco sucesso fora do Japão. Os MSX, que eram tecnologicamente menos desenvolvidos, tiveram muito mais sucesso.

O primeiro modelo do FM-7 já tinha 64KB de RAM em 1982! No ano anterior, o ZX81 tinha feito enorme sucesso na Inglaterra com apenas 1KB de RAM. O modelo anterior (FM-8; não tente compreender a numeração dos modelos) já tinha 64KB em 1981. O último modelo (o FM77AV40SX) tinha 192KB de RAM (que podiam ser expandidos até 448KB) e ainda mais 192KB de VRAM. As capacidades gráficas também eram impressionantes. O FM-7 sustentava 640x200 pixeis em 8 cores; o último modelo, até 640x400 em 8 cores de uma paleta de 4096 ou 320x200 com 4096 cores. Ademais, era possível ter várias páginas de gráficos. Isto é, era possível ter em memória 2, 4, 6 ou 8 telas simultaneamente, conforme o modo gráfico.

Os Fujistu usavam dois processadores 68B09 (uma variação do 6809), sendo o segundo encarregado dos gráficos e do I/O. O 6809 foi o melhor processador de 8 bits. Ele podia endereçar até 1MB de RAM, em páginas de 4KB. Seus modelos rodavam a 1Mhz (6809), 1,5Mhz (68A09) e 2Mhz (68B09). Mesmo sendo mais lento que o Z80, ele (e seu primo 6502) eram muito mais rápidos ao acessar a memória e por isso pareciam produzir sistemas muito mais velozes, como se podia notar ao comparar o jogo Elite num Z80 de 3.58MHz com o mesmo jogo num 6502 de 2Mhz.

quinta-feira, 15 de julho de 2010

O vendedor de tapetes

Musad Algibar ganhou muito dinheiro vendendo tapetes e criando camelos. Tanto dinheiro que um dia decidiu tirar umas férias e finalmente conhecer o Índico.

Chegando a Dar Es Salaam, decidiu passear pelo comércio local e acabou encontrando uma pequena livraria. Era uma só desordem e um livro de camelos acabou entre os romances traduzidos de Saramago. A capa era bonita e o preço baixo, então ele o levou, mesmo não sabendo do que se tratava e duvidando que os ingleses soubessem algo sobre camelos. O pequeno Arif já sabia inglês e traduziria!

O livro não era sobre camelos, evidentemente, mas era deveras útil. Com pouco esfoço, Arif começou a criar dezenas de desenhos novos para tapetes com motivos matemáticos no computador de casa. O primeiro veio da simples x2+y2:



Será que a subtração produziria algo menos curvilíneo (para não atormentar os tecelões)?





Tentanto maiores potências, Arif descobriu que x4+y4 produz algo bastante tradicional, mas x5+y5 produz um desenho realmente moderno!





Finalmente, ao tentar sin(x)5+cos(x)5, um projeto surpreendente surgiu. Um psicoteste!




Que mensagem estará Alá a nos passar?

terça-feira, 13 de julho de 2010

Visões do camelo

O Perl não é conhecido por suas habilidades gráficas, mas com algumas bibliotecas é possível gerar imagens interessantes com muita facilidade.

O mais fácil é usar o módulo GD, conforme o exemplo abaixo:

use GD;

my $img=new GD::Image(256,256);

my @colours=();

for my $c (0..255) {
$colours[255-$c]=$img->colorAllocate($c, $c, $c);
}

for my $i (0..255) {
for my $j (0..255) {
$img->setPixel($i, $j, $colours[($i*$j)%255]);
}
}

binmode STDOUT;
print $img->png;

Esse pequeno programa faz o seguinte:
  1. Instancia uma imagem com 256 pontos de cada lado;
  2. Cria uma paleta de cinzas (sendo 0 branco e 255 preto);
  3. Atribui a cada ponto da imagem uma a cor dada pelo produto das coordenadas;
  4. Escreve a imagem para o stdout.
Porque a imagem é escrita para o stdout, é preciso usar um pipe ao rodar o programa:

imagem.pl > img.png


O resultado parece algo vindo de uma ruina Maia e também não ficaria mal num museu de arte moderna:


Trocando os números inteiros por primos (no eixo horizontal; o eixo vertical continua com os inteiros), o resultado ganha bastante ruído, mas continua interessante.

Finalmente, coloquei em cada ponto (i,j) o resto da divisão do i-ésimo primo pelo j-ésimo primo. Surgiu um deserto misterioso. Não é por nada que os primos deixam os matemáticos perdidos!

domingo, 11 de julho de 2010

Marxismo no TI

Há uma tentativa recorrente de implementar uma espécie de comunismo tecnológico nos departamentos de TI. Muitas vezes ouvi dizer que seria melhor ter todos os sistemas implementados numa única linguaguem ou tecnologia.

Pois, eu digo que compreenderam mal o marxismo! Karl Marx disse:

De cada um, de acordo com suas habilidades,
a cada um, de acordo com suas necessidades.


Portanto, só posso concluir que um verdadeiro socialista deve procurar a ferramenta certa para cada trabalho. Ademais, não deve perder de vista o momento histórico; as escolhas do passado devem ser vistas à luz das circunstâncias.

quarta-feira, 7 de julho de 2010

Zips exóticos em Java

A API do Java é enorme, mas ainda deixa alguns pontos importantes abertos. Apesar de ter facilidades para ler e escrever arquivos compactados, não suporta zips encriptados ou multi-volumes.

Por sorte, existe uma API para Java sobre o 7-zip. É preciso usar a API e as bibliotecas originais, então existe uma biblioteca para o Linux e outra para o Windows. Pode ser um pouco trabalhoso para quem desenvolve no Windows e usa o Linux para produção, como muitas vezes acontece.

Pois bem, a API é deveras estranha, então o melhor é conseguir um exemplo e reaproveitá-lo.

RandomAccessFile file=new RandomAccessFile(zip, "r");
ISevenZipInArchive zip=SevenZip.openInArchive(null,
new RandomAccessFileInStream(file));
ISimpleInArchive ui=zip.getSimpleInterface();
for(int i=0; i<zip.getNumberOfItems(); i++) {
final ByteArrayOutputStream stream=new ByteArrayOutputStream();
String nome=(String) zip.getProperty(i, PropID.PATH);
ISimpleInArchiveItem item=ui.getArchiveItem(i);
ExtractOperationResult result=item.extractSlow(new ISequentialOutStream() {
public int write(byte[] data) throws SevenZipException {
try {
stream.write(data);
} catch(Exception e) {

}
return data.length;
}
}, "senhasecreta");

O arquivo sendo analisado é o zip (uma referência do tipo File). Para cada arquivo dentro de zip, obtem-se um item (do tipo ISimpleInArchiveItem) e descompactam-se os dados originais usando o método extractSlow(). Esse método recebe uma instância de ISequentialOutStream que neste caso copia os dados para uma instância de ByteArrayOutputStream. O último parâmetro de extractSlow() é a senha, caso os dados estejam protegidos.

É muito útil, mas espero que a JDK logo ganhe novos métodos para suportar encriptação e multi-volumes. E com uma API mais elegante que esta!

quarta-feira, 30 de junho de 2010

Dia da apreciação

O ramo de trabalho mais democrático que conheço é o do varejo. Não é preciso ter grandes habilidades para conseguir um emprego num supermercado e as oportunidades são enormes. É possível começar limpando o chão e dez anos mais tarde estar gerindo uma loja com dezenas de funcionários.

Por isso, não me surpreendeu que ocorra no varejo uma prática muito interessante: o dia da apreciação. Nesse dia, as pessoas que trabalham no escritório vão para a loja e fazem o trabalho pesado do dia-a-dia. Nesse dia, a pessoa que achava que o trabalho de recepção de mercadorias era fácil descobre que é um trabalho sujo, pesado e muito cansativo. Nesse dia, as pessoas do escritório, que podem passar dias comunicando-se apenas por email, descobrem que é muito difícil interagir com dezenas de clientes todos os dias, mesmo que se esteja apenas organizando frutas.

Tenho visto alguns casos de falta de apreciação por parte de usuários nada compreensivos, assim como tenho tido dificuldade para compreender por que os usuários fazem certas coisas. Então, creio que seria no benefício de todos os setores de TI ter um dia da apreciação. E ele pode ser dividido em dois. No primeiro, os analistas e programadores visitam seus clientes: os usuários. No segundo, os usuários visitam o setor de TI e lhes é explicado tudo o que envolve criar e administrar os sistemas de apoio ao negócio.

Os usuários não vão entender os detalhes, mas isso não importa. Basta que tenham uma visão geral sobre como as coisas estão interligadas e sobre como há milhares de pequenos detalhes que interferem no cotidiano.

Por outro lado, os trabalhadores de TI vão entender melhor o que os clientes precisam. Analistas trabalham bastante com seus usuários, mas dificilmente ocupam seus postos e, por isso, estão limitados à capacidade de comunicação e síntese dos clientes.

Não pode haver forma mais simples e barata de melhorar a compreensão do negócio e de integrar as pessoas dentro de uma empresa.

quarta-feira, 23 de junho de 2010

Estamos nos libertando do hábito que tínhamos de explicar tudo

Na fria manhã de segunda-feira passei por um tapume que havia sido decorado com as palavras do título. Não costumo dar atenção a essas filosofias pontuais, mas tomei gosto por essa frase.

Deve ser por causa dos livros que tenho lido: Incompletude (sobre o trabalho de Gödel) e Metamat (de Chaitin). E hoje é o nonagésimo oitavo aniversário de Turing.

O autor dessa intervenção urbana deve ser um matemático rebelde. Na próxima, sugiro que ele escreva o seguinte:

Certas coisas nunca saberemos

segunda-feira, 14 de junho de 2010

A lei de Benford

Muitos conjuntos de dados apresentam uma propriedade interessante: os primeiros dígitos não aparecem todos com a mesma freqüência.

A lei de Benford prevê que os dígitos mais baixos apareçam com maior freqüência.

Esta lei funciona especialmente bem com números que crescem exponencialmente como preços e salários. O crescimento exponencial torna os dígitos mais altos mais raros, porque eles somem rapidamente. Se um produto tem um preço inicial 100, ele vai ficar valendo entre 100 e 199 o dobro do tempo que ficará valendo entre 200 e 299. Isto porque a inflação e os juros são cumulativos e, portanto, crescem exponencialmente.

Então, armado com este novo conhecimento, decidi avaliar minha base de preços.

select
substr(preco, 1, 1),
trunc((count(1)/29528)*100,2)
from produtos
group by substr(preco, 1, 1)
order by 2 desc

Em primeiro lugar, contei os registros para simplificar a consulta e fazer o cálculo mais rapidamente. Já dá para deduzir que tenho 29.528 registros. Os dados são do mundo real; eu não gerei números aleatoriamente.

A tabela abaixo mostra a distribuição dos primeiros dígitos:

DígitoFFe
133,65%30,1%
217,49%17,6%
312,93%12,5%
49,49%9,7%
58,71%7,9%
66,26%6,7%
74,83%5,8%
83,9%5,1%
92,7%4,6%

F é a freqüência encontrada e Fe é a freqüência esperada. Como se pode ver, a previsão chegou muito perto da realidade.

Essa lei, além de ser curiosa, é útil para apontar dados problemáticos na contabilidade forense. Distribuições muito estranhas podem colocar em evidência tentativas de esconder maracutaias financeiras.

quinta-feira, 10 de junho de 2010

Hey Hey 16K

Já vi algumas músicas com referências a tecnologia, mas este é o primeiro que vejo dedicado aos micros de 8 bits e com BASIC na letra.


É um Flash de uns 2MB, então carrega rápido. Para ajudar a cantar, ele já vai mostrando a letra.

Divirta-se!

sexta-feira, 4 de junho de 2010

O coeficiente de Gini em SQL

O coeficiente de Gini é uma medida de desigualdade usado na economia para avaliar a distribuição de renda de diferentes sociedades.

Ele é um número entre 0 e 1, sendo 1 a desigualdade completa (uma única pessoa tem toda a riqueza) e 0 a igualdade total (todos ganham a mesma coisa). O número também é interpretado como a metade da diferença média normalizada. Ou seja, se a média é R$1.000 e o coeficiente de Gini for 0,6, então a diferença média entre rendas vai ser R$1.000*0,6*2=R$1.200. Ou seja, neste caso, a diferença média é maior que a média; esta seria uma sociedade bastante desigual. Países considerados avançados, como os da Escandinávia têm coeficientes entre 0,2 e 0,3. Países como Brasil, Uruguai, Estados Unidos e China, têm coeficientes entre 0,4 e 0,5. Atualmente, o país mais desigual do mundo é a Namíbia, com 0,707.

Na falta de dados sobre renda, usei uma lista de produtos. Mesmo assim, vou supor estar usando uma tabela PESSOAS com as seguintes colunas:
  • ID - um identificador único para casa pessoa;
  • SALARIO - o salário.

Usando a definição supracitada, escrevi o seguinte no Oracle:

select (diff/media)/2 from (
select avg(abs(p1.salario-p2.salario)) diff
from pessoas p1, pessoas p2
where p1.id<>p2.id
),(
select avg(salario) media
from pessoas
)

Temos dois cross-joins. O primeiro é terrível, porque multiplica todos os registros da tabela por ela mesma para calular as diferenças. Como eu tinha 14.354 registros, o cross-join resultante disso tinha 206.022.962 linhas. Nada bom. Se eu quisesse calcular o coeficiente de Gini do Brasil, teria que usar uma tabela de uns 190 milhões de registros e isso não ia funcionar. O segundo é inócuo, porque o último select produz apenas uma linha.

Por sorte, um economista chamado Angus Deanton encontrou uma maneira melhor de calcular isto. Na fórmula abaixo, N é o número total de indivíduos, u é a média dos rendimentos, X é o rendimento de cada um e P é a classificação de cada um (sendo 1 o mais rico e N o mais pobre).


Isso é muito mais eficiente, porque agora não é mais preciso comparar cada um a todos os demais.

A consulta resultante usa a função analítica ROW_NUMBER() para classificar os salários:

select ((n+1)/(n-1))-(2/(n*(n-1)*media))*sum(pc*salario) from (
select salario, row_number() over (order by salario desc) pc
from pessoas
), (
select avg(salario) media, count(1) n
from pessoas
) group by n, media

A tabela abaixo compara os resultados:

SELECTTempoResultado
167,94s0,55617007994480478319584630635542608397
20,03s0,5561700799448047831958463063554260839701

O segundo, além de ser 2.264 vezes mais rápido, ainda produziu mais duas casas de precisão que, por outro lado, não têm nenhuma utilidade.

segunda-feira, 31 de maio de 2010

O oráculo e o camelo

Tive que carregar uma base de dados com o conteúdo de dois DVDs cheios de informações. Os dados estavam separados em arquivos texto que por sua vez estavam dentro vários níveis de pastas.

A primeira tentativa foi usar um script em Perl. A solução funcionou, mas não foi rápida o suficiente para o curto espaço de tempo disponível (como sempre, o prazo era exíguo). Rápido mesmo foi usar o SQL Loader.

Mesmo assim, vou descrever a solução em Perl. Ela não é tão rápida, mas ela é mais flexível.

Em primeiro lugar, usei o módulo Find para buscar os arquivos. O módulo é dos básicos, então sempre está disponível.

Uma linha basta para disparar uma busca em uma hierarquia de pastas:

find(\&process_file, $ARGV[0]);

Essa linha dispara a busca com o valor do primeiro argumento dado ao script. Esse argumento deve conter o caminho de uma pasta. Para cada arquivo que o find() encontrar, ele invocará a função process_file().

Antes de apresentar a process_file(), vou mostrar a estrutura de dados que faz o relacionamento entre os nomes dos arquivos, seus formatos e os respectivos inserts.

my $tables={
'PRODUTOS' => {
format => 'A4A20A10',
insert => q{insert into produtos (codigo, descricao, valor)
values (?,?,?/100)}
},
'PESSOAS' => {
format => 'A11A60A8',
insert => q{insert into pessoas (cpf, nome, data_nascimento)
values (?,?,to_date(?, 'DDMMYYYY'))}
}
};

A referência $table aponta para um hash; cada chave do hash representa uma tabela cujo nome coincide com o nome do arquivo; associado a cada nome de tabela está um formato e um insert. Uso referências como poderia usar hashes diretamente.

A função process_file(), então, determina se existe um mapeamento para cada arquivo encontrado e invoca a função load() quando existir.

sub process_file {
my $filename=$File::Find::name;
my $name=$_;

if($name=~/(\w+)\.txt/) {
if(exists $tables->{$1}) {
load($filename, $1);
}
}
}

Os nomes dos arquivos têm os mesmos nomes das tabelas e o sufixo "txt". Usando uma expressão regular, determino se o arquivo tem o sufixo desejado e uso a primeira parte do nome ($1) para encontrar o mapeamento.

Para extrair os dados dos arquivos, uso a função unpack(). Esta função recebe um string que descreve uma linha e a linha propriamente dita. Usando a descrição, ela divide a linha e produz um array com os campos. Então, para percorrer um arquivo texto com 3 colunas de 10 caracteres cada, bastaria fazer o seguinte:

while(<FILE>) {
my @cols=unpack("A10A10A10");
}

O segundo parâmetro é implícito (<FILE> coloca a linha lida em $_ e unpack() o usa como parâmetro implícito).

Para gravar os dados, uso o DBI, que é o módulo de acesso a bases de dados. O exemplo abaixo mostra como abrir uma conexão, preparar um comando e executá-lo.

$db = DBI->connect( "dbi:Oracle:host=HOST;sid=SID", "SCHEMA", "PASSSWD" )
|| die( $DBI::errstr . "\n" );
$st=$db->prepare('INSERT INTO PRODUTOS (CODIGO, NOME, VALOR) VALUES(?,?,?)');
$st->execute('0123', 'BOLA', 49.95);

Juntando o unpack() com o DBI, o resultado é:

sub load {
my ($filename, $table)=@_;
my $format=$tables->{$table}->{format};
my $insert=$tables->{$table}->{insert};

open(my $fh, "<$filename");
my $st=$db->prepare($insert);
while(<$fh>) {
my @row=unpack($format, $_);
$st->execute(@row);
}
$db->commit();
close($fh);
}

A função unpack() produz um array e o método execute() recebe um array. Não podia ser mais fácil. Executo um commit() por arquivo, mas poderia muito bem executar um único commit no fim de tudo.

E, finalmente, juntando todas as partes tem-se:

#!/usr/bin/perl
use DBI;
use File::Find;

my $db = DBI->connect( "dbi:Oracle:host=HOST;sid=SID", "SCHEMA", "PASSSWD" )
|| die( $DBI::errstr . "\n" );
$db->{AutoCommit} = 0;
$db->{RaiseError} = 1;

my $tables={
'PRODUTOS' => {
format => 'A4A20A10',
insert => q{insert into produtos (codigo, descricao, valor)
values (?,?,?/100)}
},
'PESSOAS' => {
format => 'A11A60A8',
insert => q{insert into pessoas (cpf, nome, data_nascimento)
values (?,?,to_date(?, 'DDMMYYYY'))}
}
};

sub load {
my ($filename, $table)=@_;
my $format=$tables->{$table}->{format};
my $insert=$tables->{$table}->{insert};

open(my $fh, "<$filename");
my $st=$db->prepare($insert);
while(<$fh>) {
my @row=unpack($format, $_);
$st->execute(@row);
}
$db->commit();
close($fh);
}

sub process_file {
my $filename=$File::Find::name;
my $name=$_;

if($name=~/(\w+)\.txt/) {
if(exists $tables->{$1}) {
load($filename, $1);
}
}
}

find(\&process_file, $ARGV[0]);