Ruby's pack and unpack explained

后端 未结 4 548
长发绾君心
长发绾君心 2021-02-08 04:40

Even after reading the standard documentation, I still can\'t understand how Ruby\'s Array#pack and String#unpack exactly work. Here is the example that\'s causing me the most t

相关标签:
4条回答
  • 2021-02-08 05:02

    The 'H' String directive for Array#pack says that array contents should be interpreted as nibbles of hex strings.

    In the first example you've provided:

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

    you're telling to pack the first element of the array as if it were a sequence of nibbles (half bytes) of a hex string: 0x61 in this case that corresponds to the 'a' ASCII character.

    In the second example:

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

    you're telling to pack 3 elements of the array as if they were nibbles (the high part in this case): 0x60 corresponds to the '`' ASCII character. The low part or second nibble (0x01) "gets lost" due to missing '2' or '*' modifiers for "aTemplateString".

    What you need is:

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

    in order to pack all the nibbles of all the elements of the array as if they were hex strings.

    The case of 'H2' * char.size only works fine if the array elements are representing 1 byte only hex strings.

    It means that something like chars = ["6161", "6262", "6363"] is going to be incomplete:

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

    while:

    2.1.5 :049 > chars.pack('H*' * chars.size)
     => "aabbcc"
    
    0 讨论(0)
  • 2021-02-08 05:02

    The Array#pack method is pretty arcane. Addressing question (2), I was able to get your example to work by doing this:

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

    See the Ruby documentation for a similar example. Here is a more general way to do it:

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

    This is probably not the most efficient way to tackle your problem; I suspect there is a better way, but it would help to know what kind of input you are starting out with.

    The #pack method is common to other languages, such as Perl. If Ruby's documentation does not help, you might consult analogous documentation elsewhere.

    0 讨论(0)
  • 2021-02-08 05:03

    I expected both these operations to return the same output: "abc".

    The easiest way to understand why your approach didn't work, is to simply start with what you are expecting:

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

    So, it seems that Ruby expects your hex bytes in one long string instead of separate elements of an array. So the simplest answer to your original question would be this:

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

    This approach also seems to perform comparably well for large input:

    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)
    
    0 讨论(0)
  • 2021-02-08 05:24

    We were working on a similar problem this morning. If the array size is unknown, you can use:

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

    You can reverse it using:

    str = "abc"
    str.unpack('H2' * str.size)
    => ["61", "62", "63"]
    
    0 讨论(0)
提交回复
热议问题