sexta-feira, 27 de dezembro de 2013

Menos velocidade, mais rapidez

Acho muito curiosos os argumentos de que o limite de velocidade nas avenidas de Porto Alegre é baixo, porque minhas viagens para casa no fim do dia dificilmente alcançam 10km/h. Considerando a quantidade de carros nas ruas, aumentar a velocidade não iria resolver nada, portanto.

Parece-me que há, por outro lado, bons argumentos para diminuir a velocidade máxima em toda a cidade para 40km/h.

É comum reencontrarmos os apressadinhos nas sinaleiras. Eles correm para utrapassar e ganhar a corrida até o próximo sinal vermelho. O que me leva a creer que os tempos dos deslocamentos urbanos são muito mais regidos pelos tempos de parada ou lentidão que pelos tempos de velocidade.

Porto Alegre não é uma cidade muito grande. Pode-se ir da zona sul à zona norte em menos de 20km. E, supondo que não houvesse qualquer obstáculo no caminho, essa distância seria percorrida em 20 minutos a 60km/h ou em 30 minutos a 40km/h.

Quem tenta entrar numa avenida sabe que a velocidade na via mais rápida dificulta o acesso. Nas ruas de 40km/h, os rapidinhos que excedem o limite também roubam dos outros motoristas a possibilidade de fazer uma conversão em menos tempo. E ainda há o tempo de luz amarela nos semáforos, que deve ser maior para dar segurança aos cruzamentos das avenidas.

Além disso tudo, há o fato de que os atropelamentos a 60km/h (90% são fatais) são muito mais perigosos que os atropelamentos a 40km/h (50% são fatais). Havendo menos velocidade, a cidade tornar-se-ia mais segura para os ciclistas e mais gente poderia sentir-se motivada a adotar esse meio de locomoção.

Em resumo, acho que há mais vantagens de termos uma velocidade máxima menor do que perdemos por poder andar rápido por alguns segundos de vez em quando. Mesmo assim, não guardo muita esperança de que o motorista portoalegrense médio queira adotar uma medida racional e civilizada para melhorar o trânsito.

sexta-feira, 13 de dezembro de 2013

Até tu, Ubuntu?

Minha máquina Ubuntu tem dois monitores. Eu queria desabilitar um deles temporariamente, porque não estava funcionando. Assim como o Windows, o Ubuntu retorna à configuração anterior se uma mudança na configuração de video não for confirmada em poucos segundos (para salvar o usuário, caso a alteração tenha desligado o sinal).

Pois bem, o Ubuntu, deduzo eu, estava tentando pedir a confirmação justamente no monitor que eu estava tentando desabilitar, porque, alguns segundos depois da alteração, tudo piscava e o monitor desligado constava novamente como habilitado.

Foi um momento muito triste na história dos sistemas operacionais.

segunda-feira, 9 de dezembro de 2013

Fatorial revisitado III

Minhas tentativas anteriores de otimizar o cálculo dos fatoriais a partir de potências de números primos tinha evoluído até eu conseguir calcular o total dos expoentes de cada primo p entre potências consecutivas. Agora, consegui encontrar uma maneira de calcular todos os expoentes até uma potência qualquer.

Para um primo p, a soma dos expoentes de sua participação em cada múltiplo anterior ou igual a pn é

n+[(pn-p*n+n-1)/(p-1)]
= pn-1/p-1

Por exemplo, entre 2 e 8, temos os seguintes múltiplos de 2: 2, 4, 6, e 8. Anteriormente, teríamos que calcular os expoentes entre 2 e 4 e somá-los aos expoentes entre 4 e 8 e ainda somar os expoentes das potências (2, 4, e 8).

Agora, chegamos rapidamente ao resultado:

3+[(23-2*3+3-1)/(2-1)] = 3+[(8-6+3-1)/1] = 3+4 = 7
= 8-1/2-1
= 7

Conferindo os múltiplos de 2, confirmamos que
  • 2 = 21
  • 4 = 22
  • 6 = 21*3
  • 8 = 23
E assim, percebemos que a soma dos expoentes é, de fato, 7.

Alterei um pouco o código para exibir as respectivas fatorações.


import java.math.BigInteger;

public class Factorial {

  private static int in(int p, int i) {
    int n=0;
    while(i%p==0) {
      n++;
      i=i/p;
    }
    return n;
  }

  public static BigInteger f(int n) {
    int m=n+1;
    int[] p=new int[m];
    double logn=Math.log(n);
    for(int i=2; i<=m/2; i++) {
      if(p[i]!=-1) {
        //Marcar todos os múltiplos
        for(int j=i+i; j<m; j+=i) {
          p[j]=-1;
        }
        //Calcular soma de expoentes até a maior potência anterior a n
        int power=(int)(logn/Math.log(i));
        int k=(int)Math.pow(i, power);
        p[i]=(k-1)/(i-1);
        //Calcular os expoentes para os múltiplos que seguem a última potência
        for(int l=k+i; l<m; l+=i) {
          p[i]+=in(i, l);
        }
      }
    }
    BigInteger f=BigInteger.ONE;
    for(int k=2; k<m; k++) {
      int pk=p[k];
      if(pk>=0) {
        if(k>2) {
          System.out.print("*");
        }
        if(pk<2) {
          System.out.print(k);
          f=f.multiply(BigInteger.valueOf(k));
        } else {
          System.out.print(k+"^"+pk);
          f=f.multiply(BigInteger.valueOf(k).pow(pk));
        }
      }
    }
    return f;
  }
    
  public static void main(String... args) {
    for(int i=2; i<20; i++) {
      System.out.printf("%d! = ", i);
      BigInteger f1=f(i);
      System.out.printf(" = %d\n", f1);
    }
  }
 }

E isso produz o seguinte:

2! = 2 = 2
3! = 2*3 = 6
4! = 2^3*3 = 24
5! = 2^3*3*5 = 120
6! = 2^4*3^2*5 = 720
7! = 2^4*3^2*5*7 = 5040
8! = 2^7*3^2*5*7 = 40320
9! = 2^7*3^4*5*7 = 362880
10! = 2^8*3^4*5^2*7 = 3628800
11! = 2^8*3^4*5^2*7*11 = 39916800
12! = 2^10*3^5*5^2*7*11 = 479001600
13! = 2^10*3^5*5^2*7*11*13 = 6227020800
14! = 2^11*3^5*5^2*7^2*11*13 = 87178291200
15! = 2^11*3^6*5^3*7^2*11*13 = 1307674368000
16! = 2^15*3^6*5^3*7^2*11*13 = 20922789888000
17! = 2^15*3^6*5^3*7^2*11*13*17 = 355687428096000
18! = 2^16*3^8*5^3*7^2*11*13*17 = 6402373705728000
19! = 2^16*3^8*5^3*7^2*11*13*17*19 = 121645100408832000
É muito mais interessante que um fatorial recursivo simples, com if ou sem.

sexta-feira, 6 de dezembro de 2013

Fatorial revisitado II

Uma propriedade interessante dos números surgiu nas minhas investigações do fatorial. O número de vezes que um número divide seus múltiplos entre duas potências suas consecutivas é igual à primeira potência menos um mais o expoente dessa primeira potência. Bastante intuitivo, eu penso.

Entre 2n e 2n+1, por exemplo, temos os múltiplos 2n+2, 2n+4, 2n+6, e assim por diante. Cada um desses números pode ser dividido por uma potência de 2. E a soma dos expoentes é 2n-(1+n).

Tomemos outro exemplo com 5. Entre 51 e 52 temos os seguintes múltiplos de 5: 10 (2*5); 15 (3*5); e 20 (2*2*5). A soma dos expoentes de 5 é 3, que é igual a 5-2, ou, 5n-(1+n)=51-(1+1)=3.

