Mostrando postagens com marcador utils. Mostrar todas as postagens
Mostrando postagens com marcador utils. Mostrar todas as postagens

quarta-feira, 25 de novembro de 2015

Implementação Entropia

    Antes de iniciarmos o estudo do Ganho de Informação, vamos ver a implementação de um método mais genérico que calcula a entropia de um conjunto de valores, vamos ao código:


public class Entropy {
    /**
     * Metodo que dado um conjunto de valores calcula e entropia deste
     * conjunto.
     * 
     * @param values  - Lista de valores que se quer calcular a entropia
     * @return    - A entropia do conjunto
     */
     public static Double calculateEntropy(List values) {
         Map map = new HashMap();
  
         // Somatório para calcular a ocorrencia de cada valor
         for (String sequence : values) {
             // Preenche o mapa com a key sendo o valor e o value a quantidade
             if (!map.containsKey(sequence)) {
                 map.put(sequence, 0);
             }
             // Adiciona um a quantidade
             map.put(sequence, map.get(sequence) + 1);
         }

         // Calcula a entropia
         Double result = 0.0;
  
         // Itera pelo conjunto de possíveis valores
         for (String sequence : map.keySet()) {
             // Calcula a frequencia que o registro aparece, a probabilidade do valor
             // aparecer na base de dados informada
             Double frequency = (double) map.get(sequence) / values.size();
             // Faz o calculo da entropia
             result -= frequency * (Math.log(frequency) / Math.log(2));
         }

         // Retorna o valor
         return result;
     }
}


    Podemos ver através do código acima que a implementação do calculo de entropia é bastante simples quando se entende o funcionamento da equação e consegue ver além das notações matemáticas.

quinta-feira, 29 de outubro de 2015

Padrões de Comentários

Bom pessoal, hoje vou falar um pouco sobre as boas práticas na hora de comentar e documentar um código no ambiente corporativo. Primeiramente gostaria de deixar bem claro o fato de que o mundo acadêmico e o mundo corporativo têm expectativas e realidades muito diferentes, portanto os padrões que foram aprendidos dentro de uma universidade, ou através de livros, nem sempre são bem vistos no mundo dos negócios.

Por exemplo, os códigos fontes que são postados aqui nesse blog possuem uma quantidade excessiva e desnecessária de comentários, do ponto de vista empresarial, e o porquê disso é o que iremos abordar abaixo. Porém, esse excesso aqui cometido é proposital para que possamos ser o mais didático possível e para que qualquer um possa entender o funcionamento dos nossos códigos, aí você me pergunta, “Mas esse não é o objetivo dos comentários em qualquer situação? Inclusive no ambiente corporativo. ”, para responder essa pergunta vamos levar em conta os seguintes pontos:


1) Comentários não são legendas

// Passa por todas as bananas no cacho
foreach(banana b no cacho) {
    monkey.eat(b);  //Faz o macaco comer a banana
}

Você não precisa explicitar o que faz cada linha do seu código, após um período não tão grande de tempo, qualquer programador poderá ler linhas de códigos quase tão bem quanto entende a sua linguagem nativa. A exceção para esses casos são linguagens de programação muito próximas a linguagem de máquina, como por exemplo Assembly.

2) Comentários não são projetos de arte 


Esta é uma péssima prática de programação muito disseminada por exemplos em livros e copyright de códigos open source, pois são formas de chamar a atenção do leitor. Como por exemplo:

/*
   _     _      _     _      _     _      _     _      _     _      _     _
  (c).-.(c)    (c).-.(c)    (c).-.(c)    (c).-.(c)    (c).-.(c)    (c).-.(c)
   / ._. \      / ._. \      / ._. \      / ._. \      / ._. \      / ._. \
 __\( Y )/__  __\( Y )/__  __\( Y )/__  __\( Y )/__  __\( Y )/__  __\( Y )/__
(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)
   || M ||      || O ||      || N ||      || K ||      || E ||      || Y ||
 _.' `-' '._  _.' `-' '._  _.' `-' '._  _.' `-' '._  _.' `-' '._  _.' `-' '._
(.-./`-'\.-.)(.-./`-'\.-.)(.-./`-'\.-.)(.-./`-'\.-.)(.-./`-'\.-.)(.-./`-'\.-.)
 `-'     `-'  `-'     `-'  `-'     `-'  `-'     `-'  `-'     `-'  `-'     `-'
 
                 -It's Monkey Business Time! (Version 1.5)
*/

Você pode achar que nunca faria algo parecido, mas repare que o código abaixo não é muito diferente, apenas mais trabalhados.

+------------------------------------------------------------+
 | Module Name: classMonkey                                   |
 | Module Purpose: emulate a monkey                           |
 | Inputs: Bananas                                              |
 | Outputs: Grunts                                            |
 | Throws: Poop                                               |
 +------------------------------------------------------------+


