sábado, 20 de janeiro de 2018

Combinações de dígitos IV

Para completar as minhas funções de combinações, só faltavam os desarranjos: aqueles embaralhamentos nos quais nenhum elemento está no seu lugar original.

Por exemplo, para o conjunto (A B), o único embaralhamento no qual os dois elementos estão forma de suas posições originais é (B A). Para o conjunto (A B C), há duas possibilidades: (B C A) e (C A B).

Escrevi uma pequena função que recebe uma função de callback e uma lista de elementos para embaralhar.

#!/usr/bin/perl
use strict;
use experimental 'smartmatch';

sub derange(&\@;$@) {
  my $callback=\&{shift @_};
  my $items=shift;
  my $count=shift||0;

  if($count>$#$items) {
    $callback->(@_);
  } else {
    my @column=@$items;
    splice @column, $count, 1;

    for my $el (@column) {
      if(!($el~~@_)) {
        derange($callback, $items, $count+1, @_, $el);
      } 
    }
  }
}

derange {print "@_\n"} @ARGV;


Minha pequena função lançou mão do operador experimental ~~ para verificar se um elemento já não foi usado. Então, a função basicamente caminha pelas permutações, usando para cada posição apenas os elementos que não estavam ali originalmente e descartando os elementos já usados no ramo atual da busca.

Uma otimização óbvia seria deixar calculados os elementos que podem aparecer em cada posição.

O operador ~~ é experimental, então é preciso declarar seu uso.

Como ele imprime um desarranjo por linha, pude testar o resultado com o wc:

$ perl desarranjo.pl A B C D E F G H I J | wc -l
1334961

terça-feira, 2 de janeiro de 2018

2018

Um amigo enviou um post curioso sobre o número 2018. Eu já havia percebido que 2017 era primo e que 2018 era 2 vezes um primo. Ainda não tinha reparado que 2019 é 3 vezes um primo.

Não há muitas dessas sequências; a próxima iniciará em 2557.

Resolvi procurar sequências de 4 números. E de 5. Usei os 10 mil primeiros primos.

Achei, procurando sequências de 4, os seguintes:
  • 12.721
  • 16.921
  • 19.441
  • 24.481
  • 49.681
  • 61.561
  • 104.161
Então, não vamos ver o primeiro ano. O blog não existirá. Com sorte a humanidade terá sobrevivido.

Com 5, achei apenas um: 19.441. E com 6, nenhum. Ampliei a busca para o primeiro milhão de primos e surgiu um primo que inicia uma sequência de 6: 5.516.281. Os polvos já terão dominado o planeta.


Gerador de Augusto dos Anjos

Encontrei um pequeno artigo sobre cadeias de Markov e resolvi reescrever o código em Perl.

A idéia básica é criar uma estrutura que, para cada palavra, aponte quais as palavras que a seguem no texto que for usado como treinamento. Usei o livro "Eu".

Então, o código tem dois passos principais:
  1. Ler linha a linha o texto e, para cada palavra, criar uma lista de palavras que a sucedem;
  2. Escolher uma palavra aleatória (dentre as palavras que iniciam sentenças) e depois escolher uma palavra que a suceda em algum ponto do texto recursivamente até encontrar uma palavra que termine uma sentença.
A minha estrutura de dados principal é o hash de hashes chamado chain. Para ser mais preciso, chain é uma referência a um hash que associa palavras a referências de hashes com as palavras que as sucedem.

A função choose recebe um array e retorna um elemento qualquer desse array.

Dois símbolos especiais são usados para marcar as palavras que iniciam sentenças e as que terminam sentenças: START e END.


#!/usr/bin/perl
use strict;
use warnings;
use utf8;
use Data::Dumper;

sub choose {
  return @_[rand @_];
}

my $chain={START=>{},END=>{}};
open(my $file, 'eu.txt');
while(my $line=<$file>) {
  $line=~s/[[:punct:]]//g;
  my @words=split('\s',$line);
  $chain->{START}->{$words[0]}=1;
  $chain->{END}->{$words[-1]}=1;
  for my $i (1..$#words) {
    $chain->{$words[$i-1]}->{$words[$i]}=1;
  }
}

my $verse=[];
my $word;
do {
  $word='START' if(!$word);
  $word=choose(keys %{$chain->{$word}});
  push(@$verse, $word);
} while(!exists $chain->{END}->{$word});

print join(' ',@$verse);
  

Ele nem sempre produz algo interessante, mas, de quando em vez acerta uma pérola. As primeiras rodadas geraram o seguinte:
  • Ele hoje nas
  • Convidou-me a transição emocionante
  • Meus olhos se fosse agulha.
  • Ultrafatalidade de engolir, igual a Lua Cheia
  • Despir a sensação de cera
  • Abafava-me o gênero humano
  • Respira com essa finíssima epiderme
  • Ele hoje volto assim, pelos mata-pastos.
  • Andam monstros sombrios pela escuridão dos remorsos.
Gerei vários, até juntar alguns versos para uma poesia inédita:

