Alguns dias atrás estávamos escovando os bits de uma questão sobre um sorteio de números aleatórios aqui no fórum (http://forum.rubyonbr.org/forums/1/topics/361) e eu mandei uma solução usando o método new da classe Hash de uma forma meio “obscura”, um lance meio de magia negra (não meu, por favor, mas do Sr. Yukihiro “Matz” Matsumoto, o bruxo por trás do Ruby ;-) e resolvi escrever esse artigo sobre algumas situações do tipo que são pegadinhas também. Algumas delas vocês podem encontrar facilmente em artigos espalhados na web, mas com o risco de parecer que copiei algumas delas e alguns exemplos da documentação do Ruby, vou dizer que “compilei” tudo aqui para uma melhor visualização. ;-)
A primeira é que a classe Array permite algumas manipulações no seu método new. Copiando ligeiramente o exemplo da RubyCentral, temos:
[taq@~]irb
irb(main):001:0> a = Array.new
=> []
irb(main):002:0> a = Array.new(2)
=> [nil, nil]
irb(main):003:0> a = Array.new(5,"A")
=> ["A", "A", "A", "A", "A"]
irb(main):004:0> a = Array.new(2,Hash.new)
=> [{}, {}]
irb(main):005:0>
Eu alterei ligeiramente o exemplo inserindo uma variável para a atribuição do objeto criado por new, para mostrar isso:
irb(main):005:0> a = Array.new(5,"A")
=> ["A", "A", "A", "A", "A"]
irb(main):006:0> a[0] = "B"
=> "B"
irb(main):007:0> a
=> ["B", "A", "A", "A", "A"]
irb(main):008:0> a = Array.new(2,Hash.new)
=> [{}, {}]
irb(main):009:0> a[0][:um] = 1
=> 1
irb(main):010:0> a
=> [{:um=>1}, {:um=>1}]
irb(main):011:0> a[1][:dois] = 2
=> 2
irb(main):012:0> a
=> [{:um=>1, :dois=>2}, {:um=>1, :dois=>2}]
Opa! Peraí, o que aconteceu ali? Quando atribuímos “B” para o primeiro elemento de a e imprimimos o seu conteúdo, está tudo ok. Quando inserimos uma chave :um na Hash do elemento 0 do Array, ela foi transmitida também para a Hash do elemento 1 do Array! E quando inserimos uma chave :dois na Hash do elemento 1 do Array ela foi transmitida para a Hash do elemento 0 do Array! O que “se sucede”?
O lance é que Array.new(quantidade,objeto) cria um Array novo preenchido com o mesmo objeto criado pelo segundo parâmetro, ou seja, as Hashes são as mesmas, e quando manipulamos um elemento do Array estamos manipulando o mesmo objeto nos outros elementos do Array. Duvidam? Olhem só:
irb(main):013:0> a = Array.new(5,"A")
=> ["A", "A", "A", "A", "A"]
irb(main):014:0> a.map {|e| e.object_id}.uniq
=> [-605437908]
irb(main):015:0> a = Array.new(2,Hash.new)
=> [{}, {}]
irb(main):016:0> a.map {|e| e.object_id}.uniq
=> [-605182418]
Usando map para pegar os object_id’s e uniq para retornar os elementos únicos, podemos comprovar que realmente se trata do mesmo objeto em todas as posições do Array. Mas o que aconteceu no caso da string “B” que não foi transmitida para os outros elementos? Diferente do que fizemos com a Hash, onde acessamos uma chave do objeto do Array, no caso da string “B” foi criado um novo objeto e inserido na posição 0 do Array. Vamos comprovar:
irb(main):017:0> a = Array.new(5,"A")
=> ["A", "A", "A", "A", "A"]
irb(main):018:0> a[0] = "B"
=> "B"
irb(main):019:0> a.each {|e| puts "#{e}:#{e.object_id}"}
B:-605315838
A:-605303288
A:-605303288
A:-605303288
A:-605303288
=> ["B", "A", "A", "A", "A"]
irb(main):020:0> a.map {|e| e.object_id}.uniq
=> [-605315838, -605303288]
Ou seja, fizemos a atribuição de um objeto novo na posição do Array. Para fazer algo similar ao que foi feito com as Hashes, podemos usar:
irb(main):021:0> a = Array.new(5,"A")
=> ["A", "A", "A", "A", "A"]
irb(main):022:0> a.map {|e| e.object_id}.uniq
=> [-605384618]
irb(main):023:0> a[0].replace "B"
=> "B"
irb(main):024:0> a
=> ["B", "B", "B", "B", "B"]
irb(main):025:0> a.map {|e| e.object_id}.uniq
=> [-605384618]
O método replace troca o conteúdo do próprio objeto, sem atribuição do novo objeto. É um método destrutivo que não tem o ”!” no final. Se não prestada atenção quando usada alguma “magia negra” desse tipo, podemos perder algum tempinho depurando até chegar em algo como o mesmo objeto utilizado acima, então, prestem atenção.
Outra coisa que as vezes embanana é a questão de precedência de operadores. Por exemplo:
irb(main):026:0> falso = false => false irb(main):027:0> verdadeiro = true => true irb(main):028:0> resultado = falso or verdadeiro => true irb(main):029:0> resultado => false
O primeiro true retornado quando avaliada a expressão na linha 28 conflita com o false da variável resultado. O que aconteceu aqui? Aconteceu que a precedência de or é menor que de =. Ou seja, a expressão foi avaliada como:
irb(main):028:0> (resultado = falso) or verdadeiro
Ou seja, resultado agora vale falso. No teste com or e verdadeiro, retornou true pois false or true = true. Mas o valor na variável resultado é false. Para resolver isso, podemos ou utilizar os parênteses para forçar a avaliação:
irb(main):030:0> resultado = (falso or verdadeiro) => true irb(main):031:0> resultado
Ou usar o operador ||, que é um or também porém com precedência mais alta que =:
irb(main):032:0> resultado = falso || verdadeiro => true irb(main):033:0> resultado => true
Outro caso interessante é como pegar o valor de um caracter de uma string através do seu índice. Se eu quiser verificar o que consta na posição 1 da string “TaQ”, por exemplo:
irb(main):034:0> "TaQ"[1] => 97
Oooops! Aquilo é o código do caracter. Para retornarmos o próprio caracter, ou seja, “a”, podemos usar:
irb(main):035:0> "TaQ"[1].chr => "a"
ou
irb(main):036:0> "TaQ"[1,1] => "a" irb(main):037:0> "TaQ"[2,1] => "Q"
Onde o primeiro inteiro é a posição na string e o segundo indica que queremos apenas um caracter.
Também temos que tomar cuidado com os nomes de variáveis que utilizamos nos blocos, esse é um exemplo que ilustra isso:
irb(main):038:0> a = [1,2,3]
=> [1, 2, 3]
irb(main):039:0> a.each {|a| puts a}
1
2
3
=> [1, 2, 3]
irb(main):040:0> a
=> 3
irb(main):041:0> a.class
=> Fixnum
Ouch! Tínhamos um Array antes e agora temos um Fixnum! Isso aconteceu pelo fato da variável a de dentro do bloco “permear” a variável a de fora do bloco. Isso é uma das coisas que provalmente vão ser alteradas nas próximas versões, mas por enquanto fiquem espertos com isso.
Para finalizar, uma curiosidade: dei a louca aqui e resolvi digitar todos os exemplos no irb direto, com a chance de errar. Dá frio na barriga, não recomendo pois qualquer burradinha tira a sequência das linhas. Maluco mesmo era o Seymour Cray, o designer dos supercomputadores Cray, que inseriu um sistema operacional que ele mesmo desenhou em um computador que ele mesmo desenhou através das chaves do painel frontal, em octal, sem nenhum erro, e a coisa funcionou. É mais ou menos o Chuck Norris dos hackers, o cara deu um roundhouse kick em todo mundo. Depois dessa, vou embora. :-)
Todos os diretos reservados a RubyOnBr. Copyright RubyOnBr .
This site is powered by Radiant CMS.