sábado, 16 de junho de 2012

Simplificação do código para ser otimizado


Um problema de matemática chamou-me a atenção e encontrei a solução usando a soma de uma P.A. O enunciado é este:


Seja
f(n)=n se n<10
f(n)=multiplicação dos algarismos se n>=10
Então f(1)+f(2)+f(3)+...+f(100) é igual a?

Decidi fazer um programa e o principal deste texto não é o programa que resolve este problema. É como o conhecimento pode simplificar o processamento e resolver em menos tempo além de comparar o desempenho de duas linguagens de programação.

Primeiro foi feito em shell script. No bloco for i in `seq 10 1 99` existem duas variáveis que armazenam os algarismos do número. O comando cut -c 1 associa a dezena à variável valor1 e cut -c 2 associa a unidade à variável valor2. A variável multiplicado armazena o produto enquanto que soma acumula os resultados de multiplicado.


#!/bin/bash 

for i in `seq 9`
   do 
      let soma1=$soma1+$i  
   done  
 
for i in `seq 10 1 99`  
   do  
      valor1=`echo $i | cut -c 1`  
      valor2=`echo $i | cut -c 2` 
      multiplicado=`echo "$valor1 * $valor2" | bc`  
      let soma2=$soma2+$multiplicado  
   done 
  
let soma=$soma1+$soma2 echo Resultado $soma

Executando o script junto com o comando time tenho a resposta que levou para efetuar os cálculos:

 

 
Resultado 2070

real 0m0.704s
user 0m0.036s
sys 0m0.028s

Encontrada a solução em uma linguagem, quis testar em C++. No entanto como não consegui separar as dezenas e unidades em duas variáveis, encontrei uma solução mais simples. E para que a comparação seja válida, é preciso que os programas executem as mesmas rotinas.

Usando dois laços for separam-se as dezenas e unidades. A otimização em shell script é apresentada abaixo:

   
#!/bin/bash  

for i in `seq 9`
   do  
      let soma1=$soma1+$i  
   done  

for dezena in `seq 9`  
  do  
    for unidade in `seq 9`  
      do  
        resultado=$dezena*$unidade  
        let soma2=$soma2+$resultado 
      done  
  done  

let soma=$soma1+$soma2  
echo Resultado $soma  


Resultado 2070

real 0m0.041s
user 0m0.004s
sys 0m0.012s

Por fim, testa-se em C++.

Como foi dito, elaborei a lógica deste código antes do segundo shell script. As explicações são as mesmas do código anterior.

 
# include <iostream>  
using namespace std;  
int main()  
{  
   int dezena, unidade;  
   int soma1=0, soma2=0;
  
   for (int i=1; i<10; i++)  
      soma1+=i;  

   for (dezena=1; dezena<10; dezena++) 
   {  
      for (unidade=1; unidade<10; unidade++) 
      { 
        soma2+=dezena*unidade; 
      } 
   } 
 
   cout << "Resultado " << soma1+soma2; 
   cout << endl; 
 
   return 0; 
}  


Resultado 2070

real 0m0.004s
user 0m0.000s
sys 0m0.000s


Resumo dos tempos:

Shell Script Shell Script (com otimização) C++
0.704s 0.041s 0.004s

Conclusão: cada linguagem tem a sua particularidade. Para desenvolver algo rápido considero que o shell script é uma ferramenta que atende muito bem. Por outro lado, se for preciso alto desempenho, o C++ é insuperável*. Adicionalmente, fica claro que o resultado do que é compilado é mais rápido do que interpretado.
* De acordo com o professor doutor José Luis Zem da disciplina de Arquitetura e Organização de Computadores, a linguagem Fortran tem desempenho superior além de maior capacidade de ponto flutuante.

Agradeço ao professor Zem por explicar a diferença de desempenho entre compilado e interpretado. No caso do interpretado há um outro programa analisando cada linha do script e transformando em instruções de computador. No caso dos compilados, o compilador usa regras de otimização para gerar o binário.

