terça-feira, 20 de novembro de 2012

SQL Gaussiano

É bem conhecida a história de quando o professor de matemática de Gauss pediu que a turma somasse todos os números até 100. O mestre esperava ter um pouco de paz enquanto as crianças trabalhassem na soma. O pequeno Gauss, no entanto, resolveu a questão rapidamente.

A solução era simples. Ele somou 1 com 100, depois 2  com 99, então 3 com 98 e assim por diante. Vindo pelas pontas, a soma é sempre 101 e depois de 50 somas, os números encontram-se em 50 e 51. O total, portanto, é 50 vezes 101.

Esse pequeno truque pode ser usado no SQL para encontrar sequências de números ou de datas. Considere os seguintes números: 1; 2; 3; 5; 6; 8; 9; e 10. Se cada um estiver numa linha de uma tabela, eu quero uma consulta que gere o seguinte resultado:

   1   3
   5   6
   8 10

É necessária uma sequência decrescente para fazer o jogo com a nossa sequência crescente. Isso podemos resolver facilmente com funções analíticas. Na consulta abaixo, já somo cada número ao seu equivalente.


select n, n+row_number() over (order by n desc)
from numeros
order by n


Isso vai produzir as seguintes linhas:

19
29
39
510
610
811
911
1011

E todos os números consecutivos compartilham o mesmo valor na segunda coluna. O que nos resta é agrupá-los e encontrar o máximo e mínimo de cada grupo. Assim, chegamos ao nosso objetivo:


select min(n), max(n) from (
  select n, n+row_number() over (order by n desc) grupo
  from numeros
)
group by grupo
order by 1


O resultado é, conforme esperado, este:

   1   3
   5   6
   8 10

O mesmo pode ser feito com datas, basta tratar cada uma como o número de dias a partir da primeira data.

terça-feira, 13 de novembro de 2012

Nau em chamas

Quase sem querer, encontrei um fractal curioso chamado Burning Ship. Ele é uma variação do conjunto de Mandelbrot; a única diferença é que, a cada iteração, são aplicados os valores absolutos dos componentes imaginário e real.

Aproveitei o código de aventuras anteriores. Com o Javascript abaixo é possível analisar este fractal; basta clicar sobre a imagem para inspecionar regiões cada vez menores.

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