Programadores adoram enfeitar os seus comentários, como no exemplo acima, quando estão cansados e precisam parar de pensar no problema que estão tentando resolver. O problema com essa prática é que ela gera uma perda de horas para a manutenção desses comentários para qualquer um que vá trabalhar com eles. Mesmo programadores que não gostam disso, perdem tempo na manutenção dos layouts, pois se incomodam com a inconsistência ou a quebra do layout. Você pode até achar que isso não é verdade, mas aposto que a quebra de layout na coluna da direta está te incomodando.

 3) Cabeçalhos: Incomodo ou Ameaça?


Cabeçalhos tanto de classes como de métodos são ruins pois eles abrem uma brecha para que se dê nomes ruins aos métodos, pois são uma desculpa para não colocar nomes que representem as funcionalidades. Além disso, esses códigos nunca são atualizados o que pode levar ao leitor a uma ideia errada do real funcionamento do código, caso alguém leia esse comentário, o que raramente ocorre.

Óbvio que existem exceções para esse caso, uma delas é o Java que disponibiliza o JavaDoc que possibilita a criação de cabeçalhos, especialmente formatados, que disponibilizam essa documentação no momento que o desenvolvedor usa o auto complete. Além de ser possível gerar uma documentação completa do sistema através de ferramentas que extraem esses comentários.

 4) Comentários não são controles de código

// method name: pityTheFoo (Incluido para aumentar a redundancia)
// created: Feb 18, 2009 11:33PM
// Author: Bob
// Revisions: Sue (2/19/2009) - Lengthened monkey's arms
//            Bob (2/20/2009) - Solved drooling issue
 
void pityTheFoo() {
     ...
}

Hoje não existem empresas que desenvolvam um sistema sem o uso de softwares de controle de versão e esse software é perfeitamente capaz de armazenar e atualizar as informações que são colocadas em comentários como o mostrado acima, isso sem que seja preciso sujar o código e obrigar os outros desenvolvedores a gastar seu tempo para atualiza-los. Obviamente, os desenvolvedores que gostam desse tipo de comentário geralmente são aqueles que não colocam nenhuma informação, ou informações imprecisas, nos commits de código.

5) Comentários são sinais de códigos ruins


A quantidade excessiva de comentários são geralmente um indicativo que o código precisa passar por um processo de refactor. Sempre que você achar que seu código precisa de comentários para ser entendido, você deve se fazer a seguinte pergunta "Como posso refatorar meu código para que seu objetivo fique claro?".

Conclusão:

Com esses 5 pontos podemos responder a pergunta, “Mas esse não é o objetivo dos comentários em qualquer situação? Inclusive no ambiente corporativo. ”, códigos quando utilizados corretamente podem e são muito uteis, mas devemos ter cuidado com excessos ou comentários desnecessários pois isso pode gerar um gasto de tempo de outros programadores para manter atualizados e com o formato correto, os nossos comentários. Segue abaixo algumas dicas de como podemos evitar o uso de comentários desnecessários:

1) Sempre prefira nomes significativos a comentários

// Before
// Calculate monkey's arm length
// using its height and the magic monkey arm ratio
double length = h * 1.845; //magic numbers are EVIL!
 
// After - No comment required
double armLength = height * MONKEY_ARM_HEIGHT_RATIO;

2) Utilize entradas e saídas tipadas para os seus métodos

// Before
// input parameter monkeysToFeed:
// DataSet with one table containing two columns
//     MonkeyID (int) the monkey to feed
//     MonkeyDiet (string) that monkey's diet
    void feedMonkeys(DataSet monkeysToFeed) {
}
 
//  After: No comment required
    void feedMonkeys(Monkeys monkeysToFeed) {
}

3) Extraia o trecho que necessita de comentários para outro método

// Before
// Begin: flip the "hairy" bit on monkeys
foreach(monkey m in theseMonkeys) {
    // 5-6 steps to flip bit.
}
// End: flip the "hairy" bit on monkeys
 
// After No comment required
flipHairyBit(theseMonkeys);

4) Evite um encadeamento grande de estruturas de repetição ou decisão

		} // ... if see evil
      } // ... while monkey do.
    } // ... if monkey see.
  } // ... class monkey
} // ... namespace primate

Espero com esse artigo ter ajuda programadores novos e veteranos a produzirem códigos melhores e principalmente a saber diferenciar o estilo de programação que usamos ao criar tutoriais, entregar trabalhos na faculdade, ou fazermos códigos para nós mesmos, do estilo que deve ser usado no ambiente corporativo.

Fonte:

quarta-feira, 14 de outubro de 2015