Andam monstros sombrios pela escuridão dos remorsos
Pairando acima dos transeuntes
Maldito seja o gênero humano
Prostituído talvez em desintegrações maravilhosas

sexta-feira, 15 de dezembro de 2017

Docker com DBD::Oracle

São poucas as aplicações que não requerem conexão a um banco de dados. Então, após conseguir criar uma imagem de Docker com Perl e alguns pacotes adicionais, resolvi experimentar algo mais complicado: instalar o DBD::Oracle. Este pacote já é naturalmente difícil de instalar, mas com alguma experimentação, descobri uma maneira rápida e simples de resolver este problema.

Em primeiro lugar, é preciso buscar os rpms do cliente da Oracle. Inicialmente, usei os mais modernos (versão 12.2), mas estes não tinham tudo que o DBD::Oracle verifica na fase de testes (e a instalação falha). Então, usei os seguintes arquivos da versão 11.2:
  • oracle-instantclient11.2-devel-11.2.0.3.0-1.x86_64.rpm
  • oracle-instantclient11.2-basic-11.2.0.3.0-1.x86_64.rpm
  • oracle-instantclient11.2-sqlplus-11.2.0.3.0-1.x86_64.rpm
Dentro da pasta do projeto, é preciso extrair os arquivos dos rpms, desta maneira: 

rpm2cpio oracle-instantclient11.2-devel-11.2.0.3.0-1.x86_64.rpm | cpio -idmv
rpm2cpio oracle-instantclient11.2-basic-11.2.0.3.0-1.x86_64.rpm | cpio -idmv
rpm2cpio oracle-instantclient11.2-sqlplus-11.2.0.3.0-1.x86_64.rpm | cpio -id/usr/lib64/libaio.so.1.0.1mv

A ordem não é importante. O resultado será uma pasta usr/ com várias subpastas e diversos arquivos ocupando cerca de 183MB.

Adicionalmente, é preciso copiar a bliblioteca libaio (Asynchronous I/O) do /usr/lib64 desta máquina para o usr/lib64 da pasta do projeto. Provavelmente haverá um link simbólico chamado libaio.so.1 apontando a um arquivo  libaio.so.1.0.1. Eu simplesmente copiei o arquivo com o nome libaio.so.1.

O Dockerfile requer apenas que seja adicionada essa pasta e que sejam preparadas as variáveis de ambiente.

FROM        perl:latest
MAINTAINER  forinti

ENV ORACLE_HOME /usr/lib/oracle/11.2/client64
ENV PATH $PATH:$ORACLE_HOME
ENV LD_LIBRARY_PATH $LD_LIBRARY_PATH:$ORACLE_HOME/lib:/usr/lib64

COPY usr/ /usr/
RUN curl -L http://cpanmin.us | perl - App::cpanminus
RUN cpanm DBI
RUN cpanm -v DBD::Oracle
RUN cpanm HTML::Parser
RUN cpanm Dancer2

EXPOSE 3000

CMD perl /app/hello.pl


As três linhas com ENV ajustam os valores das variáveis de ambiente. A opção -v na linha de instalação do DBD::Oracle faz com que todo o andamento da instalação seja impresso na tela. Sem essa opção, o cpanm escreve num arquivo de log que acaba sendo perdido quando ele falha e o docker termina.

Testes simples comprovaram que o driver funciona.

quarta-feira, 13 de dezembro de 2017

Primeiros passos com Docker

Um dilema que enfrento com frequência é o de instalar pacotes novos em servidores nos quais não quero mudar muito ou onde já existem versões conflitantes. Outro problema é o de manter registro de todos os pacotes que uma instalação complexa requer.

O Docker permite isolar as aplicações e então decidi experimentar criar uma imagem com a última versão do Perl, mas que executasse uma aplicação definida alhures e montada em tempo de execução.

Então, escrevi uma pequena aplicação com Dancer:

#!/usr/bin/perl
use Dancer2;

get '/hello/:name' => sub {
    return "Why, hello there " . params->{name};
};

dance;

É muito simples. Ela criar um servidor que atende a requisições do tipo http://localhost:3000/hello/nome. A porta default do Dancer é a 3000.

Então, o próximo passo foi definir uma imagem para o Docker a partir da última imagem do Perl. O Dockerfile contém:

FROM        perl:latest
MAINTAINER  forinti

RUN curl -L http://cpanmin.us | perl - App::cpanminus
RUN cpanm Dancer2

EXPOSE 3000

CMD perl /app/hello.pl


As seções são:
  • FROM - indica a imagem inicial;
  • MAINTAINER - serve apenas para registrar o dono do projeto;
  • RUN - executa os comandos exatamente como numa linha de comando;
  • EXPOSE - indica a porta que estará disponível para comunicação; e
  • CMD - indica o comando que será executado quando o contêiner for criado.
Para facilitar a gerência do contêiner, resolvi usar o docker-compose, conforme o arquivo de configuração abaixo (docker-compose.yml):

version: '3.3'
services:
  hello:
    build: .
    container_name: hello
    restart: unless-stopped
    volumes:
      - type: bind
        source: /home/forinti/hello/
        target: /app
    ports:
      - "3000:3000"