Essa propriedade podemos usar para acelerar o cáculo do fatorial. Infelizmente, ainda precisamos manter o método anterior para calcular os expoentes de cada primo em cada múltiplo posterior à última potência anterior a n (quando estivermos calculando n!).

Então, o algoritmo ficou divido em 3 partes:
  1. Para cada primo, marcar seus múltiplos menores que n+1;
  2. Para cada potência de cada primo p, calcular pm-(1+m);
  3. Para os múltiplos de p superiores à última potência inferior a n, calcular os expoentes de p.
Em Java, montei o seguinte:

  private static int in(int p, int i) {
    int n=0;
    while(i%p==0) {
      n++;
      i=i/p;
    }
    return n;
  }

  public static BigInteger f(int n) {
    int m=n+1;
    int[] p=new int[m];
    for(int i=2; i<=m/2; i++) {
      if(p[i]!=-1) {
        //Marcar todos os múltiplos
        for(int j=i+i; j<m; j+=i) {
          p[j]=-1;
        }
        //Calcular os expoentes entre potências consecutivas
        int k=i;
        int power=1;
        for(; k*i<=m; k*=i) {
          p[i]+=k-power-1;
          p[i]+=power;
          power++;
        }
        //Calcular os expoentes para os múltiplos que seguem a última potência
        if(k<m) {
          p[i]+=power;
          for(int l=k+i; l<m; l+=i) {
            p[i]+=in(i, l);
          }
        }
      }
    }
    BigInteger f=BigInteger.ONE;
    for(int k=2; k<m; k++) {
      int pk=p[k];
      if(pk>=0) {
        if(pk==0) {
          f=f.multiply(BigInteger.valueOf(k));
        } else {
          f=f.multiply(BigInteger.valueOf(k).pow(pk));
        }
      }
    }
    return f;
  }
O mais surpreendente é que funciona.

quinta-feira, 5 de dezembro de 2013

Fatorial revisitado

Eu estava tendo dificuldades para criar um executável com um banco de dados embutido (com Tcl, SQLite e Freewrap), quando resolvi brincar um pouco com o Tcl. Escrevi uma pequena função para calcular fatoriais. Tcl permite gerar números bem grandes (como 1000!) com facilidade. Para tornar a coisa mais interessante, resolvi escrever uma versão sem if (abaixo).

proc fac {n} {
  set e 1;
  for {set x 2} {$x<=$n} {incr x} {
    append e "*$x"
  }
  puts $e
  return [expr $e]
}
A função simplesmente gera uma expressão (por exemplo, ela gera "2*3*4*5" para 5!) e depois calcula o resultado. Talvez inspirado pela recente releitura o livro Metamat! (de Gregory Chaitin), achei que seria interessante encontrar uma maneira de encolher essa expressão e assim tornar o programa mais curto e eficiente.

A solução mais próxima seria gerar um produto de primos, assim a multiplicação teria o menor número possível de multiplicandos. Não demorou muito para sair um algoritmo parecido com o Crivo de Eratóstenes. Se eu quiser calcular n!, faço o seguinte:
  1. Enumero todos os inteiros até n;
  2. Começando em 2, marco todos os múltiplos dos primos e anoto qual potência de cada primo divide cada múltiplo (e vou somando as potências);
  3. Ao fim, terei uma potência para cada primo e a multiplicação de cada um elevado à sua potência produzirá o fatorial.
Por exemplo, para 6!, os passos são:
  1. Começando em 2, descubro que 2=2^1, 4=2^2 e 6=2^1*3;
  2. Com 3, tenho 3=3^1 e 6=3^1*2;
  3. Quatro posso ignorar, porque já o visitei e anotei como inteiro composto;
  4. 5 não tem múltiplos menores ou iguais a 6, então anoto 5^1;
  5. Finalmente, 6 já sei que não é primo.
O resultado é 2^4*3^2*5 e isso produz 720, conforme esperado. Abaixo apresento uma solução em Java:

  private static int in(int p, int i) {
    int n=0;
    while(i%p==0) {
      n++;
      i=i/p;
    }
    return n;
  }

  public static BigInteger f(int n) {
    int m=n+1;
    int[] p=new int[m];
    for(int i=2; i<=m/2; i++) {
      if(p[i]!=-1) {
        p[i]=1;
        for(int j=i+i; j<m; j+=i) {
          if(j%i==0) {
            int v=in(i,j);
            p[i]+=v;
            p[j]=-1;
          }
        }
      }
    }
    BigInteger f=BigInteger.ONE;
    for(int k=2; k<m; k++) {
      int pk=p[k];
      if(pk>=0) {
        if(pk==0) {
          f=f.multiply(BigInteger.valueOf(k));
        } else {
          f=f.multiply(BigInteger.valueOf(k).pow(pk));
        }
      }
    }
    return f;
  }

Esse código tem uma pequena otimização. Ele percorre os primos apenas até n/2. Todos os primos acima disso só vão aparecer uma única vez no produto final (em 10!, por exemplo, 7 só aparece como 7^1). O array p começa com zeros. As posições ocupadas por primos vão recebendo as respectivas potências e as demais recebem -1. Ao fim, as posições de primos acima de n/2 vão continuar com 0 (porque não as visitamos), então a segunda parte da função (que faz as multiplicações para calcular o resultado final) ignora os valores menores que zero, troca os zeros por uns e usa os demais valores como os encontrar.

Comparando com uma função simples que multiplica instâncias de BigInteger de 2 até n, para valores pequenos, a diferença de tempo é insignificante. Em algum ponto entre 1000! e 2000!, a nova função passa a tomar um pouco mais que a metade do tempo. Ela é mais econômica em memória também.

O método f() retorna um BigInteger, mas imagino que em algumas situações possa ser mais útil guardar o produto de primos ou simplesmente imprimir a expressão.

segunda-feira, 25 de novembro de 2013

Tcl e Freewrap

Recebi a incumbência de escrever um pequeno programa para corrigir alguns arquivos de configuração. Os requisitos eram estes:
  1. Rodar em Windows;
  2. Ser um executável sem dependências externas (principalmente, não depender de uma VM);
  3. Alterar o valor da coluna 114 para 4 em todas as linhas;
  4. Gravar uma cópia do arquivo original.
Pareceu-me uma boa oportunidade de usar o Tcl. Desta vez, sem o Tk. Com o Freewrap, posso rapidamente criar um executável.
O script abaixo mostra como é simples a tarefa com Tcl, mas é o Freewrap que torna a aplicação da linguagem interessante no Windows.

if {$argc<1} {
  puts "Uso: setcol arquivo";
  exit
}

set filename [lindex $argv 0]

proc replace {filename} {
  set in  [open $filename]
  set out [open "$filename.temp" w]

  while {[gets $in line] >= 0} {
    puts $out [string replace $line 113 113 4]
  }
  
  close $in
  close $out
  file delete "$filename.bak"
  file rename $filename "$filename.bak"
  file rename "$filename.temp" $filename
}

if [catch {replace $filename} result] {
  puts stderr "Cuidado: $result"
  exit
}


A força do Freewrap para este tipo de problema (uma solução para uma máquina remota com poucos recursos de software) é que ele permite agregar binários adicionais ao pacote. Então, posso enviar um script e vários binários acessórios sem nenhum esforço. Posso também criar uma interface gráfica com Tk para facilitar o uso de algum binário por um operador leigo. E o resultado vai rodar em qualquer versão do Windows (talvez não no Windows 3.11, mas, sem testar, não é possível excluir a possibilidade).