domingo, 13 de maio de 2012

Desligar computador se o download não terminar

  No código-fonte desta publicação tive que adaptar o código que está em Desligando o micro ao terminar o download.
  Sempre funcionou bem, mas foi preciso pensar numa situação de contorno quando a conexão caia ou quando o roteador wireless dava seus tilts. Neste caso, o download não se completa e a condição test -s "$inteiro" não passa para verdadeiro.
  Foi pensado da seguinte forma: comparo a quantidade de bytes entre 2 segundos consecutivos.
  Se não forem iguais a variável contagem sofre um reset voltando a valer 1 (um). Se forem iguais a variável contagem é incrementada de 1 a cada 1 segundo. Se a contagem chegar a 300 (5 minutos), é executado o comando para desligar.
  Alguns downloads não admitem continuar de onde pararam e talvez seja melhor desligar o computador tão logo que se perceba que o download não será concluído.


#!/bin/bash
# v1.0: 10/21/2006
# v1.1: 04/11/2012
# v1.2: 05/12/2012

parte=""
inteiro=""

contagem=0
until test -s "$inteiro"
  do
   status_antes=`du "$parte" | awk '{printf "%2s\n",$1}'`
   sleep 1
   status_depois=`du "$parte" | awk '{printf "%2s\n",$1}'`

   if [ "$status_antes" -ne "$status_depois" ]
    then
     contagem=1
   else
    let contagem=$contagem+1
     if [ "$contagem" -eq 300 ]
      then
       /sbin/shutdown -h now
     fi
   fi

   echo Download em andamento: $status_depois kB $contagem
   sleep 2
   continue
  done

test -s "$inteiro"
  /sbin/shutdown -h now

sexta-feira, 6 de abril de 2012

Desligando o micro ao terminar o download

   O script que apresento é uma tarefa automática que desliga o computador imediatamente após o último byte do seu download chegar. Deixo claro que só serve se for usado através do gerenciador de downloads do firefox como na figura abaixo:

   A demonstração será feita com o download "LACUNA COIL - Enjoy The Silence.flv" que aparece no topo da lista. Adapte para situações que você for baixar algo que não queira ficar esperando.

   É necessário subtituir as variáveis parte e inteiro pelo nome do arquivo que está baixando. Recomendo ir no diretório que o gerenciador baixa e com o mouse copie o nome do arquivo e cole como no exemplo do script abaixo.

   Como o gerenciador cria dois arquivos (nome.extensão e nome.extensão.part), a grande sacada é testar se nome.extensão é diferente de zero byte. Somente ao terminar nome.extensão.part o tamanho é transferido para nome.extensão deixando de ser um arquivo vazio.

   O comando until testa se nome.extensão deixou de ser um arquivo vazio. Enquanto isso não sai desta condição, é exibido o tamanho do arquivo a cada 2 segundos através do comando sleep. O comando awk que pega um campo específico (no caso o tamanho do arquivo) é usando para a variável parte.

   Terminado o loop until existe outro test que verifica se o arquivo deixou de ter zero byte e o shutdown entra em ação. Por esta razão, o script tem que ser executado pelo root.

!/bin/bash
# v1.0: 10/21/2006
# v1.1: 04/11/2012

parte="LACUNA COIL - Enjoy The Silence.flv.part"
inteiro="LACUNA COIL - Enjoy The Silence.flv"

until test -s "$inteiro"
  do
   status=`du "$parte" | awk '{printf "%2s\n",$1}'`
   echo Download em andamento: $status kB
   sleep 2
   continue
  done

test -s "$inteiro"
   /sbin/shutdown -h now

sexta-feira, 9 de março de 2012

