[Rio-pm] Teste Perl

breno breno em rio.pm.org
Domingo Março 22 11:10:04 PDT 2009


2009/3/20 Fernando Oliveira <fernandocorrea em gmail.com>:
> eu já tinha visto a numero 7 em algum lugar (não lembro onde...) senão eu
> não teria acertado não!
>

Gabarito para iniciantes abaixo!











SPOILER ALERT!!! SPOILER ALERT!!! SPOILER ALERT!!! SPOILER ALERT!!!
SPOILER ALERT!!! SPOILER ALERT!!! SPOILER ALERT!!! SPOILER ALERT!!!
SPOILER ALERT!!! SPOILER ALERT!!! SPOILER ALERT!!! SPOILER ALERT!!!
SPOILER ALERT!!! SPOILER ALERT!!! SPOILER ALERT!!! SPOILER ALERT!!!
SPOILER ALERT!!! SPOILER ALERT!!! SPOILER ALERT!!! SPOILER ALERT!!!
SPOILER ALERT!!! SPOILER ALERT!!! SPOILER ALERT!!! SPOILER ALERT!!!
SPOILER ALERT!!! SPOILER ALERT!!! SPOILER ALERT!!! SPOILER ALERT!!!
SPOILER ALERT!!! SPOILER ALERT!!! SPOILER ALERT!!! SPOILER ALERT!!!
SPOILER ALERT!!! SPOILER ALERT!!! SPOILER ALERT!!! SPOILER ALERT!!!
SPOILER ALERT!!! SPOILER ALERT!!! SPOILER ALERT!!!









1)  map {2*$_} (1..4)

O "operador de alcance" (range operator - ".."), quando usado em
contexto de lista, retorna uma lista de valores compostos pelos
números entre o valor da esquerda e o da direita, subindo de um em um.
Se o valor da esquerda for maior que o da direita, ele retorna uma
lista vazia. No caso acima, temos (1..4), o que retorna a lista (1, 2,
3, 4).

A função map, por sua vez, recebe uma lista e, para cada elemento,
avalia o bloco {}, definindo a variável de contexto $_ para o elemento
sendo avaliado. O valor retornado pelo map é composto do valor de
retorno de cada avaliação. Na questão proposta, o bloco avaliador
contém a expressão 2 * $_, ou seja, para cada valor da lista ($_), o
map retornará duas vezes ele (2* $_). Assim, ao receber a lista (1, 2,
3, 4), o map retorna a lista (2, 4, 6, 8).


2)  grep { $_ % 2 } (1..5)

A função grep do Perl (não confundir com o comando grep do sistema),
recebe uma lista e, para cada elemento, avalia o bloco {}, definindo a
variável de contexto $_ para o elemento sendo avaliado. O valor
retornado pelo grep é composto de todos os valores da lista original
para os quais a expressão foi verdadeira.

Na questão proposta, o bloco avaliador contém a expressão $_ % 2
(resto da divisão por 2), ou seja, para cada valor da lista ($_), o
grep retornará esse mesmo valor caso o resto da divisão dele por 2
seja verdadeiro (não-zero). Em outras palavras, se o número for
divisível por 2, a expressão será 0 (falso). Assim, a lista retornada
pelo grep será composta pelos elementos da lista original que não são
inteiramente divisíveis por 2. Recebendo (1, 2, 3, 4, 5), ela retorna
(1, 3, 5).


3) grep {$_ = 5} (1..3)

Parece o mesmo problema que o acima, mas tem uma pequena pegadinha.
Aqui, a expressão avaliada pelo grep é uma atribuição! Usar
atribuições em condicionais é um erro comum (de fato, o perlcritic
reclama dessa construção!), mas pode ser usado para algumas tarefas
específicas. Duas coisas precisam ser notadas para a resposta correta
dessa questão:

  - o valor de retorno de uma atribuição é o valor da variável atribuída.
  - grep (e map, e foreach) colocam no $_ uma referência ao valor da
lista. Ou seja, mudou $_, mudou o valor da lista original!

No caso, grep recebeu (1, 2, 3) e, para cada elemento, transformou no
número 5. Como o valor de retorno é a variável em questão, e 5 é
verdadeiro (não é zero), o valor é passado adiante (após a
atribuição). Assim, o valor retornado é (5, 5, 5)


4) map {2*$_} grep {/../} (1,5,15,100)