Este cenário é dos mais simples e gerar o executável é uma questão apenas de digitar o seguinte comando:

freewrapTCLSH setcol.tcl

Isso produz um arquivo chamado setcol.exe com aproximadamente 2,3MB. Essa versão do freewrap não inclui o Tk. Com ele, o executável teria mais de 4MB.

quinta-feira, 17 de outubro de 2013

Não é TRI

Uso o transporte público de Porto Alegre todos os dias, principalmente as lotações. Por isso, fiquei entusiasmado com a implantação do cartão de transporte integrado (TRI).

Durou pouco minha empolgação. Assim que descobri como funciona, percebi que o cartão foi concebido para beneficiar unicamente as empresas de transporte. Calculo que o objetivo inicial tenha sido o de eliminar as fraudes com as fichas.

Em primeiro lugar, é preciso cadastrar-se e são exigidos CPF, RG e comprovante de residência. É uma invasão de privacidade difícil de aceitar para quem quer apenas pagar pelo serviço de locomoção. Além disso, é preciso esperar 10 dias. Não, o visitante que estiver alguns dias em Porto Alegre não pode comprar um cartão. Em outros lugares, como Londres e Paris, pode-se comprar um cartão de transporte em qualquer esquina.

Como se as exigências burocráticas não fossem suficientes, os aspectos financeiros do sistema são um insulto ao consumidor. O portador abastece seu cartão com dinheiro, não com passagens. Mesmo pagando antecipadamente, se as passagens forem reajustadas, o paisano terá subtraído o novo valor a cada deslocamento. Além disso, não há qualquer desconto! Comprar cem passagens não confere nenhuma vantagem sobre comprar dez.

Em resumo, o cartão TRI é um esquema de prevenção de fraudes e de antecipação de receita. As necessidades dos clientes não entraram na equação. Passo por várias bancas de revistas pelo centro e torço pelo dia em que eu vou poder entrar numa para comprar um cartão de transporte para uma semana, um mês ou um dia.

terça-feira, 15 de outubro de 2013

Comandos distribuídos

Imediatamente depois de escrever o pequeno script do artigo anterior para procurar discos cheios nos servidores, percebi que poderia escrever algo um pouco mais genérico.

O código abaixo aceita como parâmetros uma lista de servidores e um comando a ser executado nesses servidores.

my $servers={
  srv01 => {user=>'user1', pass=>'pass1'},
  srv02 => {user=>'user2', pass=>'pass2'},
  srv03 => {user=>'user3', pass=>'pass3'}
};

my $cmd=pop(@ARGV);
print "$cmd:\n";
for my $server (@ARGV) {
  my $pass=$servers->{$server}->{pass};
  my $user=$servers->{$server}->{user};
  print "$server\n";
  my $data=`plink -pw $pass $user\@$server $cmd`;
  print "$data\n";
}

Por exemplo, para executar um "uname -a" em srv01 e srv02, eu poderia chamar o script assim (o nome do arquivo com o script é exec.pl):

exec.pl srv01 srv02 "uname -a"

Minha vontade era a de oferecer oções de linha de comando para referenciar grupos de servidores por nome, mas ainda não venci a preguiça.

sexta-feira, 11 de outubro de 2013

df distribuído

Tenho me deparado muito com discos cheios em diversas máquinas. Então, resolvi encontrar uma maneira de identificá-los mais rapidamente. Juntanto Perl e Plink (a versão de linha de comando do Putty), cheguei a uma solução bem simples.


my $servers={
  srv01 => {user=>user1, pass=>pass1},
  srv02 => {user=>user2, pass=>pass2}
};

foreach $server (keys %$servers) {
  my $pass=$servers->{$server}->{pass};
  my $user=$servers->{$server}->{user};
  print "$server\n";
  my $data=`plink -pw $pass $user\@$server df -h -l`;
  my @lines=split(chr(10), $data);
  for my $line (splice(@lines,1)) {
    my @cols=split(/\s+/, $line);
    (my $pct=$cols[4])=~s/\D//g;
    print "\t$cols[0] -> $cols[4] ($cols[5] has $cols[3] free)\n" if $pct>90;
  }
}


Através do plink, executo um "df -h -l" em cada máquina. Depois, basta processar o resultado, que será parecido com isto:


srv01
  /dev/sda1 -> 95% (/ has 500MB free)
srv02
  /dev/sdc1 -> 98% (/home has 25MB free)


Só são mostrados os discos com mais de 90% de uso.

terça-feira, 1 de outubro de 2013

Aumento de impostos já!

Uma peculiaridade dos impostos no Brasil é que os impostos sobre mercadorias são calculados por dentro. Isso significa que o valor efetivamente pago não é exatamente o indicado pela alíquota. Uma alíquota de 12%, por exemplo, corresponderia a uma alíquota de 13,63% se o imposto fosse cobrado por fora.

Se o consumidor for à Argentina e comprar uma mercadoria de valor 100, pagará adicionais 21 a título de IVA. A alíquota é de 21%. Se um consumidor brasileiro comprar uma mercadoria com ICMS de 17%, pagará, em realidade, quase 20,5%. Porque o ICMS é calculado por dentro, o preço final tem que ser inflado para que os 17% englobem também o valor agregado por ele mesmo. Isto é, os 17% têm que representar 17% do valor total pago e não apenas 17% adicionais sobre o custo da mercadoria.

Para calcular o quanto a alíquota realmente representa sobre o custo da mercadoria, é preciso multiplicá-la por 1/(1-a), sendo a o valor da alíquota.

Essa conta tem a particularidade de que, quanto maior a alíquota, mais ela foge do valor aparente. Uma alíquota de 30% representa mais de 42% sobre o valor da mercadoria. Uma alíquota de 50% representaria dobrar o valor do produto, o que equivaleria a uma alíquota de 100% por fora.

Esse cálculo fica interessante depois dos 100%. Os números ficam negativos!

Então, proponho que, uma vez que nossos legisladores não demonstram o devido interesse em simplificar os impostos ou diminuir as alíquotas, façamos campanha por aumento de impostos. Como deputados não costumam ser muito bons em contas, podem achar positivo que o ICMS seja aumentado para 500%. Talvez não gostem de descobrir que isso significará que terão que devolver 125% do preço dos produtos (os consumidores ganharão o produto e mais uns pilas).

A solução pode ser, dada a pouca tendência para a simplicidade dos nossos legisladores, aumentar o imposto de renda acima dos 100%. Então, pagaríamos mais do que ganhamos, mas ganharíamos por comprar as necessidades diárias. Não seria muito mais absurdo do que a situação atual.

quinta-feira, 26 de setembro de 2013

Evitando o ruído matinal

Há uma revolta crescente contra o grupo que domina a mídia no Rio Grande do Sul. Eu não consumo nenhum de seus produtos; evito até os da TV aberta. E não é por ideologia; é porque o conteúdo é muito pobre mesmo.

Este ano descobri o Télématin da TV5 (TV francesa). Não havendo qualquer outro motivo para aprender a língua francesa, ter o prazer de assistir à programação matinal da TV5 já deveria bastar. Os apresentadores são bem-humorados, o conteúdo é leve e informativo. Quando discutem política ou economia, o fazem com seriedade e alguma profundidade (o tempo, é claro, também é exíguo na TV francesa).

Fujo assim do ruído matinal dos noticiários da RBS e da Globo. Eles contêm excesso de violência (quem quer saber de violência durante o café da manhã?), excesso de futebol (mesmo quando não há jogos!) e bobagens como a abertura das bolsas. Se o movimento de fechamento das bolsas informa pouca coisa, que dirá o de abertura! A variação diária da bolsa de valores é apenas ruído: metade dos dias ela sobe e metade dos dias ela desce. Agregar uma explicação é patético, quando não malicioso.