Impressão de Árvores em Java

Impressão de Árvores 

Após o início dos desenvolvimentos do algoritmo ID3, comecei a pesquisar algum método pronto para a impressão de árvores, tanto em arquivo quanto na saída padrão, em Java com a restrição de cada ramo poderia ter N filhos, para que o tempo de desenvolvimento diminuísse. Porém, todas as implementações que encontrei eram desenvolvidas visando árvores binárias e na sua grande maioria, formadas por códigos confusos e desnecessariamente complexos. 

Por isso decidi deixar aqui, em um post a parte, a minha versão para impressão de árvores em arquivo, independente do número de folhas ou do número de níveis que a árvore possuí. Segue abaixo o esquema no qual a árvore será impressa: 

Folha-Nível1 
        Folha1-Nível2 
        Folha2-Nível2 
                Folha1-Nível3
                Folha2-Nível3 
                        Folha1-Nível4 
                        Folha2-Nível4 
                Folha3-Nível3 
            Folha4-Nível3 
        Folha3-Nível2 
            Folha5-Nível3
        Folha4-Nível2  

(i) Notar que o nível em que a folha se encontra é determinado pela tabulação. 

Dessa forma ficou fácil analisar as saídas do algoritmo, sem ter que ficar utilizando a funcionalidade de debug do Eclipse. Segue abaixo o código implementado:

public class FileWriter {
 /**
  * Função recursiva para impressão da árvore resultante dos algoritmos de
  * árvore de decisão.
  * 
  * @param root  - Nó que será impresso
  * @param writer - Arquivo onde será impresso
  * @param treeLevel - Nível em que a árvore se encontra
  */
    public static void writeTree(Node root, PrintWriter writer, int treeLevel) {
        String line = "";
  
        //Verifica se existe apenas o resultado ou se existe um outro campo a ser avaliado
        if(root.getTestAttribute().getName().isEmpty()) {
            line = root.getTestAttribute().getValue();
        } else {
            line = root.getValue() + " -> " + root.getTestAttribute().getName();
        }
  
        //Imprime o nível do nó
        for(int i = 0; i < treeLevel * 2; i++){
            writer.print("\t");
        }
  
        //Aumenta em 1 o nível da árvore
        treeLevel++;
  
        writer.print(line + "\n");
  
        //Imprime de forma recursiva as folhas desse nó
        if(root.getChildren() != null) {
            for(Node child : root.getChildren()) {
                writeTree(child, writer, treeLevel);
            }
        }
  
        return;
    }
}

segunda-feira, 12 de outubro de 2015

Algumas definições - Classes Úteis

Algumas definições

Para o desenvolvimento dos algoritmos vou criar algumas classes genéricas, e irei acrescentar funcionalidades a elas conforme o necessário. Para o algoritmo ID3, foram criadas as seguintes classes:

public class Record {
    
    private ArrayList<attribute> attributes;

    publicArrayList<attribute> getAttributes() {
        return attributes;
    }
    
    public void setAttributes(ArrayList<attribute> attributes) {
        this.attributes = attributes;
    }

}
Classe criada para abrigar os exemplos do conjunto de treinamento e seus respectivos atributos.
public class Attribute {

    private String name;
    private double value;
    private boolean isUnknown;
 
    public Attribute(String name, double value) {
        this.name = name;
        this.value = value;
        isUnknown = false;
    }
 
    public Attribute(String name, String value) {
        this.name = name;
        try {
            this.value = Double.valueOf(value);
            this.isUnknown = false;
        }
        catch(NumberFormatException nfe) {
            this.value = -1;
            this.isUnknown = true;
        }
    }
 
    public void setName(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
 
    public void setValue(double value) {
        this.value = value;
    }

    public double getValue() {
        return value;
    }
 
    public void setUnknown(boolean isUnknown) {
        this.isUnknown = isUnknown;
    }

    public boolean isUnknown() {
        return isUnknown;
    }
}
Classe utilizada para guardar as informações referentes ao atributo, como por exemplo, nome, valor, etc. Como se trata de uma classe com propósitos genéricos o único tipo de dados aceito pela classe é o double, para aceitar os dados processados pelo ID3, que só aceita constantes, foi utilizado a classe descrita abaixo:
public class DiscreteAttribute {
    private Map<String, Integer> values;

    public DiscreteAttribute() {
        values = new HashMap<String, Integer>();
    }

    public void setValue(String key) {
        if(!values.containsKey(key)) {
            values.put(key, values.size());
        }
    }

    public int getValue(String key) {
        return values.get(key);
    }
}
Classe que funciona como um enum dinâmico e tem como propósito transformar os valores discretos do conjunto de dados de treinamento do ID3 em valores numéricos.