Exibindo os bytes de download e upload a cada 1 segundo

  Considero extremamente importante saber como anda o tráfego de informações que passa pelo meu computador. Os motivos são:
  • Questão da segurança: certificar-se de que está coerente com as atividades que você desempenha. Em outras palavras, nenhum processo está desviando seus dados.
  • Saber se sua rede está ativa.
  • Curiosidade. Já observou as taxas de transferências quando os arquivos são transmitidos por protocolos diferentes como http, ftp, scp, netbios entre outros?
  O script a seguir monitora a cada segundo a taxa de upload e download. A seguir os comentários das tarefas realizadas.
  O laço for mostra 999 vezes, mas pode ser interrompido com CTRL + C. Altere para a quantidade que convém.
  A variável antes_down pergunta para o ifconfig a quantidade de bytes total que passou pela placa de rede. O comando grep exibe a linha que contém o texto "TX bytes", o tr substitui símbolos por espaço em branco (' ') e então o awk exibe o terceiro campo ($3). A mesma ideia é aplicada para a variável antes_up. Em seguida, aguarda 1 segundo.
  Os mesmos procedimentos são aplicados para depois_down e depois_up. Como levou 1 segundo para coletar as novas informações, o programa subtrai a os valores de todos os bytes que passaram pela placa de rede neste intervalo. De posse dessa diferença, basta imprimir com o comando echo.

#!/bin/bash
# Murilo Fujita <murilofujita@gmail.com>
# Exibe taxas de download e upload
# Created in February, 20th, 2010

for i in `seq 999`
  do
   antes_down=`ifconfig wlan0 | grep "TX bytes" | tr '[:punct:]' ' ' | awk '{printf "%s\n",$3}'`
   antes_up=`ifconfig wlan0 | grep "TX bytes" | tr '[:punct:]' ' ' | awk '{printf "%s\n",$9}'`
   sleep 1
   depois_down=`ifconfig wlan0 | grep "TX bytes" | tr '[:punct:]' ' ' | awk '{printf "%s\n",$3}'`
   depois_up=`ifconfig wlan0 | grep "TX bytes" | tr '[:punct:]' ' ' | awk '{printf "%s\n",$9}'`
   printf "download: `echo "$depois_down-$antes_down" | bc | awk '{printf "%6s",$1}'` upload: `echo "$depois_up-$antes_up" | bc | awk '{printf "%6s\n",$1}' `\n"
   download=`echo "$depois_down-$antes_down" | bc`
   upload=`echo "$depois_up-$antes_up" | bc`
  done

  Neste caso foi usando o device wlan0. Lembre-se de substituir pelo nome que seu sistema está usando: ethX, pppX (comunicações seriais), entre outros.
  Bom proveito!

segunda-feira, 13 de fevereiro de 2012

Backup de um banco de dados e recuperação de tabelas

  Se você tem o banco de dados MySQL instalado na sua máquina, passe a fazer o backup! Dependendo de período que são inseridos dados, você determina a frequência que são suas informações são salvas.

  Certa vez confirmei o comando update sem dizer em qual id deveria acontecer a mudança e todas as linhas de uma certa coluna foram modificadas. Aí é tarde!
  O primeiro passo é o procedimento para o backup. Eu uso o crontab e ele é ativado uma vez por dia. Para chamar o crontab, digite:

  $ crontab -e

  Para que todos os dias, às 21h, minha tabela ponto do banco de dados banco seja salva no arquivo ponto.sql do diretório documentos, digite:

  00 21 * * * mysqldump -uroot --password=xxx banco ponto > ~/documents/ponto.sql

  Então, quando acontecer um acidente, recupere sua tabela digitando este comando no Shell:

  mysql -uroot -p banco < ponto.sql

  Sendo:

  • -u: usuário. No caso, o root.
  • -p: a senha. Como não foi fornecida na linha de comando, um prompt pedirá a autenticação.
  • banco: o nome do banco onde a tabela será restaurada.
  • ponto.sql: o arquivo que todos os dias sofreu um dump salvando todas as informações de uma tabela dentro de um banco.

  Nada de despero! Tudo será restabelecido em segundos!

