User Tools

Site Tools


perl:arraysantipatterns

Arrays Anti Patterns

Há muitas formas de programar numa linguagem. Quanto mais versátil é a linguagem, mais idiomas se podem encontrar do seu uso. No entanto, existem idiomas que não devem ser utilizados em determinadas linguagens, não por estarem errados, mas porque não tiram partido das funcionalidades básicas da linguagem, e portanto, são ineficientes e difíceis de ler. Este artigo mostra algumas construções que não devem ser utilizadas para a gestão de arrays em Perl.

Este artigo é uma tradução de um outro escrito por mim para a The Perl Review!

Verificar a existência de um valor num array:

Como primeiro exemplo, analisemos esta função que verifica se determinado elemento existe em determinado array.

sub in_array {

    my ($check, @array) = @_;
    my $retval = 0;
    foreach my $test (@array){
      if($check eq $test){
        $retval =  1;
      }
    }
    return $retval;
  }

O código é fácil de ler. Primeiro é inicializada uma variável a 0 (falso), a indicar que ainda não encontramos o valor no array. Quando o encontrarmos a variável passará a 1 (verdadeiro). Esta abordagem não é eficiente já obriga a uma cópia do array (no cíclo for), o array é passado por valor e não por referência, e mesmo depois de encontrado o valor o ciclo irá continuar até que o array termine. Embora fosse fácil corrigir alguns destes erros (passar o array por referência e retornar da função assim que se encontre o valor em causa), a função continuaria a não ser eficiente e a não utilizar as potencialidades do Perl. No entanto, um programador típico escreveria algo como:

  sub in_array {
    my $check = shift;
    return grep { $check eq $_ } @_;
  }

Claro que este exemplo continua a não ser muito eficiente já que o array continua a ser passado por valor. Mas se repararmos, a nossa função é um simples grep que pode ser utilizada directamente onde precisamos de encontrar o elemento, e portanto, poupando a passagem de parâmetros e a chamada a uma nova função.

Eliminação de repetidos

Uma tarefa habitual é remover elementos duplicados de um array (transformar uma sequência num conjunto de elementos). Um programador pouco conhecedor de Perl, escreveria algo como:

  sub array_unique {
    my (@array) = @_;
    my @nodupes;
    foreach my $element (@array){
      if(not in_array($element, @nodupes)){
        @nodupes = (@nodupes, $element);
      }
    }
    return @nodupes;
  }

Porque é que este código é mau? Por muitas e demasiadas razões. Uma delas é completamente ineficiente para arrays grandes:

   @nodupes = (@nodupes, $element);

Este código irá criar um array novo que é uma cópia do @nodupes, adicionar-lhe o elemento, e guardar de novo, apagando o array anterior. Tudo isto gasta recursos, especialmente se pensarmos num grande array. Para adicionar um elemento ao fim de um array existe uma função Perl chamada push que trata do assunto:

   push @nodupes, $element;

Esta solução é muito mais rápida, e muito mais legível. Mas não era esta a única alteração que um bom programador Perl faria. Possivelmente implementaria uma função muito semelhante a:

  sub array_unique {
    my %h;
    @h{@_} = @_;
    return keys %h;
  }

Uma função muito mais pequena (que ainda pode ser mais eficiente se passarmos os arrays por referência) e muito mais eficiente (embora crie uma hashtable do mesmo tamanho do array). Então qual a ideia deste código? Inicializar uma hashtable, toda de uma única vez, usando um array. A linha

@h{@_} = @_;

cria para cada elemento de @_ uma entrada na hashtable a apontar para ele mesmo. Como estamos a utilizar uma hashtable, não temos chaves duplicadas, e portanto, o conjunto das chaves é o que queremos retornar na nossa função.

Histograma de ocorrências

Supondo que queremos contar quantas vezes cada elemento de um array ocorre nesse mesmo array.

  sub array_count_values{
    my(@array) = @_;
    my @unduped = array_unique(@array);
    my %rethash;
    foreach my $mykey (@unduped){
      $rethash{$mykey} = 0;
    }
    foreach my $key (@array){
      my $value = $rethash{$key};
      $value++;
      $rethash{$key} = $value;
    }
    return %rethash;
  }

Esta é outra solução completamente despropositada para quem usa Perl. O primeiro grande erro é não saber que o Perl inicializa os valores de uma hashtable a zero quando a chave não existe, e portanto, o ciclo inicial é desnecessário. Também não é necessário ir buscar elementos a uma hashtable para lhes adicionar uma ocorrência. Uma solução bastante mais elegante seria:

    sub array_count_values {
      my %rethash = ();
      $rethash{$_}++ for @_;
      return %rethash
    }

Concatenação de arrays

Esta foi a função que mais me espantou (sim, não foi inventada por mim, encontrei-a mesmo, como a todas as outras que aqui mostro, num módulo publicado no CPAN). Se têm dois arrays e os querem juntar, o que fariam? Bem, o autor desta função acha que o seguinte seria uma boa solução:

  sub array_merge{
    my(@array1, @array2) = @_;
    foreach my $element (@array2){
      @array1 = (@array1, $element);
    }
    return @array1;
  }

Além de que esta função tem o mesmo problema de não usar o comando push, tem o problema de ser completamente despropositada. Se realmente querem concatenar dois arrays usem antes:

  sub array_merge {
    return @_
  }

Mas só se quiserem mesmo ter uma função. Isto porque qualquer programador Perl iria preferir escrever directamente no código:

   @newlist = (@list1, @list2);

Concluindo

Antes de terminar queria lembrar que a ideia deste artigo é ajudar a aprender Perl e não gozar com quem não sabe. Não tenham medo em programar à vossa maneira. Tentem é ir aprendendo com os programadores mais experientes.

Alberto Simões: 2008/07/10 14:14

perl/arraysantipatterns.txt · Last modified: 2008/07/10 22:14 by ambs