Está definido um serviço (hello:) que servirá a porta 3000 (ports: define que a porta 3000 de dentro do contêiner corresponderá à porta 3000 fora do contêiner). O diretório /home/forinti/hello será montado dentro da imagem como /app. Além disso, o serviço será reiniciado, exceto se explicitamente terminado.

O diretório /home/forinti/hello contém os seguintes arquivos:

total 20
drwxrwxr-x  2 forinti forinti 4096 Dez 13 15:28 ./
drwxr-xr-x 49 forinti forinti 4096 Dez 13 12:37 ../
-rw-rw-r--  1 forinti forinti  223 Dez 13 15:27 docker-compose.yml
-rw-rw-r--  1 forinti forinti  172 Dez 13 15:28 Dockerfile
-rwxrw-r--  1 forinti forinti  120 Nov 21 16:19 hello.pl*

Para criar a imagem, basta rodar "docker-compose build". E, para iniciar o serviço, "docker-compose up".

$curl -XGET localhost:3000/hello/forinti
Why, hello there forinti


Dá para pegar gosto pela coisa.

sexta-feira, 24 de novembro de 2017

Perl é ilegível

...para quem não sabe Perl. Claro. Mas é uma provocação que escuto muito de meus colegas que gostam de Java. Eu também gostava, mas antes dela tentar tanto se transformar numa linguagem hipster.

Bom, resolvi comparar a legibilidade com um problema simples: tenho um array e quero criar um string com a concatenação de todos os elementos não vazios.


my $s=join ';', grep {length>0} @lista;

Parece bem razoável. Um colega apresentou uma solução em Python.

s=';'.join([x for x in lista if len(x)>0])

Gostei. Simples e claro. Dá para usar. Já minha primeira tentativa em Java foi um pouco mais prolixa:

String s=String.join(";",(new ArrayList(lista))
  .removeAll(Arrays.asList("", null)));

String e String, ArrayList, Arrays e asList. Parece um poema concreto.

Um colega apontou o fato de haver uma alternativa mais moderna:

String s=lista.stream()
  .filter(s->s!=null && s.length()>0)
  .collect(Collectors.joining(";"));

Tive que quebrar esta solução moderna em 3 linhas para caber neste pequeno blog.

sexta-feira, 19 de maio de 2017

Consertando um Kobo

Há alguns anos ganhei um Kobo Touch. Ele funcionou muito bem até que, no último verão, a tela falhou. Por excesso de manuseio, ela deve ter fissurado e deixou de atualizar boa parte de sua área.



Imediatamente procurei uma forma de consertá-lo e descobri que é possível comprar a tela (da China, obviamente). Abri o leitor e descobri que o modelo da tela é ED060SCE(LF). Encontrei vários vendedores no AliExpress e decidi por um que vendia um ED060SCE(LF)C1. Não era exatamente o modelo do meu, mas servia. Infelizmente, não encontrei uma explicação para as variantes (para poder, ao menos, escolher a mais recente). Aparentemente, ED indica o fabricante (E-Ink); 060 indica o tamanho (6"); e SCE(LF) indica o modelo. Depois disso, as letras e os números indicam variações que só o fabricante conhece. Neste caso, encontrei T1 e C1.

Paguei US$18,00 (menos de R$60) e isso inclui ferramentas: quatro chaves de fenda (só foi preciso usar uma) e duas peças de plástico (uma para abrir e outra que não achei utilidade). Menos de um quinto do valor de um aparelho novo, embora a Livraria Cultura (que representava a canadense Kobo no Brasil) tenha desistido de vendê-lo. É uma pena, porque é um aparelho muito bom. Por outro lado, considerando o valor da tela, percebe-se que o valor de venda do aparelho era excessivo (eu comprei uma tela só e imagino que uma compra grande consiga um preço ainda melhor).



Duas coisas interessantes aparecem dentro do Kobo, Primeiro, há um cartão microSDHC de 2GB. Portanto, é possível aumentar a capacidade de armazenamento. Não testei, porque 2GB de livros já é uma quantidade infindável de leitura e, de qualquer maneira, há também um slot externo. Em segundo lugar, há uma porta serial. Se um dia a tela voltar a falhar, posso tranformar o Kobo noutra coisa.

A maior dificuldade na troca foi descolar a tela original. A placa-mãe está aparafusada numa base de plástico (que é forte, por sorte) e esta base é aparafusada à caixa. A tela fica colada tanto à caixa como à base de plástico. E a cola é forte. Foi preciso quebrar a tela em muitos pedacinhos para poder extraí-la da armação. A cola permaneceu e bastou para encaixar tudo novamente.

Acho triste que esses projetos sejam feitos de maneira a dificultar a manutenção. A caixa é montada por encaixes, mas eles exigem força e não é difícil quebrar a tela no processo. Além disso, a bateria é colada e os fios soldados: não é fácil trocá-la.

Embora não fosse tecnicamente interessante, esse conserto deu a satisfação de dar nova vida a um aparelho que, doutra sorte, iria para o lixo.