2012-12-05 14 views
7

Nawet po przeczytaniu standardowej dokumentacji, nadal nie mogę zrozumieć, jak dokładnie działają Ruby Array#pack i String#unpack. Oto przykład, który sprawia mi najwięcej problemów:Pakiet i rozpakowanie Ruby wyjaśnił

irb(main):001:0> chars = ["61","62","63"] 
=> ["61", "62", "63"] 
irb(main):002:0> chars.pack("H*") 
=> "a" 
irb(main):003:0> chars.pack("HHH") 
=> "```" 

Oczekiwałem, że obie te operacje zwrócą ten sam wynik: "abc". Każda z nich "zawiedzie" w inny sposób (nie jest to naprawdę porażka, ponieważ prawdopodobnie oczekuję niewłaściwej rzeczy). Tak więc dwa pytania:

  1. Jaka jest logika tych wyjść?
  2. Jak uzyskać pożądany efekt, tj. Przekształcenie ciągu liczb szesnastkowych w odpowiadający ciąg znaków. Jeszcze lepiej - biorąc pod uwagę liczbę całkowitą n, jak przekształcić ją na ciąg identyczny z plikiem tekstowym, który, gdy jest uważany za liczbę (powiedzmy, w edytorze szesnastkowym) jest równy n?
+0

Dla ' 'formatów H'',' * 'nie działając w oczekiwany sposób zgodnie z dokumentacją. Inne znaki formatu zachowują się poprawnie, więc podejrzewam, że jest to błąd w używaniu przez Ruby '' H * ''. –

Odpowiedz

10

Dziś rano pracowaliśmy nad podobnym problemem. Jeśli rozmiar tablicy jest nieznany, można użyć:

ary = ["61", "62", "63"] 
ary.pack('H2' * ary.size) 
=> "abc" 

można odwrócić za pomocą:

str = "abc" 
str.unpack('H2' * str.size) 
=> ["61", "62", "63"] 
+1

Czy byłoby to wydajne przy dużych nakładach? –

+0

Powinien być bardzo wydajny. Jedyny dodatkowy koszt, jaki widzę, to utworzenie tymczasowego ciągu formatu, który i tak musiałby zrobić "H *". –

+0

To zabawne, że "H2 *" nie działa. –

5

Metoda Array#pack jest dość tajemnicza. Adresowanie pytanie (2), udało mi się dostać do pracy na przykład w ten sposób:

> ["61", "62", "63"].pack("H2H2H2") 
=> "abc" 

Zobacz Ruby documentation na podobny przykład. Oto bardziej ogólny sposób:

["61", "62", "63"].map {|s| [s].pack("H2") }.join 

Prawdopodobnie nie jest to najskuteczniejszy sposób rozwiązania problemu; Podejrzewam, że jest lepszy sposób, ale pomógłbym się zorientować, z jakim rodzajem wejścia zaczynasz.

Metoda #pack jest wspólna dla innych języków, takich jak Perl. Jeśli dokumentacja Ruby nie pomoże, możesz przejrzeć analogiczną dokumentację w innym miejscu.

2

Spodziewałem obie te operacje, aby powrócić do tego samego wyjścia: „ABC”.

Najprostszym sposobem, aby zrozumieć, dlaczego podejście nie działa, to po prostu zacząć, czego oczekują:

"abc".unpack("H*") 
# => ["616263"] 

["616263"].pack("H*") 
# => "abc" 

Więc wydaje się, że Ruby spodziewa się swoimi hex bajtów w jednym długim ciąg znaków zamiast oddzielnych elementów tablicy. Więc najprostszą odpowiedź do oryginalnego pytanie byłoby to:

chars = ["61", "62", "63"] 
[chars.join].pack("H*") 
# => "abc" 

Takie podejście wydaje się również, aby wykonać stosunkowo dobrze dla dużych wejścia:

require 'benchmark' 

chars = ["61", "62", "63"] * 100000 

Benchmark.bmbm do |bm| 
    bm.report("join pack") do [chars.join].pack("H*") end 
    bm.report("big pack") do chars.pack("H2" * chars.size) end 
    bm.report("map pack") do chars.map{ |s| [s].pack("H2") }.join end 
end 

#     user  system  total  real 
# join pack 0.030000 0.000000 0.030000 ( 0.025558) 
# big pack 0.030000 0.000000 0.030000 ( 0.027773) 
# map pack 0.230000 0.010000 0.240000 ( 0.241117) 
3

Dyrektywa 'H' String dla Array#pack mówi, że zawartość tablicy powinna być interpretowane jako przysmaki strun heksadecymalnych.

W pierwszym przykładzie masz pod warunkiem że:

irb(main):002:0> chars.pack("H*") 
=> "a" 

mówisz spakować pierwszy element tablicy, tak jakby to była sekwencja przekąski (pół bajtach) ciąg hex: 0x61 w tym przypadku odpowiada to znakowi ASCII 'a'.

W drugim przykładzie:

irb(main):003:0> chars.pack("HHH") 
=> "```" 

mówisz do Pack 3 elementów tablicy tak, jakby były przekąski (duża część w tym przypadku): 0x60 odpowiada charakterem '`' ASCII. Niska część lub druga skubbita (0x01) "ginie" z powodu braku modyfikatorów "2" lub "*" dla "aTemplateString".

czego potrzebujesz to:

chars.pack('H*' * chars.size) 

aby spakować wszystkie przekąski wszystkich elementów tablicy tak, jakby były hex sznurki.

Sprawa 'H2' * char.size działa poprawnie tylko wtedy, gdy elementy tablicy reprezentują ciągi szesnastkowe tylko jeden bajt.

Oznacza to, że coś chars = ["6161", "6262", "6363"] będzie niekompletny:

2.1.5 :047 > chars = ["6161", "6262", "6363"] 
=> ["6161", "6262", "6363"] 
2.1.5 :048 > chars.pack('H2' * chars.size) 
=> "abc" 

natomiast:

2.1.5 :049 > chars.pack('H*' * chars.size) 
=> "aabbcc"