Os apresentadores da TV5 conseguem ser divertidos sem cair no ti-ti-ti que as mocinhas da RBS julgam apropriado para um programa de informação. Os franceses também falam de esportes, mas não se limitam ao futebol e tampouco gastam tanto tempo nele quanto os jornalistas locais. De quando em vez tenho a sorte de assistir às Escapadas de Petitrenault (que segue o Télématin), um programa no qual o Monsieur Petitrenault passeia pela França conversando com as pessoas principalmente sobre a culinária local, mas sempre em lugares interessantes (e nunca dentro de estúdio).

O principal jornal gaúcho, a Zero Hora, é o maior representante da pobreza jornalística do estado. Tem muito mais opinião que análise. Dificilmente as notícias são colocadas num contexto histórico; parece que retórica basta. A seção mais informativa são os classificados. Na última página, para fechar em grande estilo, temos a opinião de um senhor que confessou que lê apenas o jornal para o qual escreve.

A Globo consegue inadvertidamente agregar algum humor quando convoca um correspondente estrangeiro em Buenos Aires para apresentar notícias da Venezuela. Ou alguém em Kuala Lumpur para informar sobre Tóquio (ao vivo!).

Eu tenho sorte, posso usufruir da TV5, da TVE, da BBC e até da DW (que tem programação em espanhol e inglês). É uma pena que muitos de meus compatriotas estejam presos ao mundo pequenino (e um tanto sombrio) das mídias nacionais, porque elas ofercerem muito pouco.

quarta-feira, 11 de setembro de 2013

Respeito

Recebo todos os dias a "Word of the Day" do dicionário Webster. Frequentemente, as palavras são de origem latina e essas dificilmente oferecem alguma novidade para quem tem como língua materna o português. Mesmo assim, as análises etimológicas que sempre acompanham são interessantes, porque revelam como as duas línguas (o português e o inglês) maltrataram de formas diferentes as palavras.

A última palavra do dia foi respite, cuja avó latina é respectus. Em português, respeito descende dessa palavra. A latina respectus significa "ação de olhar para trás".

Consequentemente, quando um rapaz passar por uma moça brasileira, cheia de graça, beleza e ziriguidum e olhar para trás, estará, em realidade, demonstrando respeito no sentido mais latino da palavra.

terça-feira, 10 de setembro de 2013

Suporte e seus padrões

Tenho a impressão que o trabalho do suporte deve ter muito em comum com a medicina. Os usuários repetem certos padrões de comportamento que os médicos devem testemunhar com frequência. Cataloguei alguns padrões muito comuns de comportamento:
  1. Terceirização - alguns usuários pedem aos colegas para conversar com o suporte; o grande problema é que os colegas geralmente não conseguem esclarecer nenhuma questão, porque não sabem o que houve (sabem apenas que algo de ruim aconteceu);
  2. Diagnóstico precoce - talvez baseando-se em sua experiência, muitos usuários gostam de entregar o diagnóstico pronto para o suporte; infelizmente, estão com frequência errados, ou o diagnóstico é insuficiente;
  3. Informação insuficiente - é comum ver imagens de telas (inclusive BMPs de 3MB com "Page not found - 404"), sem o devido contexto, quando muito mais simples seria um número de documento ou uma URL;
  4. Atraso - alguns usuários esperam uma ou duas semanas antes de reclamar de um problema; com frequência, o problema torna-se insolúvel (porque os logs já foram apagados) ou impraticável (recuperar um backup do banco de dados, quando teria sido possível executar uma consulta de flashback no momento da falha);
  5. Dificuldade com o óbvio - não importa quanta informação o sistema oferecer, para certos usuários, todos os avisos são inescrutáveis ("por que o sistema está dizendo que minha senha expirou?").
Alguns usuários mais avançados conseguem aplicar combinções desses padrões. Por exemplo, existe a terceirização atrasada com informação insuficiente ("meu colega teve um problema na semana passada, mas não lembro qual a tela"). Ou ainda, a perniciosa terceirização atrasada com diagnóstico precoce e informação insuficiente ("já resolveram aquela questão do banco de dados que deu problema para o fulano no mês passado?").

quarta-feira, 31 de julho de 2013

Garimpando acessos ao Apex

O Oracle Apex é muito interessante para desenvolvimento rápido de aplicações, mas tem a característica duvidosa de armazenar os dados das sessões no banco de dados. Por isso é importante, de tempos em tempos, descobrir por que um determinado cliente está iniciando muitas sessões.

Uma URL do Apex termina sempre com algo assim:
    /f?p=10702:1:2602444723283688:::::

O único parâmetro p recebe todos os dados separados por ponto-e-vírgula. O primeiro número é o número da aplicação, o segundo é o número da página e o terceiro é o número da sessão.

Nos logs do Apache, encontrei o seguinte:
180.76.5.93 - - [31/Jul/2013:00:00:21 -0300] "GET /aplicprod/f?p=10702:1:2602444723283688::::: HTTP/1.1" 302 -

E a minha missão era a de descobrir quais IPs iniciaram maior número de sessões distintas. A solução é trivial:

perl -ne 'print "$1 $2\n" if /^(\S+).+p=\d+:\d+:(\d{2,})/' access_log \
  | sort | uniq | grep -Po "(\d+\.){3}\d+" | uniq -c | sort -nr 

O primeiro passo é extrair o IP e o número da sessão e colocá-los lado-a-lado assim:

180.76.5.93 2602444723283688

Isso resolvemos com o Perl. Depois, ordeno todas as linhas e elimino as repetições (que são linhas com o mesmo IP e mesmo número de sessão). Nesse ponto, cada IP aparece tantas vezes quantas sessões distintas tiver abertas para si. Então, retiro o IP de cada linha (com o grep) e conto as ocorrências com os dois últimos comandos (uniq -c | sort -nr).

A conclusão é que o Perl facilita muito a estrepolias com a linha-de-comando e que há uns engraçadinhos nos Estêites e na China que gostam de iniciar sessões do Apex.

sexta-feira, 5 de julho de 2013

Treze meses

O número treze não é muito querido e talvez por isso nunca se dê muita atenção ao fato de que o nosso ano estaria melhor dividido em treze meses que pelos atuais doze. Ou talvez, como eu, as pessoas simplesmente não pensem muito sobre isso.

Supreendeu-me que treze divida 365 tão melhor que doze. O resto é apenas um para o treze, enquanto é cinco para o doze. Além disso, um ano de treze meses produziria meses de exatamente quatro semanas de sete dias (28 dias, portanto).

Esse dia extra poderia viver fora dos treze meses como um dia especial (no primeiro dia do ano, por exemplo), de forma que os dias das semanas caíssem sempre nos mesmos dias de cada mês. Então, o primeiro dia de Janeiro seria sempre uma segunda-feira. E o última dia do ano seria sempre um domingo. Os anos bissextos teriam um segundo dia especial.

Eu nunca tenho ilusões de ser original, dada a quantidade de gente no planeta, então fui pesquisar se isso já havia sido proposto. Descobri que Augusto Compte propôs um "Calendário Positivista" em 1849. Ele também queria renomear totalmente os meses, mas acho que seria mais fácil que o calendário fosse aceito sem uma alteração tão radical.

Resta, então, achar um novo nome para o décimo-terceiro mês. Penso que Minerva seria um bom nome. Em primeiro lugar, para termos ao menos um nome feminino de mês. Em segundo lugar, para manter a tradição de ter nomes romanos. Em terceiro lugar, a expressão "voto de minerva" refere-se justamente a uma votação envolvendo doze jurados e que foi desempatada pela deusa grega Atena (que corresponde à deusa romana Minerva).