function mandel(x,y) {
  var c=1;
  var r=0;
  var i=0;
  var MAXITER=512;
  while(r*r+i*i<4 && c<MAXITER) {
    var t=2*Math.atan2(i,r);
    var d=Math.pow(Math.sqrt(r*r+i*i),2);
    var nr=d*Math.cos(t)+x;
    var ni=d*Math.sin(t)+y;
    r=Math.abs(nr);//Aqui ocorre
    i=Math.abs(ni);//a mágica
    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>

Como se espera de um fractal, o tema torna a surgir com formas e cores ligeiramente diferentes.

segunda-feira, 12 de novembro de 2012

O iPad mini e o novo consumidor de TI

Observando alguns comentários e algumas avaliações do novo iPad mini eu percebi um mudança radical em relação aos consumidores de micros de 8 bits na década de 1980. O novo comprador de TI quer a perfeição e não se importa em gastar muito dinheiro para ter o melhor para cada situação.

O novo iPad mini, dizem, é muito melhor para carregar pela rua e ler no ônibus. Os modelos anteriores são grandes e pesados. Mas há pouco tempo tudo era perfeito; surge agora um novo modelo mais perfeito ainda.

O comprador de micros de 8 bits era um sonhador. Ele tinha que enxergar muito longe para ver algum potencial naquelas máquinas lentas e com pouquíssima memória. Os jogos exigiam muita criatividade tanto dos programadores como dos jogadores.

Junto com essa mudança nas expectativas, sumiram aquelas revistas legais para amadores, a Byte sendo a mais interessante (embora um pouco mais técnica que as outras). Hoje em dia temos revistas para profissionais (sobre Java, SQL, e outros assuntos bem específicos) e algumas revistas de consumidor com avaliações superficiais (como a Info Exame).

Eu sinto falta daquelas revistas, porque sempre abriam portas para áreas novas: gerar fractais, simular colisões de galáxias, fazer música. Parece que agora que o micro é poderoso, as pessoas não se preocupam em explorá-lo.

Torço que com o lançamento dessas plataformas pequenas, como o Raspberry Pi, voltem a surgir publicações para amadores inquisitivos. Enquanto isso, vou vasculhando as revistas antigas por projetos interessantes.

terça-feira, 23 de outubro de 2012

Contando linhas com Perl

Eu queria contar as linhas de um arquivo de uns 4GB no Windows. No Linux, o wc resolveria a questão com sobra. Eu tentei usar o GNU Coreutils, mas não achei uma versão binária para o Sistema Operacional de Redmond.

Então, recorri ao camelo e com uma linha de Perl, resolvi o problema:

C:\perl -pe "}{$_=$." meu_arquivo_grande.txt
8738433

Não podia ser mais simples e claro.

quarta-feira, 17 de outubro de 2012

Relatórios no Oracle

Considere uma tabela PESSOAS com estas colunas:

  (NASC DATE, SEXO VARCHAR2(1), NOME VARCHAR2(100))

A coluna NASC guarda a data de nascimento e a coluna SEXO pode conter 'M' ou 'F'. Se eu quiser montar um relatório com o número de aniversários para cada dia do ano, vou ter que fazer uns contorcionismos, mas se eu quiser, ademais, separar o número de aniversários por sexo, a consulta vai ficar ilegível.

Escondido na documentação da Oracle estava um tipo de join que permite resolver esse problema com relativa facilidade: o partitioned outer join. Esse join tem a seguinte forma:

select *
from a 
     left outer join b partition by (cz) on a.ca=b.ca

E isso significa que será executado um cross-join dos valores distintos de CZ com as linhas da tabela A e, a seguir, o resultado desse cross-join será usado no outer-join com a tabela B.

De volta ao problema original, inicio com uma contagem dos aniversários por dia e por sexo:

  select
    to_char(nasc,'MMDD') nasc, 
    sexo, 
    count(1) total
  from pessoas
  group by to_char(nasc,'MMDD'), sexo

Além disso, preciso de uma tabela com todos os dias do ano:

  select to_char(trunc(sysdate, 'YYYY')+rownum-1, 'MMDD') dia
  from all_tables
  where rownum<367

E junto tudo com o partitioned outer join:

select dia, sexo, nvl(total, 0)
from (
  select to_char(trunc(sysdate, 'YYYY')+rownum-1, 'MMDD') dia
  from all_tables
  where rownum<367
) dias
  left outer join (
    select to_char(nasc,'MMDD') nasc, sexo, count(1) total
    from pessoas
    group by to_char(nasc,'MMDD'), sexo) p partition by (sexo) on p.nasc=dia
order by dia, sexo

Essa consulta produz duas linhas para cada dia do ano: uma para o número de aniversários de homens e outro para o número de aniversários de mulheres:

0101F2
0101M0
0102F1
0102M3

Armado desta nova ferramenta, decidi resolver um problema ainda maior: enumerar os aniversariantes de cada dia. Comecei com uma lista dos aniversariantes para cada dia:

  select nasc, sexo, substr(SYS_CONNECT_BY_PATH(nome,', '),3) nomes  
  from (
    select 
      nasc, nome, sexo,
      row_number() over (partition by nasc, sexo order by nome) ordem,
      row_number() over (partition by nasc, sexo order by nome desc) medro 
    from (
      select to_char(nasc, 'MMDD') nasc, sexo, nome from pessoas
    ) 
  )
  where medro=1
  start with ordem=1
  connect by prior nasc=nasc and prior sexo=sexo and prior ordem=ordem-1

Juntando com a lista dos dias do ano, descubro o seguinte:

select dia, sexo, nvl(nomes,'-') nomes
from (
  select to_char(trunc(sysdate, 'YYYY')+rownum-1, 'MMDD') dia
  from all_tables
  where rownum<367
) dias
  left outer join (
    select nasc, sexo, substr(SYS_CONNECT_BY_PATH(nome,', '),3) nomes  
    from (
      select 
        nasc, nome, sexo,
        row_number() over (partition by nasc, sexo order by nome) ordem,
        row_number() over (partition by nasc, sexo order by nome desc) medro 
      from (
        select to_char(nasc, 'MMDD') nasc, sexo, nome from pessoas
      ) 
    )
    where medro=1
    start with ordem=1
    connect by prior nasc=nasc and prior sexo=sexo and prior ordem=ordem-1
  ) p partition by (sexo) on p.nasc=dia
order by dia, sexo

Não é das coisas mais simples, mas ele produz bastante para o seu tamanho. O resultado é parecido com:

0101FHelena, Maria
0101M-
0102FFernanda
0102MHenrique, João, Pedro

Por enquanto, apenas o Oracle oferece esse join.

quinta-feira, 11 de outubro de 2012

Dialética do software corporativo

Durante a implementação de uma série de regras de negócio ocorreu-me que seria muito mais interessante desenvolver software como uma ferramenta: o usuário que decida como usá-lo e o usuário que decida quais são as formas válidas de usá-lo.

Os usuários já se permitem pequenas transgressões como escrever nomes próprios em caixa alta e sem acentos. Na língua portuguesa, só há uma maneira de escrever João da Silva (exatamente essa). Tanto "joao da silva" como "JOAO DA SILVA" ou "XOAU d4 $y)v4" estão errados. Mas como não é prático implementar um conjunto de regras para validar os nomes próprios, essa tarefa raramente é ensaiada, embora frequentemente seja contemplada (principalmente quando alguém quer um relatório esteticamente aceitável).

Esse objetivo pode ser abandonado porque é claramente inatingível, mas por que o software continua sendo escrito sob a ilusão de que é possível (e desejável) limitar o usuário apenas a interações válidas (conforme o arcabouço vigente no momento de sua concepção)?

No momento em que o ambiente do software muda (muitas vezes antes que ele entre em produção), as regras de negócio precisam ser reavaliadas e recodificadas.

Creio que a resposta esteja na dinâmica das empresas. A criação de regras rígidas para a operação de sistemas está assentada sobre as disputas de poder. O analista de sistemas não tenta apenas sintetizar as regras de negócio, ele também frequentemente testemunha o processo de disputa de poder no qual duas ou mais partes tentam impor suas visões de mundo não com o intuito de produzir o melhor para a corporação, mas para validar e avançar seu poder.

Pode-se até dizer que alguns softwares incorporam as contradições internas de suas respectivas corporações e que num momento de realinhamento de forças eles serão, inevitavelmente, redesenhados.

Além disso, existe a falta de confiança explícita no proletariado da empresa. Já se espera que o usuário final vá subverter as regras usando o sistema de forma errônea e que, como acontece com frequência, a culpa será atribuida ao sistema. E isso é visto como natural, mesmo quando for provado que o usuário agiu de má-fé (ninguém culpa o fabricante de martelos quando o marceneiro erra o prego e quebra algo). Por isso, o próprio proletariado abdica de assumir responsabilidade pelo negócio e o delega todo ao sistema (já tratam o trabalhador com desconfiança desde o início e ele sabe igualmente que pode tercerizar qualquer culpa ao software).

Ocorre também que um proletário iluminado tente impor novas regras como forma de abraçar o sistema, tomar posse dele e do poder que ele representa. Já testemunhei, por exemplo, uma tentativa de eliminar colunas desnecessárias de um relatório que, no entanto, eram importantes para outras pessoas. Se o relatório fosse visto apenas como uma ferramenta, as colunas extras poderiam muito bem ser ignoradas, mas a posse do relatório servia também como delimitação de território. Evidentemente, esse tipo de atitude representa uma traição do trabalhador à sua própria classe, numa tentativa de emular a classe dominante e, quem sabe, um dia ser aceito nela.

As metodologias de gerência de projetos apontam como importante a definição dos stakeholders, porque as empresas de software já identificaram o problema dos conflitos internos e de como eles atrapalham o processo de análise. Para a fábrica de software, não é realmente importante qual regra de negócio será implementada, desde que algum representante do cliente aceite a responsabilidade de assinar o cheque.

O que precisamos agora é introduzir o marxismo nos cursos de informática.

terça-feira, 25 de setembro de 2012

Os números da Revolução Farroupilha

As minhas aulas de história no primário e no secundário sofriam de uma falta grave: a falta de números. Sem agregar dados aos acontecimentos históricos, as coisas ficam muito nebulosas.

Já que estamos num setembro mui chuvoso e a gauchada está mais faceira que sapo em banhado, resolvi apontar uns números da Revolução Farroupilha.

Em 1835, a população do estado era de apenas 170 mil. É menos de um paisano por quilômetro quadrado (0,6 para ser mais preciso). Porto Alegre tinha 12 mil habitantes. Imagino que muita gente não tenha tido notícia dos acontecimentos.

Morreram cerca de 3.400 combatentes nos quase 10 anos de luta (em média, menos de 1 por dia). A taxa de mortalidade era de cerca de 30 por mil. Logo, seriam esperadas 5.100 mortes por ano; a guerra, portanto, adicionou cerca de 6% à taxa de mortalidade.

Lutaram mais de 100.000 soldados (pelo menos 40 mil farrapos e pelo menos 60 mil imperiais). Portanto, a taxa de mortalidade da guerra era pouco mais que um décimo da taxa da população em geral. É provável que tenha sido mais seguro lutar que ficar em casa comendo churrasco.

Claro, a taxa de mortalidade era impulsionada muito mais por crianças e por idosos que por homens em idade para lutar, mas as proporções são curiosas e um pouco cômicas (os falecidos talvez não enxerguem humor neste dado).

A história do Rio Grande não me comove muito, então proponho diminuir um pouco o tom ufanista do hino trocando "sirvam nossas façanhas de modelo a toda terra" por algo mais proveitoso comercialmente. Por exemplo, "sirvam nossas picanhas de modelo a toda terra".