Unconfigured Ad Widget

Collapse

Anúncio

Collapse
No announcement yet.

hashantipatterns

Collapse
X
 
  • Filter
  • Tempo
  • Show
Clear All
new posts

  • Font Size
    #1

    Perl hashantipatterns

    Hashes Anti Patterns

    Quando é que que devemos usar hashes?

    Um dos primeiros exemplos sobre a necessidade de uso de hashes é quando temos um conjunto de variáveis diferentes para representar propriedades diferentes para determinado objecto. Por exemplo, se queremos descrever duas ligações a base de dados podemos escrever:

    $main_server_addr = "some.server.url";
    $main_server_port = 4000;
    $main_server_username = "me";

    $backup_server_addr = "back.server.url";
    $backup_server_port = 4444;
    $backup_server_username = "me";

    Este tipo de código tem dois problemas principais. Primeiro, estou a poluir a tabela de símbolos do Perl. Em segundo lugar, se precisar de passar esta informação para uma função, terei de usar três parâmetros diferentes:

    $connection = make_connection($main_server_addr,
    $main_server_port,
    $main_server_username);

    No entanto, poderia ter escrito a mesma informação usando duas tabelas de hashing:

    $main_server = { addr => "some.server.url",
    port => 4000,
    username => "me" };
    $backup_server = { addr => "back.server.url",
    port => 4444,
    username => "me" };

    Este código, além de ser bastante mais limpo, define apenas duas variáveis que podem ser facilmente passadas para uma função:

    $connection = make_connection($backup_server);

    Podia ter usado directamente uma tabela de hashing em vez de uma referência:

    %main_server = ( addr => "some.server.url",
    port => 4000,
    username => "me" );

    e chamar a função com

    $connection = make_connection(\%main_server);

    No entanto, se eu tiver mais que um servidor de backup talvez tenha melhores resultados utilizando uma tabela de hashing de tabelas de hashing:

    %servers = (
    main => { addr => "some.server.url",
    port => 4000,
    username => "me" },
    backup1 => { addr => "back.server.url",
    port => 4444,
    username => "me" },
    )

    Outro exemplo do uso de hashes é a implementação de uma estrutura case:

    if ($x eq 'pdf') { $type = 'application/pdf' }
    elsif ($x eq 'xml') { $type = 'text/xml' }
    elsif ($x eq 'html') { $type = 'text/html' }
    ...
    else { $type = 'unknown' }

    Imaginem-me agora a escrever todos os tipos existentes. As tabelas de hashing podem ajudar definindo uma associação entre a extensão dos ficheiros e o tipo do documento. Assim, o nosso código torna-se tão simples quanto consultar uma tabela de hashing:

    %type = ( 'pdf' => 'application/pdf',
    'xml' => 'text/xml',
    'html' => 'text/html', );

    if (exists($type{$x})) {
    $type = $type{$x}
    } else {
    $type = 'unknown'
    }

    Embora tenha escrito este código de forma mais limpa e usando mais linhas que no exemplo anterior, a verdade é que este é bastante mais fácil de manter. Uma estrutura bastante semelhante a esta é a conhecida como dispatch tables. Se em vez de querer apenas o nome do tipo de ficheiro eu quisesse invocar um parser específico, podia usar uma abordagem semelhante:

    %type = ( 'pdf' => \&parse_pdf,
    'xml' => \&parse_xml,
    'html' => \&parse_html, );

    if (exists($type{$x})) {
    $type{$x}->($filename)
    } else {
    die "Unknown filetype"
    }

    Técnicas no uso de Hashes

    Supondo que temos de contar as palavras de um texto e, para simplificar o exemplo, vamos considerar que o ficheiro de texto tem as palavras separadas por espaços. Uma abordagem inicial para a solução passaria por:

    while(<>) {
    chomp($_);
    my @words = split /\s+/, $_;
    for my $word (@words) {
    if (exists{$hash{$word}}) {
    $hash{$word}++
    } else {
    $hash{$word}=0
    }
    }
    }

    Este exemplo esquece-se de que o Perl converte valores indefinidos automaticamente para o valor nulo, e que portanto, não preciso de inicializar os valores da tabela de hashing:

    while(<>) {
    chomp;
    for (split /\s+/) {
    $hash{$_}++
    }
    }

    Como exemplo seguinte, vamos considerar dois arrays, ambos do mesmo tamanho, e que queremos criar uma tabela de hashing a associar os primeiros elementos de cada um dos arrays, a associar os segundos elementos, os terceiros, e por aí fora. Um programador pouco experiente escreveria qualquer coisa como:

    my $i = 0;
    for my $key (@first) {
    $hash{$key} = $second[$i];
    $i++;
    }

    enquanto podia ter escrito simplesmente:

    @hash{@first} = @second;

    Outras vezes, se quero tirar os elementos duplicados de um array, posso utilizar uma tabela de hashing de forma muito semelhante à anterior. Como as tabelas de hash não têm chaves repetidas, se executar:

    @hash{@array} = @array;

    fico com os elementos do array como chaves da tabela de hashin, sem valores repetidos. Uma forma mais complicada seria associar a cada elemento do array um valor fixo, por exemplo, o valor 1:

    @hash{@array} = (1) x @array;

    Os exemplos seguintes mostram a utilidade das tabelas de hashing para a gestão de conjuntos:

    sub union {
    my ($set1, $set2) = @_;
    my %h;
    @h{@$set1} = @$set1;
    @h{@$set2} = @$set2;
    return [keys %h];
    }

    sub intersection {
    my ($set1, $set2) = @_;
    my %h;
    @h{@$set1} = @$set1;
    return [grep {exists($h{$_})} @$set2];
    }

    Erros comuns

    Um dos erros mais comuns na utilização de tabelas de hashing é a atribuição do valor undef a uma chave para remover essa chave da tabela.

    $hash{$key} = undef;

    Isto não irá apagar a chave, mas mudar o seu valor. Isto significa que a chave continua a existir e que o comando keys continua a retornar a chave em cause. A forma correcta para a remover da hash é usar a função delete:

    delete($hash{$key});

    Outro erro comum é o uso de tabelas de hashing aninhados sem o devido cuidado de verificar se as chaves existem. Se executar:

    if ($hash{a}{b}) { ... do something ... }

    o que na verdade estou a fazer é:

    $hash{a} = {} unless exists $hash{a};
    if ($hash{a}{b}) { ... do something ... }

    Isto significa que da próxima vez que pedir as chaves da tabela de hashing ela irá retornar a chave “a” mesmo que ela nunca tenha sido definida. A isto é chamado auto-vivificação. Se eu não quiser este comportamento (o mais certo), então usaria a função exists para verificar a existência das chaves:

    if (exists($hash{a}{b})) { .. do something .. }

    Quando se criam tabelas de hashing com muitas chaves, sempre que executo:

    for (keys %hash) { ... }

    o Perl irá criar um novo array com todas as chaves do hash. Se precisar de poupar memória, o mais certo é usar um ciclo while com o comando each.

    while( ($key,$value) = each %hash) {
    ...
    }

    O each é um iterador, que guarda informação sobre a posição actual no hash, de forma a que sempre que seja chamado retorne um par diferente até que a tabela de hashing termine. As tabelas de hashing são os tipos de dados mais comuns em Perl, e um dos mais eficazes. Podem ser usados simplesmente como estruturas de dados mas também como ferramentas: remoção de duplicados, criação de conjuntos, contagem de ocorrências… Além disso, também são muito fáceis de usar. Para começar a usar é só preciso lembrar-se da sintaxe correcta para armazenar e consultar valores, e de uma ou duas formas para iterar sobre eles.
    Last edited by bolinhaxp; 18-12-2009, 19:27.

  • Font Size
    #2
    pra quem esta fazendo curso
    de perl deve ajudar =]



    Durante os tempos de mentiras universais, dizer a verdade se torna um ato revolucionário

    Comment

    X
    Working...
    X