Essa alteração seria, ademais, ecológica! Não haveria necessidade de imprimir calendários novos a cada ano. Com a prática, as pessoas sequer precisariam de um calendário para saber que o dia 16 é uma terça-feira, por exemplo. Talvez as oficinas mecânicas sofram um pouco para achar uma desculpa para colocar mulheres exiguamente vestidas nas paredes.

terça-feira, 2 de julho de 2013

Garimpanho logs com expressões regulares

Depois de analisar os logs do Apache, precisei investigar logs do JBoss. Como são logs de aplicações, as linhas não têm formato uniformizado. Logo, é preciso aplicar expressões regulares.

Com a ajuda do Perl, posso extrair o texto de interesse e depois ordenar com sort e uniq.

Por exemplo, o seguinte comando extrai endereços IPs do arquivo server.log:

perl -ne 'print "$1\n" if /((\d+.){3}\.\d+)/' server.log

Então, para ordenar os IPs por frequência, entram sort e uniq:

perl -ne 'print "$1\n" if /((\d+.){3}\.\d+)/' server.log \
  | sort | uniq -c | sort -nr | more

Se a busca não requer mais que uma expressão regular, pode-se usar a opção -o do grep, que indica que deve ser impressa apenas a parte da linha identificada pela expressão regular.

grep -Po "(\d+.){3}\.\d+" server.log

A opção -P indica que a expressão regular é da sintaxe do Perl

sexta-feira, 24 de maio de 2013

Garimpando logs do Apache

Devido a um surto de acessos, resolvi descobrir quem estava vasculhando meus sistemas.

Cada linha do log do Apache tem essa aparência:
  66.249.75.122 - - [24/May/2013:00:01:17 -0300] "GET /a.html HTTP/1.1" 403 213
Meu objetivo era obter uma lista de IPs organizados por números de acessos. Após lutar com os parâmetros de xargs e grep, descobri que algumas opções do sort e do uniq resolvem o problema.

A solução final foi esta:
  cat access_log | cut -d - -f 1 | sort | uniq -c | sort -nr
Os detalhes são estes:
  1. cat imprime o arquivo;
  2. cut -d - -f 1 corta cada linha em campos separados por "-" e extrai o primeiro deles;
  3. sort ordena as linhas
  4. uniq -c elimina as linhas repetidas e adiciona o número de linhas
  5. sort -nr ordena as linhas numericamente (usando o primeiro número em cada uma) em ordem decrescente.
As primeiras 3 linhas foram estas:
  3149 66.249.75.122
  3108 66.249.75.22
  2747 66.249.75.143
Para descobrir a quem pertencem esses IPs, posso usar o nslookup adicionando o seguinte:
  | sed -n '1,3p' | cut -d ' ' -f 5 | nslookup
As adições foram estas:
  1. sed -n '1,3p' imprime as 3 primeiras linhas (os 3 IPs com mais ocorrências no log);
  2. cut -d ' ' -f 5 recorta o IP (porque eles estão acompanhados do número de ocorrências);
  3. nslookup procura os nomes associados aos IPs.
E, como resultado, descobri que o maior usuário tem sido o Google. Talvez haja uma maneira mais simples, mas duvido que seja tão divertida.

quinta-feira, 9 de maio de 2013

Relato de uma travessia a um novo banco de dados

No qual narram-se os trabalhos e as dificuldades enfrentados por 60GB para alcançar um novo banco de dados. 

Neste ano do nosso Senhor de dois mil e treze, 60GB de dados empreenderam com grande valor uma mui laboriosa jornada até um novo banco de dados. Este que vos narra demonstra como as ferramentas do Linux contribuiram para amenizar os trabalhos e compensar a inexperiência da tripulação com as ferramentas da Oracle.

A jornada iniciou-se pelo porto de Toad, a partir do qual foram gerados arquivos ctl para as tabelas que transportariam os dados. Sendo estas naus mui frágeis, logo apresentaram problemas com as âncoras das colunas, sendo estas o conhecido ponto-e-vírgula. Ademais, muitos campos de texto possuíam aspas duplas; carácter este que serve também para delimitar os conteúdos.

Sendo assim, voltamos ao porto e buscamos novas naus, desta vez ancoradas com o tilde. Para as aspas, buscamos auxílio com uns comerciantes mouros que passavam com seus camelos.

  perl -pe "s/(?<=[^~])\"(?=[^~])/\"\"/g" dados.ctl > dados2.ctl

Essa singela expressão regular substitui as aspas que não estejam na vizinhança de um tilde por duas aspas e assim evitamos problemas com o SQL Loader da Oracle.

Infelizmente, os mouros estragaram os cabeçalhos. Nosso Senhor há de perdoar nossa falha e nunca voltaremos a negociar com esses infiéis. Ao menos, roubamos-lhes a sapiência a respeito dos operadores de lookahead (?=) e lookbehind (?<=) que, buscando revertê-los ao serviço de nosso Senhor, permitem procurar sem consumir a fonte. Essa habilidade nos é muito útil na presença de aspas separadas por um único carácter (este seria consumido pela expressão [^~] e impediria a segunda aspa de ser reconhecida).

Buscamos o cabeçalho original e recortamo-lo assim:

  $grep -n BEGINDATA dados.ctl
  66
  $sed -n '1,66p' dados.ctl > dados.cabecalho
  $sed '1,66d' dados2.ctl > dados3.ctl
  $cat dados.cabecalho dados3.ctl > dados4.ctl

E, com a graça de nosso Senhor, procedemos à carga, que obteve sucesso e muita riqueza tratará ao nosso reino.

quarta-feira, 24 de abril de 2013

Novas mídias, velhos problemas

Algumas pessoas têm extrema dificuldade de comunicar-se. Recentemente, por exemplo, um senhora estava trancando a entrada do meu prédio enquanto balbuciava "não sei se é por aqui". Perguntei, solicitamente, aonde a senhora desejava ir. A reposta foi uma hesitante "minha irmã". Prontamente, desisti. Algumas pessoas, simplesmente, não conseguem comunicar-se com eficiência.

Há poucos dias recebi por email um boletim que consistia de um arquivo PDF. Exceto pelos símbolos e informações institucionais, o PDF continha apenas um link para o Twitter. No Twitter, havia um link para uma página e nesta página estava, finalmente, o artigo. Este artigo, por mais difícil que possa ser acreditar, estava guardado dentro de um arquivo PDF.

Esse tipo de coisa, tenho certeza, exige um trabalho colaborativo. Uma única pessoa não seria capaz de formular uma forma tão obtusa de comunicação. Ou talvez aquela senhora conseguisse.

quinta-feira, 11 de abril de 2013

Atualização desanimadora

Chegaram máquinas novas no trabalho, mas elas têm apenas 6GB de memória. Digo apenas 6GB, porque as atuais têm 4GB e não lembro de ter adquirido uma máquina nova que não tivesse ao menos 4 vezes mais memória que a anterior.

Primeiro, parti de um 6502 com 128KB para um 286 com 1MB. Então, para um 486DX com 8MB e depois para um Pentium 166MMX com 64MB. Depois disso, atualizei para um Pentium 4 com 1GB. Atualmente, tenho um i3 com 4GB. Pode ser que tenha havido um 386 no meio disso tudo, mas faz tanto tempo que não lembro mais.

Então, um incremento de apenas 50% é um pouco desapontador. Do jeito que as coisas estão, não vejo motivo para atualizar as máquinas como antigamente. Por isso, essa também não deve ser uma boa época para os fabricantes. É melhor começarem a produzir tablets.

sexta-feira, 5 de abril de 2013

