Ruby's pack и unpack объяснили
даже после прочтения документации, я до сих пор не могу понять, как Руби Array#pack
и String#unpack
точно работают. Вот пример, который вызывает у меня больше всего проблем:
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")
=> "```"
Я ожидал, что обе эти операции вернут один и тот же результат: "abc". Каждый из них" терпит неудачу " по-разному (не совсем неудачу, так как я, вероятно, ожидаю неправильной вещи). Итак, два вопроса:
- какова логика этих выходы?
- как я могу достичь желаемого эффекта, т. е. преобразовать последовательность шестнадцатеричных чисел в соответствующую строку. Еще лучше-учитывая целое число n, как преобразовать его в строку, идентичную текстовому файлу, который при рассмотрении в качестве числа (скажем, в шестнадцатеричном редакторе) равен n?
4 ответов
мы работаем над аналогичной проблемой сегодня утром. Если размер массива неизвестен, вы можете использовать:
ary = ["61", "62", "63"]
ary.pack('H2' * ary.size)
=> "abc"
Вы можете отменить его с помощью:
str = "abc"
str.unpack('H2' * str.size)
=> ["61", "62", "63"]
на Array#pack
метод довольно загадочный. Обращаясь к вопросу (2), я смог заставить ваш пример работать, сделав следующее:
> ["61", "62", "63"].pack("H2H2H2")
=> "abc"
посмотреть Ruby documentation для аналогичного примера. Вот более общий способ сделать это:
["61", "62", "63"].map {|s| [s].pack("H2") }.join
Это, наверное, не самый эффективный способ решения вашей проблемы; я подозреваю, что есть лучший путь, но это помогло бы знать, что именно вы начинаете.
на #pack
метод общим для других языков, таких как Perl. Если документация Ruby не помогает, вы можете обратиться к аналогичной документации в другом месте.
на 'H'
директива строку Array#pack
говорит, что содержимое массива следует интерпретировать как кусочки шестнадцатеричных строк.
в первом примере вы указали:
irb(main):002:0> chars.pack("H*")
=> "a"
вы говорите, чтобы собрать первый элемент массива, как если бы это была последовательность полубайтов (половина байта) hex-строки: 0x61
в этом случае, что соответствует 'a'
символ ASCII.
во втором примере:
irb(main):003:0> chars.pack("HHH")
=> "```"
вы говорите, чтобы упакуйте 3 элемента массива, как если бы они были кусочками (высокая часть в этом случае):0x60
соответствует '`'
символ ASCII. Нижняя часть или второй укус (0x01
)" теряется "из-за отсутствия модификаторов"2" или " * " для "aTemplateString".
что нужно:
chars.pack('H*' * chars.size)
для того чтобы упаковать все грызет всех элементов массива, как если бы они были шестнадцатеричных строк.
случае 'H2' * char.size
отлично работает, только если элементы массива представление 1 байта только шестнадцатеричных строк.
это означает, что что-то вроде chars = ["6161", "6262", "6363"]
будет неполным:
2.1.5 :047 > chars = ["6161", "6262", "6363"]
=> ["6161", "6262", "6363"]
2.1.5 :048 > chars.pack('H2' * chars.size)
=> "abc"
в то время как:
2.1.5 :049 > chars.pack('H*' * chars.size)
=> "aabbcc"
Я ожидал, что обе эти операции вернут один и тот же результат: "abc".
самый простой способ понять, почему ваш подход не работает, можно просто начать с того, что вы ожидаете:
"abc".unpack("H*")
# => ["616263"]
["616263"].pack("H*")
# => "abc"
Итак, кажется, что Ruby ожидает ваши шестнадцатеричные байты в одной длинной строке вместо отдельных элементов массива. Таким образом, самый простой ответ на ваш первоначальный вопрос будет следующим:
chars = ["61", "62", "63"]
[chars.join].pack("H*")
# => "abc"
этот подход также представляется выполните сравнительно хорошо для больших входных данных:
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)