Agora que já conhecemos o funcionamento do map e do grep, é importante
lembrarmos um pouco sobre expressões regulares. Dentre os
metacaracteres aceitos, o ponto ( "." ) casa com qualquer caractere,
exceto o de nova linha (para isso você precisaria do modificador /s no
final da ER, mas isso é outra história). Também devemos lembrar que,
por padrão, expressões regulares são feitas em relação ao $_, então a
notação /../ é a mesma coisa que dizer $_ =~ /../  (e, de fato, muito
mais simples e legível uma vez que você pega o jeito). Mas então, o
que é avaliado como verdadeiro por essa ER?

/./ casa com um caractere qualquer, então /../ casa com um caractere
qualquer, seguido de outro caractere qualquer! Mas aí é que está a
pegadinha dessa questão: se são dois caracteres seguidos, ela é falsa
para 1, falsa para 5 e verdadeira para 15. Mas e o 100? 100 são 3
caracteres, então deve ser falso, certo? errado! A expressão regular
exige dois caracteres quaisquer seguidos, mas não exige que eles sejam
os únicos na expressão! Para isso, você precisa especificar dentro da
ER o início e o fim da string, fazendo algo como: /^..$/, do contrário
ele casa com os primeiros dois caracteres que encontrar. Como 100 tem
"dois caracteres quaisquer seguidos", 100 também é avaliado como
verdadeiro por /../!

Assim, grep { /../ } (1, 5, 15, 100) retorna a lista (15, 100), que é
passada ao map. O map { 2 * $_ }, por sua vez, processa e retorna o
valor da expressão, que é duas vezes o valor de cada elemento. Logo, a
lista retornada é (30, 200).


5) grep {/../} map {2*$_} (1,5,15,100)

Mesma coisa aqui, só que ao contrário. A função map recebe a lista (1,
5, 15, 100) e retorna uma nova lista com cada elemento da antiga vezes
dois. Assim, a lista retornada é (2, 10, 30, 200). Ao receber essa
nova lista, o grep por sua vez roda a expressão regular (um tanto
capciosa, como já vimos), retornando a lista de elementos com pelo
menos dois caracteres consecutivos, ou seja, (10, 30, 200)


6) sort map {2*$_} (1..5)

A primeira parte vcs já sabem: a função map recebe a lista (1, 2, 3,
4, 5) e retorna uma lista onde cada elemento é multiplicado por dois,
dando a lista (2, 4, 6, 8, 10). Essa lista é passada para a função
sort. E agora? A função sort serve para ordenar a lista recebida de
diferentes formas, mas um detalhe importante é que, por padrão (i.e.
sem um bloco especificando a forma), o sort ordena de forma crescente
e fazendo comparação de strings(alfabética), e não numérica. Qual a
diferença? Simples: 10 (número) é maior que 2, mas '10' (string) vem
antes de '2', pois é uma comparação caractere-por-caractere: '1'
(primeiro caractere de '10') vem antes de '2' (primeiro caractere de
'2'), logo, '10' vem antes. Por esse motivo Perl oferece comparações
numéricas (==, !=, >=, <=, >, <) e comparações de strings (eq, ne, ge,
le, gt, lt), de modo que você possa fazer exatamente o que quer
conforme o problema em questão. Como a ordenação de strings (nomes de
arquivos em um diretório, por exemplo) é, do ponto de vista dos
core-developers do Perl, mais útil e comum entre programas da
linguagem, ela é a padrão.

De volta ao problema, sort recebe a lista (2, 4, 6, 8, 10). Com base
no que vimos, a comparação é feita tratando os números como strings,
então o valor ordenado que o sort retorna é a lista (10, 2, 4, 6, 8).

Para comparações numéricas, você deveria especificar um bloco de
comparação, de modo que o comando acima deveria ser escrito como: sort
{$a <=> $b} map {2*$_} (1..5). Veja a função sort (perldoc -f sort)
para mais informações sobre ordenação)

7) length(grep {$_>10} (1..15))

A primeira parte vocês conhecem: grep recebe (1, 2, 3, 4, 5, 6, 7, 8,
9, 10, 11, 12, 13, 14, 15) e retorna uma lista com os números maiores
que 10, a saber: (11, 12, 13, 14, 15).