Renda e IDH dos estados brasileiros e países sul-americanos

Resolvi juntar os dados dos países da América do Sul com os dados dos estados brasileiros. O resultado não foi inesperado, mas ainda assim a imagem ilustra muito bem como os estados brasileiros ocupam todo o espectro da renda e do desenvolvimento humano da América do Sul. Clique na imagem para ampliá-la.


O eixo vertical indica o IDH e o eixo horizontal indica a renda. As linhas cinzas indicam as médias para o continente.

Oito unidades federativas estão acima da renda média sul-americana: MT, PR, RS, ES, SC, RJ, SP, e DF. Além disso, 21 estão acima do IDH médio: todos menos PI, MA, AL, PB, CE, e PE.

As unidades federativas RJ, SP e DF estão acima da renda e do IDH de todos os países sul-americanos. Elas possuem mais de 57 milhões de habitantes, o que equivale aproximadamente à população do Chile e da Argentina somadas (são os dois países de melhor renda e IDH do continente).

Ainda mais interessante é o fato de que 6 estados têm IDH maior que o Chile (campeão sul-americano). Isso significa que quase 85 milhões de brasileiros vivem numa região com IDH superior ao IDH dos 17 milhões de chilenos.

No outro extremo, PI e MA são mais pobres que a Bolívia (em renda per capita), mas, ao menos, mantêm-se acima do IDH desse país.