quarta-feira, 21 de dezembro de 2011

Soma dos débitos do extrato do Banco do Brasil

  Eu sempre fiz o controle dos meus gastos e para ter a resposta do custo do mês, desenvolvi um script que soma todas as parcelas debitadas da minha conta corrente do banco do Brasil.
  Começou de uma forma bem simples. Um conjunto de entradas e saídas era sequênciado da seguinte forma:
  • o comando cat exibe todo o conteúdo
  • o comando cut mostrar do caracter 60 em diante
  • o grep exibe somente as linhas de débito (indicadas por um "D")
  • o awk exibe somente o primeiro campo da linha de débito
  • o comando sed substitui vírgula por ponto
  • o paste e suas opções transformam a listagem de coluna para linha e insere um sinal de mais (+) entre os números
  • o comando bc faz a soma
Observação: a necessidade de substituir a vírgula por ponto é devido ao bc requerer este símbolo para interpretar valores fracionários.

  Até Maio de 2007 e anterior a esta data, o comando era:

cat extrato_07_05.txt | cut -c 60- | grep D | awk '{printf("%s\n",$1)}' | sed 's/,/./g' | paste -s -d"+" | bc

  De Junho de 2007 até o presente, usa-se:

cat extrato_07_06.txt | cut -c 71- | grep D | awk '{printf("%s\n",$1)}' | sed 's/,/./g' | paste -s -d"+" | bc

  Estes comandos combinados funcionavam para valores até 999 reais. A partir dos valores contendo 4 dígitos, este é exibido com um ponto separador de milhar. Exemplo: 1.000,00   Como consequência, causa um erro no interpretador do bc. Para resolver o problema, apresento a solução abaixo bem como os comentários.

#!/bin/bash
# Variáveis mes e ano capturam a data
mes=`date +%m`
ano=`date +%Y`