A primeira vista, você pode achar que o length vai dizer quantos
elementos existem no array (5). Mas para isso basta usarmos a variável
com a lista em contexto escalar! Na verdade, a função length retorna o
tamanho *em caracteres* da expressão recebida. Não pode ser usada para
dizer quantos elementos tem num array ou hash pois suas expressões
recebidas são imediatamente convertidas para contexto escalar antes de
serem processadas. Assim, ao receber a lista (11, 12, 13, 14, 15) ela
trabalha com "scalar (11, 12, 13, 14, 15)", que retorna o número de
elementos da lista, ou seja, '5'. Ela então diz o tamanho da string
'5', que é... 1 caractere!

Esse exercício é uma lição para usar o length apenas em escalares.
Para saber o tamanho de uma @lista, basta testá-la em contexto escalar
(e.g.  if (@lista < 5) ou então: print "lista contém " . scalar @lista
. "caracteres\n" ).


8) scalar(grep {$_>10} (1..15))

Essa questão complementa a anterior exatamente com essa diferença de
comportamento. Aqui, grep recebe a lista (1..15) e retorna (de novo) a
lista com números maiores que 10, ou seja: (11, 12, 13, 14, 15). Ao
vermos a lista em contexto escalar recebemos... o tamanho da lista,
como vimos na questão anterior! Logo... 5.


9) @a{1,2,19,6,2,6}=(1..6); sort keys %a

Crianças, não tentem isso em casa. O próprio autor da questão a
definiu como "isso é ser mau ao candidato". O conceito de hash slices,
embora seja relativamente simples (veremos ele daqui a pouco), é raro
e pode ser confuso de ler, levando o desenvolvedor a parar por alguns
segundos até entender o que está acontecendo, o que normalmente é um
indicador de que o código não está claro o suficiente.

Como de costume, vamos dividir o problema em partes. A primeira coisa
que devemos notar ao ver essa questão é que são dois comandos
diferentes, separados por ponto-e-vírgula, em vez de uma série de
comandos encadeados como vimos até agora.

A primeira parte é um conceito chamado "hash slice". Essa técnica
permite que você acesse de uma vez só um conjunto de elementos do seu
hash (uma fatia, ou "slice"), em vez de acessar um de cada vez como de
costume. Assim, em vez de escrevermos (por exemplo):

$a{'linguagem'} = 'Perl';
$a{'versao'} = 5;

podemos fazer:

@a{'linguagem', 'versao'} = ('Perl', 5);

Note que, como estamos tratando as chaves do hash %a como uma lista,
precisamos usar o prefixo @. É o mesmo motivo pelo qual, ao acessarmos
um escalar dentro de um array ou hash, usamos o prefixo $. Agora que
sabemos disso, voltemos ao problema. A primeira expressão diz:

 @a{1,2,19,6,2,6}=(1..6)

Como vimos, isso é a mesma coisa que fazer:

( $a{1}, $a{2}, $a{19}, $a{6}, $a{2}, $a{6} ) = (1, 2, 3, 4, 5, 6)

que, por sua vez, é a mesma coisa que fazer:

$a{1} = 1;
$a{2} = 2;
$a{19} = 3;
$a{6} = 4;
$a{2} = 5;    # notem a repetição da chave!
$a{6} = 6;    # outra repetição!

No final, como as últimas declarações são as que permanecem, o que
temos é um hash (%a) com os seguintes valores:

%a = (
   '1' => 1,
   '2' => 5,
   '6' => 6,
  '19' => 3,
);

Ufa! Agora vamos para a segunda (e última) expressão, que lida com esse hash:

sort keys %a

A função keys, em contexto de lista, retorna a lista de chaves que o
hash possui. No caso, a lista é (1, 2, 6, 19). É importante lembrar
que hashes não são ordenados, de modo que a única coisa que podemos
garantir é que a lista conterá exatamente esses elementos, mas não
necessariamente nessa ordem. Por isso, quando a ordem é importante,
costumamos usar a forma "sort keys", como acima, em que garantimos o
retorno das chaves do hash em ordem. Ordem ALFABÉTICA, e não NUMÉRICA,
como já vimos. Por isso, novamente, chaves como '19' vêm antes de
chaves como '2'. Assim, a lista retornada é (1, 19, 2, 6).


Mais informações:

perldoc -f map
perldoc -f grep
perldoc -f sort
perldoc -f length
perldoc -f keys
perldoc perlre (para expressões regulares)
perldoc perldata (para slices)


[]s

-b


Mais detalhes sobre a lista de discussão Rio-pm