r6 - 15 Nov 2008 - NelsonFerraz
- NOME
- DESCRIÇÃO
- Quem precisa de Estruturas de Dados Complexas?
- A Solução
- Sintaxe
- Solução
- O Resto
- Creditos
NOME
perlreftut - Tutorial Rapido sobre Referencias do MarkDESCRIÇÃO
Uma dos mais importantes recursos adicionados na Perl 5 foi a capacidade de gerenciar estruturas de dados complexas, como matrizes multidimensionais e hashes encadeados. Para permitir isso, a Perl 5 introduziu um recurso chamado ‘referencias’ , e usar referencias é a chave para gerenciar complexas estruturas de dados em Perl. Infelizmente, existe uma sintaxe estranha para aprender e lendo nas paginas man principais pode ser dificil entender. Elas são muito completas e algumas vezes o leitor pode achar que isso é um problema, porque pode ser dificil dizer o que é importante, e o que não é. Felizmente, você precisa saber apenas 10% do que está no manual oficial para obter 90% do beneficio, e esta pagina vai lhe mostrar os 10% necessarios.Quem precisa de Estruturas de Dados Complexas?
Um problema que apareceu no tempo da Perl 4 foi como representar um hash que os valores são listas. Claro que a Perl 4 tinha hashes, mas os valores tinham que ser escalares, não poderiam ser listas. Porque você quer um hash de listas? Vamos para um exemplo simples. Você tem um arquivo de cidades e nomes de paises, como esse:
Chicago, USA
Frankfurt, Germany
Berlin, Germany
Washington, USA
Helsinki, Finland
New York, USA
e quer gerar uma saida para produzir alguma coisa como essa, com cada pais mencionado uma vez, e também há uma lista alfabetica de cidades de cada pais.
Finland: Helsinki.
Germany: Berlin, Frankfurt.
USA: Chicago, New York, Washington.
A forma natural de fazer isso, é ter um hash com as chaves com os nomes dos paises. Associada com cada nome dos paises está uma lista das cidades em cada um. Cada vez que você lê uma linha da entrada, divida-a em pais e cidade, pegue a lista de cidades que está em cada pais e coloque ao final a cidade em seu respectivo pais. Quando voce terminar de ler a entrada, trabalhe com o hash como o usual, ordenando a lista de cidades antes de imprimi-la.
Se os valores de um hash não podem ser listas, você se deu mal. Na Perl 4, os valores de um hash não podem ser listas, apenas podem ser strings. Assim nada feito. Você provavelmente terá que combinar todas as cidades em uma string de alguma forma, e depois quando elas forem para saida, você terá que dividir a string em uma lista, ordena-la e coloca-la novamente como string. Isso é uma gambiarra e sujeito a erros. E é frustrante, pois a Perl já possui listas que perfeitamente podem resolver seu problema se você apenas puder usa-las.
A Solução
Na epoca em que a Perl 5 foi lançada, nós já estavamos cansados com essa caracteristica: Os valores de um hash precisam ser escalares. E a solução foi usar referencias. A referencia é um valor escalar que se refere a um vetor inteiro ou todo um hash (ou a apenas qualquer coisa também). Nomes são um tipo de referencia que já familiar á você. Pense no Presidente dos Estados Unidos: confuso, uma incoveniente mala de sangue e ossos. Mas podemos falar sobre ele, ou representa-lo em um programa de computador, e tudo o que precisamos é facil, uma conveniente string escalar “George Bush”. Referencias em Perl são como nomes para arrays e hashes. Eles são privados em Perl, nomes internos, assim você pode estar seguro que eles são desambuiguadores. Não como “George Bush”, uma referencia apenas se refere a uma coisa, e você sempre sabe do que se trata. Se voce tem uma referencia a um array, você pode recuperar o array inteiro. Se você tem uma referencia a um hash, você pode recuperar esse hash inteiro. Mas a referencia continua simples, apenas um valor escalar. Você não pode ter um hash cujo os valores são arrays, os valores dos hashes podem apenas serem escalares. Estamos cansados de saber disso. Mas uma simples referencia é escalar, e voce pode ter um hash de referencias para arrays, e ele vai ser como um hash de arrays, e vai ser util como um hash de arrays. Nós vamos voltar com o problema da cidade-pais mais tarde, depois de vermos alguma sintaxe para gerenciar referencias.Sintaxe
Existem duas maneiras de se criar uma referencia, e apenas duas maneiras de usar elas depois de criadas.Criando Referencias
B Regra de Criação 1
Se você coloca um\ na frente da sua variavel, você tem uma referencia para aquela variavel.
$aref = \@array; # $aref agora é uma referencia para @array
$href = \%hash; # $href agora é uma referencia para %hash
Uma vez que a referencia foi armazenada em uma variavel como $aref ou $href, você pode copiar ou armazenar da mesma maneira em outra variavel escalar:
$xy = $aref; # $xy agora é uma referencia para @array
$p[3] = $href; # $p[3] agora é uma referencia para %hash
$z = $p[3]; # $z agora é uma referencia para %hash
Estes exemplos mostram como fazer referencias para variaveis com nomes. Algumas vezes você quer fazer um array ou um hash que não tem um nome. Isto é analogo a forma como você poderia estar habilitado para usar a string "\n" ou o numero 80 sem ter armazenado eles em uma variavel primeiro.
B Regra de Criação 2
[ ITEMS ] cria um novo, array anonimo, e retorna a referencia para aquele array. { ITEMS } cria um novo, hash anonimo, e retorna a referencia para aquele hash.
$aref = [ 1, “foo”, undef, 13 ];
# $aref agora é uma referencia para array
$href = { APR => 4, AUG => 8 };
# $href agora é uma referencia para um hash
As referencias que você obtem via regra 2 são do mesmo tipo que as referencias que você obtem da regra 1:
# Isso:
$aref = [ 1, 2, 3 ];
# faz a mesma coisa que:
@array = (1, 2, 3);
$aref = \@array;
A primeira linha é uma abreviação do que segue nas outras duas, exceto pelo fato que ela não cria um array superfluo @array.
Se você apenas escreve [], você tem um novo e vazio array.
Se você apenas escreve {}, você tem um novo e vazio hash.
Usando Referencias
O que você pode fazer com uma referencia quando você a obtem? Ela é um valor escalar, e nós vimos que você pode armazenar um escalar e ler novamente ele como qualquer escalar. Existem duas maneiras de usa-las:Regra numero 1
Você sempre pode usar uma referencia para um array, entre chaves, no lugar do nome do array. Como por exemplo,@{$aref} ao invez de @array.
Temos alguns exemplos disso:
Arrays:
@a @{$aref} Um array
reverse @a reverse @{$aref} Inverso the array
$a[3] ${$aref}[3] Um elemento de um array
$a[3] = 17 ${$aref}[3] = 17 Atribuindo um elemento
Cada par de expressões fazem a mesma coisa. No lado esquerdo são expressões que operam o array @a. O lado direito são versões que operam com o array que é referenciado por §aref. Quando ele acha o array em que está operando, ambas versões fazem as mesmas coisas com o array.
Usando uma referencia para um hash, é a exatamente a mesma coisa:
%h %{$href} Um hash
keys %h keys %{$href} Pegando as chaves do hash.
$h{’red’} ${$href}{’red’} Um elemento de um hash
$h{’red’} = 17 ${$href}{’red’} = 17 Atribuindo um elemento
Sempre que você quiser usar uma referencia, Usando a regra1 lhe diz como fazer. Você apenas escreve o codigo Perl que você teria escrito para fazer a mesma coisa com um array regular um ou hash, e ele substitui o nome do array ou o hash com {$referencia}. “Como eu faço um loop sobre todo array se ele é uma referencia?”. Bem, você você pode escrever:
for my $element (@array) {
…
}
e substitua o nome do array, @array, com a referencia:
for my $element (@{$aref}) {
…
}
“Como eu imprimo o conteúdo de um hash quando tudo que eu tenho é uma referencia?¨ Primeiro segue o codigo para imprimir todo um hash:
for my $key (keys %hash) {
print “$key => $hash{$key}\n”;
}
E substitua o nome do hash pela referencia:
for my $key (keys %{$href}) {
print “$key => ${$href}{$key}\n”;
}
Regra de Numero2
Regra de Numero 2 é tudo o que você precisa, porque ela lhe diz como fazer absolutamente tudo que precisara fazer com referencias. Mas a coisa mais comum a se fazer com um array ou um hash é extrair um simples elemento, e a notação usada na B é estranha. Então aqui segue uma abreviação.
C<${$aref}[3]> é muito dificil de ler, assim você pode escrever C<< $aref->[3] >> no lugar.
C<${$href}{red}> é muito dificil de ler, assim você pode escrever C<< $href->{red} >> no lugar.
Se $aref contém uma referencia para um array, quando $aref->[3] é escrito ele se refere ao quarto elemento do array. Não confunda isso com $aref[3], que é o quarto elemento de um array completamente diferente, decepcionantemente chamado @array. $aref e @aref não tem relação da mesma forma que $item e <@item>.
Similarmente, $href->{’red’} é parte de um hash referenciado pela variavel escalar $href, perhaps even one with no name. $href{'red'} é parte de um hash decepcionantemente chamado %href. É facil esquecer e deixar de fora o ->, e se você fizer isso, você terá resultados bizarros quando seu programa obter os elementos do hash ou array do conteudo
da memoria que você não quer.
Um Exemplo
Vamos ver um rapido exemplo de como isso tudo é util. Primeiro lembre-se que[1, 2, 3] cria um array anonimo contendo
(1, 2, 3), e lhe devolve uma referencia para ele.
Agora pense sobre isso:
@a = ( [1, 2, 3],
[4, 5, 6],
[7, 8, 9]
);
@a é um array com três elementos, e cada um é uma referencia para outro
array.
$a[1] é uma dessas referencias. Ela se refere a um array, um array
contendo (4, 5, 6), e por causa disso é uma referencia para um array.
B diz que nós podemos escrever $a[1]->[2] para
obter o terceiro elemento do daquele array. $a[1]->[2] é o 6.
Similarmente, $a[0]->[2] é o numero 2. O que nós temos aqui é
como um array bidimensional, você pode escrever $a[ROW]->[COLUMN]
para obter ou definir em qualquer linha ou coluna do array.
Esta notação ainda está um pouco confusa, então aqui vai mais uma
abreviação:
Regra da Seta
Entre dois B, a seta é opcional. Ao invés de$a[1]->[2], nós podemos escrever $a[1][2]; ele
faz a mesma coisa. Ao invés de $a[0]->[1] = 23, podemos escrever
$a[0][1] = 23; que ele faz a mesma coisa.
Agora finalmente parece com um array bidimensional!
Você pode ver porque as sets são importantes. Sem elas, nós temos que
escrever ${$a[1]}[2] ao invés de $a[1][2]. Para arrays
tridimensionais, podemos escrever $x[2][3][5] ao invés do ilegivel
${${$x[2]}[3]}[5].
Solução
Aqui vai a solução do problema que eu propus anteriormente, de reorganizar um arquivo com nomes de cidades e paises.
1 my %table;
2 while (<>) {
3 chomp;
4 my ($city, $country) = split /, /;
5 $table{$country} = [] unless exists $table{$country};
6 push @{$table{$country}}, $city;
7 }
8 foreach $country (sort keys %table) {
9 print “$country: “;
10 my @cities = @{$table{$country}};
11 print join ‘, ‘, sort @cities;
12 print “.\n”;
13 }
O programa está divido em duas pedaços: Das linhas 2 a 7 leem a entrada
e constroi a informação, e as linhas de 8 a 13 analisam a informação e
imprimem-na. Nós estamos obtendo um hash, %table, o qual as chaves
são os nomes dos paises e os valores são referencias para arrays com
os nomes das cidades. A estrutura de dados vai se parecer com isso:
%table
+——-+—+
| | | +———–+——–+
|Germany| *—->| Frankfurt | Berlin |
| | | +———–+——–+
+——-+—+
| | | +———-+
|Finland| *—->| Helsinki |
| | | +———-+
+——-+—+
| | | +———+————+———-+
| USA | *—->| Chicago | Washington | New York |
| | | +———+————+———-+
+——-+—+
Vamos ver a saida agora. Supondo que nós já temos esta estrutura, como
vamos imprimi-la?
8 foreach $country (sort keys %table) {
9 print “$country: “;
10 my @cities = @{$table{$country}};
11 print join ‘, ‘, sort @cities;
12 print “.\n”;
13 }
%table é um hash ordinario, e nós obtemos uma lista de chaves dele,
as ordenamos e fizemos um loop para cada elemento como o usual. O unico
uso de referencias é na linha 10. $table{$country} procura a chave
{$country} no hash e pega seu valor, que é uma referencia para um array
de cidades em cada pais. B diz que nós podemos recuperar
o array escrevendo:
@{$table{$country}}.
A linha 10 é como essa:
@cities = @array;
exceto que o nome C foi substituido pela referencia
$table{$country}. O @ diz a Perl para obter o array todo.
Tendo obtido a lista de cidades toda, nós a ordenamos, juntamos,
e imprimimos como o de costume.
As linhas de 2 a 7 são responsaveis por construir a estrutura em
primeiro lugar. Aqui esta ela denovo:
2 while (<>) {
3 chomp;
4 my ($city, $country) = split /, /;
5 $table{$country} = [] unless exists $table{$country};
6 push @{$table{$country}}, $city;
7 }
As Linhas de 2 a 4 obtem o nome da cidade e do pais. A linha 5 procura
verificar se o pais já existe como uma chave do hash. Se não, o
programa usa a notação [] (B) para fabricar um novo,
anonimo e vazio array de cidades, e coloca a referencia dentro do hash
na chave apropriada.
A linha 6 coloca o nome da cidade dentro da chave apropriada $table{$country}
que agora tem a referencia para o array de cidades que estão nos respectivos
paises A linha 6 é exatamente como:
push @array, $city;
exceto que o nome C foi substituido pela referencia {$table{$country}}.
O C
adiciona o nome da cidade para o fim do referido array.Aqui está um ponto que eu pulei. A linha 5 é desnecessaria e nós podemos a
retirar.
2 while (<>) {
3 chomp;
4 my ($city, $country) = split /, /;
5 #### $table{$country} = [] unless exists $table{$country};
6 push @{$table{$country}}, $city;
7 }
Se já existe uma entrada em %table para o <$country> atual, não
acontece nada de diferente. A linha 6 vai colocar o valor de
$table{$country}, que é uma referencia para um array, e empilhar
$city no array. Mas o que ele faz quando $country contem uma
chave, como C, que não está em %table?
Isto é a Perl, e ela faz a coisa certa. Ele entende que você quer
empilhar C no array que não existe, então ela automaticamente
cria um novo, vazio e anonimo array para você, o coloca em %table,
e assim empilha C nele. Isso é chamado `autovivification’
– trazendo as coisas a vida automaticamente. A Perl diz que a chave
não estava no hash, então ela cria uma nova entrada para no hash
automaticamente. A Perl diz que você procurava usar o valor do hash
como um array, então criou um novo e vazio array e o colocou como uma
referencia no hash de forma automatica. E como o usual, a Perl fez o
array um elemento maior para acomodar o nome da nova cidade.
O Resto
Eu prometi lhe dar o 90% de beneficio com 10% dos detalhes, e isso significa que faltam ainda 90% dos detalhes. Agora que você tem uma ideia geral das partes importantes, será mais facil ler a pagina man L , que discute 100% dos detalhes.Alguns dos pontos de destaque da L :=over 4-
Você pode criar referencias para qualquer coisa, incluindo escalares,
funções e outras referencias.
No caso da B, você pode omitir as chaves sempre que
dentro delas estiver um escalar unico como
$aref. Como exemplo,
@$aref é o mesmo que @{$aref}, e $$aref[1] é o mesmo que
${$aref}[1]. Se você está apenas começando agora, você pode
querer adotar o habito de sempre incluir as chaves.
Isto não copia todo o array:
$aref2 = $aref1;
O que você tem são duas referencias para o mesmo array.
Se você modificar: $aref1->[23] e olhar depois em:
$aref2->[23] você verá o que mudou.
Para copiar o array, use:
$aref2 = [@{$aref1}];
Isto usa a notação [...] para criar um novo e anonimo array, e
$aref2 é alocado como uma referencia para um novo aray. O novo
array é iniciado com o conteudo do array referenciado por $aref1.
De forma similar, para copiar um hash anonimo, você pode usar:
$href2 = {%{$href1}};
Para ver se uma variavel contem uma referencia, use a função C.
Ela retorna verdadeiro se o argumento é uma referencia. Atualmente
ele é um pouco melhor que isso: Ele retorna C para referencias
e um C para referencias de array.
Se você tenta usar uma referencia como uma string, você obterá
strings como essa:
ARRAY(0×80f5dec) or HASH(0×826afc0)
Se você sempre ve uma string como essa, você sabera como imprimiu
uma referencia por engano.
Um efeito colateral dessa representação é que você pode usar C
para ver se duas referencias se referem as mesmas coisas. (Mas você
pode normalmente usar == porque é muito mais rapido.)
Você pode usar uma string como se fosse uma referencia. Se você usa
uma string "foo" como uma referencia para um array, ele é pega
como uma referencia para um array @foo. Isso é chamado
I ou I. Essa declaração
C disabilita este recurso, que pode causar todos
os tipos de problema se você usar por acidente.