<html>
<head>
<script src="processing-1.4.1.min.js"></script>
<script type="text/processing" data-processing-target="canvas">
  class Pais {
    int populacao, idh, renda, cor;

    Pais(int p, int i, int r, int c) {
      populacao=p;
      idh=i;
      renda=r;
      cor=c;
    }
  }

  HashMap paises=new HashMap();
  paises.put("Argentina", new Pais(40482000,797,17516,4));
  paises.put("Bolivia", new Pais(9863000,663,4789,1));
  paises.put("Brasil", new Pais(191241714,718,11769,0));
  paises.put("Chile", new Pais(16928873,805,17222,0));
  paises.put("Colombia", new Pais(45928970,710,10249,1));
  paises.put("Equador", new Pais(14573101,720,8492,1));
  paises.put("Guiana", new Pais(772298,633,7465,3));
  paises.put("Paraguai", new Pais(6831306,665,5413,3));
  paises.put("Peru", new Pais(29132013,725,10062,2));
  paises.put("Suriname", new Pais(472000,680,9475,3));
  paises.put("Uruguai", new Pais(3477780,783,15113,4));
  paises.put("Venezuela", new Pais(26814843,735,12568,2));

  int[] cores=new int[] {#98db11, #ff9900, #ffd42a, #ff0000, #5599ff};
  
  class Estado {
    int populacao, idh, renda, regiao;

    Estado(int p, int i, int r, int s) {
      populacao=p;
      idh=i;
      renda=r;
      regiao=s;
    }
  }

  HashMap estados=new HashMap();
  estados.put("AC", new Estado(707125,751,7041,0));
  estados.put("AL", new Estado(3093994,677,5164,1));
  estados.put("AP", new Estado(648553,780,8543,0));
  estados.put("AM", new Estado(3350773,780,11829,0));
  estados.put("BA", new Estado(13633969,742,6922,1));
  estados.put("CE", new Estado(8180087,723,5636,1));
  estados.put("DF", new Estado(2469489,874,37600,2));
  estados.put("ES", new Estado(3392775,802,15236,3));
  estados.put("GO", new Estado(5849105,800,9962,2));
  estados.put("MA", new Estado(6424340,683,4628,1));
  estados.put("MT", new Estado(3033991,796,12350,2));
  estados.put("MS", new Estado(2404256,802,10599,2));
  estados.put("MG", new Estado(19159260,800,11028,3));
  estados.put("PA", new Estado(7443904,755,6241,0));
  estados.put("PB", new Estado(3753633,718,5507,1));
  estados.put("PR", new Estado(10266737,820,13158,4));
  estados.put("PE", new Estado(8541250,718,6528,1));
  estados.put("PI", new Estado(3086448,703,4213,1));
  estados.put("RJ", new Estado(15180636,832,17695,3));
  estados.put("RN", new Estado(3121451,738,6754,1));
  estados.put("RS", new Estado(10576758,832,14310,4));
  estados.put("RO", new Estado(1535625,776,8391,0));
  estados.put("RR", new Estado(425398,750,9075,0));
  estados.put("SC", new Estado(6178603,840,15638,4));
  estados.put("SP", new Estado(39924091,833,19548,3));
  estados.put("SE", new Estado(2036277,742,7560,1));
  estados.put("TO", new Estado(1373551,756,7210,0));

  int[] regioes=new int[] {#98db11, #ff9900, #ffd42a, #ff0000, #5599ff};
  
  int W=2000;

  void setup() {
    size(W,W/2);
    background(255);
    noLoop();
  }

  void draw() {
    Iterator i = paises.entrySet().iterator();
    while (i.hasNext()) {
      Map.Entry me = (Map.Entry)i.next();
      String nome=me.getKey();
      Pais pais=me.getValue();
      float x=W*(pais.renda/40000);
      float y=W*(pais.idh/1000);
      float r=2+pais.populacao/1000000;
      fill(cores[pais.cor], 150);
      stroke(cores[pais.cor], 255);      
      ellipse(x, W-y, r, r);
      fill(0, 255);
      text(nome, x, W+4-y+r/2);
    }
    
    Iterator i2 = estados.entrySet().iterator();
    while (i2.hasNext()) {
      Map.Entry me = (Map.Entry)i2.next();
      String sigla=me.getKey();
      Estado estado=me.getValue();
      float x=W*(estado.renda/40000);
      float y=W*(estado.idh/1000);
      float r=2+estado.populacao/1000000;
      fill(regioes[estado.regiao], 100);
      stroke(regioes[estado.regiao], 255);      
      ellipse(x, W-y, r, r);
      fill(#000000, 255);
      text(sigla, x, W+4-y+r/2);
    }
    
    stroke(#AAAAAA, 255);
    float rendaMedia=(11962/40000)*W;
    line(rendaMedia, 0, rendaMedia, W);
    float idhMedio=W-(729/1000)*W;
    line (0, idhMedio, W, idhMedio);
    
    save("paises_estados.jpg");
  }
</script>
</head>
<body>
<canvas id="canvas">
</body>
</html>

terça-feira, 26 de março de 2013

Como encontrar valores distintos em arquivos de texto

Recebi um arquivo de texto com quase 300MB e 300 mil linhas para uma carga que executo mensalmente. Esse arquivo veio com uma alteração de formato e também com valores novos numa coluna crítica.

Para verificar se a alteração no formato não tinha provocado uma falha na carga, resolvi inspecionar o arquivo com as ferramentas que o Linux oferece.

Descobri uma maneira bastante simples de enumerar os valores distintos de uma coluna:

cut -c N-M arquivo.txt | sort | uniq

Os valores N e M indicam, respectivamente, a coluna inicial (baseado em 1) e a coluna final do trecho interessante de cada linha. Com um arquivo compactado, fiz o seguinte:

gunzip -c arquivo.txt.gz | cut -c N-M | sort | uniq

O cut pareceu-me um pouco lento, então resolvi verificar se o Perl pode fazer o mesmo com maior velocidade.

perl -pe '$_=substr($_, C, L)."\n"' arquivo.txt | sort | uniq

E, de fato, a solução, embora mais complexa, é mais rápida. Neste caso, C indica a coluna inicial (baseada em 0) e L o tamanho do campo (ou seja, C=N-1 e L=M-N+1).

Posso usar o perl com arquivos compactados assim:

gunzip -c arquivo.txt.gz | perl -pe '$_=substr($_, C, L)."\n"' | sort | uniq

Se eu quisesse apenas saber o número de valores distintos, bastaria adicionar o wc ao fim:

cut -c N-M arquivo.txt | sort | uniq | wc -l

E, com isso, pude confirmar que tinha carregado os dados corretamente. É interessante que o comando uniq não busca valores distintos, ele apenas elimina linhas sucessivas iguais. Por isso, é preciso usar também o comando sort.

quarta-feira, 6 de março de 2013

Renda e desenvolvimento da América do Sul

Aproveitei o código do artigo sobre a renda e o IDH dos estados brasileiros para fazer uma comparação dos países sul-americanos. Aproveitei para adicionar duas informações: a renda média e o IDH médio. Esses dados são representados, respectivamente, por uma linha vertical e uma linha horizontal. O tamanho de cada círculo representa o número de habitantes.

Clique na imagem para aumentá-la.

Eu ignorei as colônias (Malvinas e Guiana Francesa), porque não possuem informação sobre IDH.

Dado o seu tamanho, o Brasil só poderia mesmo estar nas redondezas das médias. Infelizmente, ele está um pouco abaixo das duas.

Argentina e Chile lideram com bastante folga, cada um desses países liderando num quesito, mas por pequena margem. Isso me remete ao fato de que, na nossa mídia, o Chile é frequentemente referido como um sucesso retumbante enquanto a Argentina é um fracasso trágico. Resulta que são muito parecidos. O Uruguai não está muito atrás no que concerne o IDH, mas a sua renda é sensivelmente menor.

Orbitam o Brasil a Venezuela, o Peru e a Colômbia. Esses países dificilmente são retratados em bons termos na nossa mídia, curiosamente. O Equador tem renda um tanto menor que a desse grupo, mas um IDH semelhante.

A Guiana e o Suriname destoam por terem IDHs pequenos para seus respectivos níveis de renda.

Eis o código:

<html>
<head>
<script src="processing-1.4.1.min.js"></script>
<script type="text/processing" data-processing-target="canvas">
  class Pais {
    int populacao, idh, renda, cor;

    Pais(int p, int i, int r, int c) {
      populacao=p;
      idh=i;
      renda=r;
      cor=c;
    }
  }

  HashMap paises=new HashMap();
  paises.put("Argentina", new Pais(40482000,797,17516,4));
  paises.put("Bolivia", new Pais(9863000,663,4789,1));
  paises.put("Brasil", new Pais(191241714,718,11769,0));
  paises.put("Chile", new Pais(16928873,805,17222,0));
  paises.put("Colombia", new Pais(45928970,710,10249,1));
  paises.put("Equador", new Pais(14573101,720,8492,1));
  paises.put("Guiana", new Pais(772298,633,7465,3));
  paises.put("Paraguai", new Pais(6831306,665,5413,3));
  paises.put("Peru", new Pais(29132013,725,10062,2));
  paises.put("Suriname", new Pais(472000,680,9475,3));
  paises.put("Uruguai", new Pais(3477780,783,15113,4));
  paises.put("Venezuela", new Pais(26814843,735,12568,2));

  int[] cores=new int[] {#98db11, #ff9900, #ffd42a, #ff0000, #5599ff};

  void setup() {
    size(1024,1024);
    background(255);
    noLoop();
  }

  void draw() {
    Iterator i = paises.entrySet().iterator();
    while (i.hasNext()) {
      Map.Entry me = (Map.Entry)i.next();
      String nome=me.getKey();
      Pais pais=me.getValue();
      float x=1024*(pais.renda/20000);
      float y=1024*(pais.idh/1000);
      float r=2+pais.populacao/1000000;
      fill(cores[pais.cor], 150);
      stroke(cores[pais.cor], 255);      
      ellipse(x, 1024-y, r, r);
      fill(0, 255);
      text(nome, x, 1032-y+r/2);
    }
    stroke(#AAAAAA, 255);
    float rendaMedia=(11962/20000)*1024;
    line(rendaMedia, 0, rendaMedia, 1024);
    float idhMedio=1024-(729/1000)*1024;
    line (0, idhMedio, 1024, idhMedio);
    
  }
</script>
</head>
<body>
<canvas id="canvas">
</body>
</html>

domingo, 3 de março de 2013

Renda e desenvolvimentos dos estados brasileiros

Depois de brincar com a linguagem Processing, resolvi avaliar a sua versão para Javascript. Parece que, atualmente, tudo está sendo refeito em Javascript. Esta é uma das instâncias em que isso tem sentido.

Busquei os dados dos estados brasileiros na Wikipédia. As informações que resolvi comparar são a renda, a população e o índice de desenvolvimento humano (IDH).

Foi surpreendentemente fácil; a tarefa mais laboriosa foi copiar os dados da Wikipédia para o programa.

  class Estado {
    int populacao, idh, renda, regiao;

    Estado(int p, int i, int r, int s) {
      populacao=p;
      idh=i;
      renda=r;
      regiao=s;
    }
  }

  HashMap estados=new HashMap();
  estados.put("AC", new Estado(707125,751,7041,0));
  estados.put("AL", new Estado(3093994,677,5164,1));
  estados.put("AP", new Estado(648553,780,8543,0));
  estados.put("AM", new Estado(3350773,780,11829,0));
  estados.put("BA", new Estado(13633969,742,6922,1));
  estados.put("CE", new Estado(8180087,723,5636,1));
  estados.put("DF", new Estado(2469489,874,37600,2));
  estados.put("ES", new Estado(3392775,802,15236,3));
  estados.put("GO", new Estado(5849105,800,9962,2));
  estados.put("MA", new Estado(6424340,683,4628,1));
  estados.put("MT", new Estado(3033991,796,12350,2));
  estados.put("MS", new Estado(2404256,802,10599,2));
  estados.put("MG", new Estado(19159260,800,11028,3));
  estados.put("PA", new Estado(7443904,755,6241,0));
  estados.put("PB", new Estado(3753633,718,5507,1));
  estados.put("PR", new Estado(10266737,820,13158,4));
  estados.put("PE", new Estado(8541250,718,6528,1));
  estados.put("PI", new Estado(3086448,703,4213,1));
  estados.put("RJ", new Estado(15180636,832,17695,3));
  estados.put("RN", new Estado(3121451,738,6754,1));
  estados.put("RS", new Estado(10576758,832,14310,4));
  estados.put("RO", new Estado(1535625,776,8391,0));
  estados.put("RR", new Estado(425398,750,9075,0));
  estados.put("SC", new Estado(6178603,840,15638,4));
  estados.put("SP", new Estado(39924091,833,19548,3));
  estados.put("SE", new Estado(2036277,742,7560,1));
  estados.put("TO", new Estado(1373551,756,7210,0));

  int[] regioes=new int[] {#98db11, #ff9900, #ffd42a, #ff0000, #5599ff};

  void setup() {
    size(512,512);
    background(255);
    noLoop();
  }

  void draw() {
    Iterator i = estados.entrySet().iterator();
    while (i.hasNext()) {
      Map.Entry me = (Map.Entry)i.next();
      String sigla=me.getKey();
      Estado estado=me.getValue();
      float x=512*(estado.renda/40000);
      float y=512*(estado.idh/1000);
      float r=5+estado.populacao/1000000;
      fill(regioes[estado.regiao], 100);
      stroke(regioes[estado.regiao], 255);      
      ellipse(x, 512-y, r, r);
    }
  }
O programa desenha um círculo para cada estado. A posição no eixo horizontal indica a renda (quanto mais à direita, maior a renda per capita) e a posição no eixo vertical indica o IDH (quanto mais alto, maior o índice de desenvolvimento humano). O raio do círculo indica a população do estado. As cores representam as regiões: verde para norte;  laranja para nordeste;  amarelo para centro-oeste; vermelho para sudeste; e azul para sul).

Aquele ponto amarelo disparado na frente é o Distrito Federal, evidentemente. O grande ponto vermelho que o precede é São Paulo (que, tendo uma renda per capita bastante maior que a dos estados do sul, não consegue superá-los no IDH).

Pode-se constatar que os pontos aglomeram-se conforme as cores. Minas Gerais é uma exceção que se destaca, assim como o Distrito Federal.

Com pequenas alterações no método draw(), consegui inserir as siglas dos estados dentro dos círculos.

  void setup() {
    size(1024,1024);
    background(255);
    noLoop();
  }

  void draw() {
    Iterator i = estados.entrySet().iterator();
    while (i.hasNext()) {
      Map.Entry me = (Map.Entry)i.next();
      String sigla=me.getKey();
      Estado estado=me.getValue();
      float x=1024*(estado.renda/40000);
      float y=1024*(estado.idh/900);
      float r=20+estado.populacao/1000000;
      fill(regioes[estado.regiao], 100);
      stroke(regioes[estado.regiao], 255);      
      ellipse(x, 1024-y, r, r);
      fill(#FFFFFF, 255);
      text(sigla, x-8, 1028-y);
    }
  }

Tive que aumentar as dimensões do gráfico, para que as siglas não se atropelassem (ainda mais).

Clique na imagem para vê-la aumentada.

Concluí, então, que Processing.js é uma ferramenta deveras interessante e prática. Acho que o próximo passo é investigar as facilidades de animação e interação com o usuário. O fato de rodar no navegador permite jogar o processamento para o cliente.

sábado, 2 de março de 2013

Simplificando a construção de consultas dinâmicas

Um problema recorrente é o de construir consultas para formulários com campos opcionais. Em grande parte dos casos, as consultas são geradas dinamicamente antes de serem submetidas ao banco de dados. Isto é, a lógica da consulta fica escondida na lógica da aplicação com cada cláusula sendo adicionada ou não conforme o valor digitado pelo usuário (e um if na aplicação).

Penso que, em muitos casos, isso pode ser resolvido com pequenas alterações no SQL. Apresento uma tabela de pessoas (PESSOA) como exemplo. Ela possui as seguintes colunas:
  • NOME;
  • NASCIMENTO (uma data);
  • DEPARTAMENTO (um número).
Vou começar pela data. Suponha que o usuário possa pesquisar todas as pessoas pela data de nascimento, mas que o campo é opcional. A consulta mais óbvia é:

select *
from pessoa
where nascimento=:data_nascimento
Evidentemente, se o usuário não digitar uma data, a aplicação deverá retirar a cláusula. Mas, para evitar isso, podemos reescrever a consulta assim:

select *
from pessoa
where 
nascimento between nvl(:data_nascimento, to_date('1000', 'YYYY')) 
               and nvl(:data_nascimento, to_date('3000', 'YYYY'))
Neste caso, se a data de nascimento não for informada, a consulta retornará todas as pessoas que nasceram entre o ano 1000 e o ano 3000 (a segunda data poderia muito bem ser sysdate) e isso deve bastar, a menos que se trate de uma base de dados do Vaticano. Se o formulário permite informar um período (duas datas), então a consulta já está preparada para recebê-las.

Com relação aos nomes, podemos escrever o seguinte:

select *
from pessoa
where 
nome like nvl(:nome, '%')
O que mais se vê é isto, no entanto:
(:nome is null or nome like :nome) 
Isso é um pouco mais eficiente, mas menos no estilo da programação incondicional. E, se o espírito hacker bater:

select *
from pessoa
where nome between nvl(:nome, chr(0)) and nvl(:nome, chr(255))
Entre os caracteres 0 e 255 estão todas as letras do alfabeto latino, inclusive com acentos, no código ASCII. Este mesmo truque pode ser usado para o departamento:

select *
from pessoa
where departamento between nvl(:dept, -1) and nvl(:dept, 1000)
Evidemente, todos os departementos devem ter códigos entre -1 e 1000; conhecer os dados é importante para poder usar este tipo de truque, mas isso é importante de qualquer maneira. A união de todas as cláusulas é:

select *
from pessoa
where
nome like nvl(:nome, '%') 
and nascimento between nvl(:data_nascimento, to_date('1000', 'YYYY')) 
                   and nvl(:data_nascimento, to_date('3000', 'YYYY'))
and departamento between nvl(:dept, -1) and nvl(:dept, 1000)
Agrega-se um pouco de complexidade à consulta (que pode, por outro lado, ser facilmente inserida numa procedure), mas simplifica-se o código da aplicação.

Os detratores da programação incondicional levantarão as bandeiras da legibilidade e do desempenho, mas todos sabemos que o mais importante é eliminar os ifs dos programas.

quinta-feira, 10 de janeiro de 2013

Pequenas observações antropológicas

Eu tenho pensado um pouco sobre o quanto do meu dia realmente requer que eu pense ativamente sobre o que eu estou fazendo.

Tive uma premonição depois de ler um artigo sobre um estudo feito por psicólogos que descobriu que comportamentos aparentemente reflexivos, como entrar em pânico diante de uma situação inesperada, podem ser aprendidos. Uma criança, por exemplo, pode observar seu pai ficar irritado ao derramar café sobre o jornal e adquirir o mesmo comportamento.

Observamos esses comportamentos e os achamos naturais, mas poderia também ser natural que o pai risse de seu infortúnio e que a criança aprendesse a ver o humor nos pequenos acidentes cotidianos.

Então, resolvi observar as pequenas ações impensadas do dia-a-dia.

Um fenômeno que observei há muito tempo é o da fila de um supermercado muito especial. Esse supermercado tem quatro caixas e três filas. A fila do meio é servida por duas caixas. Curiosamente, as três filas costumam manter o mesmo comprimento. Irrefletidamente, as pessoas vão mantendo as filas em comprimentos semelhantes. A fila do meio anda duas vezes mais rápido que as outras. Logo, seria de se esperar que algumas pessoas vissem a vantagem de permanecer nela mesmo quando ela fosse maior que as outras (até o dobro das outras). Mais curioso ainda é o fato de que, estando na fila e com pouco o que fazer, as pessoas não observem o que está acontecendo.

Outro fenômeno curioso é o que acontece na lotação que eu uso para ir ao trabalho. Todos os dias, pessoas param a lotação para perguntar sobre o trajeto. Algumas delas portam celulares moderníssimos com acesso à internet. Recentemente, uma moça parou a lotação, gostou da resposta do motorista, subiu, acomodou-se e prosseguiu a navegar pela internet em seu aparelho (por uma rede social, claro). O que leva as pessoas a abdicarem do poder de informação que têm nas mãos?

Finalmente, há um cinema que costumo frequentar que tem duas portas de saída, uma ao lado da outra. Invariavelmente, forma-se uma fila atrás da porta pela qual a primeira pessoa saiu. Ninguém parece querer arriscar a outra porta. Eu sei que ambas funcionam, então costumo sair pela que estiver livre. Adicionalmente, já vi duas pessoas não conseguirem abrir as portas, mesmo sendo portas com trava anti-pânico (o que demonstra que o óbvio não é absoluto).

Esses incidentes me levam a crer que as pessoas gastam muito mais tempo tentando imitar os outros do que analisando os problemas que enfrentam. Penso que observar os outros pode dar boas pistas, mas que não é inteligente abdicar de pensar, mesmo correndo o risco de errar (mais que os outros).