# Exige o número do arquivo ao executar o comando
 if [ $# != 1 ]
   then
    echo "Argumento: nome_de_arquivo"
    exit 1
  fi

cat $1 | cut -c 71- | grep D | awk '{printf("%s\n",$1)}' | sed 's/,/./g' > extrato.txt
# Conta a quantidade de linhas ao ler o arquivo
total_linhas=`wc -l extrato.txt | cut -f1 -d" "`
# Apaga o arquivo
rm -f gasto_"$ano"_$mes.txt
# Loop para executar um conjunto de ações até o final do arquivo
for i in `seq $total_linhas`
  do
# Analisa cada linha e conta o número de pontos (separadores de milhar e da parte fracionária)
   um_ponto=`head -n $i extrato.txt | tail -n 1 | tr -cd "." | wc -c`
   if [ $um_ponto = 1 ]
    then
# Se for apenas 1 ponto, simplesmente exibe o valor
    head -n $i extrato.txt | tail -n 1
   fi

# Analisa cada linha e conta o número de pontos (separadores de milhar e da parte fracionária)
  dois_pontos=`head -n $i extrato.txt | tail -n 1 | tr -cd "." | wc -c`
   if [ $dois_pontos = 2 ]
    then
# Se forem 2 pontos, substitui o primeiro ponto por nada (ponto aparece somente para parte fracionária)
    echo `head -n $i extrato.txt | tail -n 1` | sed 's/\.//'
   fi
  done >> gasto_"$ano"_$mes.txt
paste -s -d"+" gasto_"$ano"_$mes.txt | bc

sábado, 29 de outubro de 2011

Horas trabalhadas usando MySQL

  Na primeira publicação deste assunto com o título Horas trabalhadas armazenadas no MySQL eu abordei sobre um script que registra as horas trabalhadas. Passado um tempo tive que fazer alguma alterações para adequar meu sistema ao modelo usado pela empresa.
  As modificações são:
  • a substituição das colunas dia, mês e ano por data que agrupa as três informações;
  • pergunta se é um dia útil. Se a resposta for negativa, a forma como a variável saldo é calculada muda para diferenciar a quantidade de horas extras.

  A seguir está a descrição da tabela de acordo com o comando do MySQL describe ponto;

id int(11) NO PRI NULL auto_increment
in1 time YES NULL
out1 time YES NULL
in2 time YES NULL
out2 time YES NULL
tempo time YES NULL
saldo time YES NULL
data varchar(11) YES NULL
comment text YES NULL

  O script encontra-se abaixo bem como seus comentários para facilitar o entedimento das instruções.

#!/bin/bash

# Entrada dos horários
read -p "Entrada manhã: " in1
read -p "Saída almoço: " out1
read -p "Entrada tarde: " in2
read -p "Saída tarde: " out2

read -p "Data de hoje? [Y/n] " resp
# Entende que Y, y, S, s ou Enter signifiquem uma resposta a data de hoje
if [ "$resp" = Y -o "$resp" = y -o "$resp" = S -o "$resp" = s -o ! -n "$resp" ]
  then
   dia=`date +%d`
   mes=`date +%m`
   ano=`date +%Y`
   data=$dia/$mes/$ano
else
  read -p "dia " dia
  read -p "mes " mes
  read -p "ano " ano
  data=$dia/$mes/$ano
fi

# Subtrai o horário da manhã
manhaSQL=`mysql -uroot -psenha << SQL
SELECT SUBTIME('$out1','$in1');
QUIT
SQL`

# Filtra a resposta anterior ficando apenas um valor do tipo HH:MM:SS
manha=`echo $manhaSQL | awk '{printf "%s\n",$2}'`

# Subtrai o horário da tarde
tardeSQL=`mysql -uroot -psenha << SQL
SELECT SUBTIME('$out2','$in2');
QUIT
SQL`

# Filtra a resposta anterior ficando apenas um valor do tipo HH:MM:SS
tarde=`echo $tardeSQL | awk '{printf "%s\n",$2}'`

# Soma o horário da manhã e da tarde
tempoSQL=`mysql -uroot -psenha << SQL
SELECT ADDTIME('$manha','$tarde');
QUIT
SQL`

# Filtra a resposta anterior ficando apenas um valor do tipo HH:MM:SS
# Tempo total do dia (manhã + tarde)
tempo=`echo $tempoSQL | awk '{printf "%s\n",$2}'`

# Horas trabalhadas menos 9h para calcular o saldo de horas
tempo_add=`mysql -uroot -psenha << SQL
SELECT SUBTIME('$tempo','09:00:00');
QUIT
SQL`

read -p "Dia útil? [Y/n] " dia_util
if [ "$dia_util" = Y -o "$resp" = y -o "$resp" = S -o "$resp" = s -o ! -n "$dia_util" ]
  then # saldo de horas trabalhadas para dia útil
   saldo_horas=`echo $tempo_add | awk '{printf "%s\n",$2}'`
  else # saldo de horas trabalhadas para sábados, domingo e feriados
   saldo_horas="$tempo"
fi

read -p "Comentário? " comment

echo "$in1" "$out1" "$in2" "$out2" "$tempo" "$saldo_horas" "$data" "$comment"

cmd=`/usr/bin/mysql -uroot -psenha << SQL
   use imf
   insert into ponto (in1, out1, in2, out2, tempo, saldo, data, comment) values
    ("$in1", "$out1", "$in2", "$out2", "$tempo", "$saldo_horas", "$data", "$comment");
  quit
  SQL`

  Através do comando

select * from ponto where id > numero;

sendo numero o id que corresponde a uma data que procuro, o MySQL devolve a listagem de um determinado intervalo que através da ferramenta txt2tags crio as células que são facilmente portáveis para o Excel com um simples CTRL + C e CTRL + V.