Ruby - распаковать массив со смешанными типами - PullRequest
2 голосов
/ 12 июня 2019

Я пытаюсь использовать unpack для декодирования двоичного файла.Двоичный файл имеет следующую структуру:

ABCDEF\tFFFABCDEF\tFFFF....

где

ABCDEF -> String of fixed length
\t -> tab character
FFF -> 3 Floats
.... -> repeat thousands of times

Я знаю, как это сделать, когда типы все одинаковые или только с числами и массивами фиксированной длины, но яЯ борюсь в этой ситуации.Например, если бы у меня был список с плавающей точкой, я бы сделал

s.unpack('F*')

Или, если бы у меня были целые числа и числа с плавающей точкой, такие как

[1, 3.4, 5.2, 4, 2.3, 7.8]

, я бы сделал

s.unpack('CF2CF2')

Но в этом случае я немного растерялся.Я надеялся использовать строку формата, такую ​​как `(CF2) * 'в скобках, но она не работает.

Мне нужно использовать Ruby 2.0.0-p247, если это имеет значение

Пример

ary = ["ABCDEF\t", 3.4, 5.6, 9.1, "FEDCBA\t", 2.5, 8.9, 3.1]
s = ary.pack('P7fffP7fff')

затем

s.scan(/.{19}/)
["\xA8lf\xF9\xD4\x7F\x00\x00\x9A\x99Y@33\xB3@\x9A\x99\x11", "A\x80lf\xF9\xD4\x7F\x00\x00\x00\x00 @ff\x0EAff"]

Наконец

s.scan(/.{19}/).map{ |item| item.unpack('P7fff') }
Error: #<ArgumentError: no associated pointer>
<main>:in `unpack'
<main>:in `block in <main>'
<main>:in `map'
<main>:in `<main>'

Ответы [ 2 ]

2 голосов
/ 12 июня 2019

Вы можете прочитать файл небольшими порциями по 19 байт и использовать 'A7fff' для упаковки и распаковки.Не используйте указатели для структурирования ('p' и 'P'), поскольку им требуется более 19 байтов для кодирования вашей информации.Вы также можете использовать 'A6xfff', чтобы игнорировать 7-й байт и получить строку с 6 символами.

Вот пример, аналогичный документации IO.read:

data = [["ABCDEF\t", 3.4, 5.6, 9.1], 
        ["FEDCBA\t", 2.5, 8.9, 3.1]]
binary_file = 'data.bin'
chunk_size = 19
pattern = 'A7fff'

File.open(binary_file, 'wb') do |o|
  data.each do |row|
    o.write row.pack(pattern)
  end
end

raise "Something went wrong. Please check data, pattern and chunk_size." unless File.size(binary_file) == data.length * chunk_size

File.open(binary_file, 'rb') do |f|
  while record = f.read(chunk_size)
    puts '%s %g %g %g' % record.unpack(pattern)
  end
end
# =>
#    ABCDEF   3.4 5.6 9.1
#    FEDCBA   2.5 8.9 3.1

Вы можете использовать кратное 19 для ускорения процесса, если ваш файл большой.

0 голосов
/ 12 июня 2019

При работе со смешанными форматами, которые повторяются и имеют известный фиксированный размер, часто проще сначала разбить строку,

Быстрый пример будет:

binary.scan(/.{LENGTH_OF_DATA}/).map { |item| item.unpack(FORMAT) }

Рассматривая приведенный выше пример, возьмите длину строки, включая символ табуляции (в байтах), плюс размер 3-х чисел. Если ваши строки буквально 'ABCDEF\t', вы должны использовать размер 19 (7 для строки, 12 для 3-х чисел).

Ваш конечный продукт будет выглядеть так:

str.scan(/.{19}/).map { |item| item.unpack('P7fff') }

За пример:

irb(main):001:0> ary = ["ABCDEF\t", 3.4, 5.6, 9.1, "FEDCBA\t", 2.5, 8.9, 3.1]
=> ["ABCDEF\t", 3.4, 5.6, 9.1, "FEDCBA\t", 2.5, 8.9, 3.1]

irb(main):002:0> s = ary.pack('pfffpfff')
=> "\xE8Pd\xE4eU\x00\x00\x9A\x99Y@33\xB3@\x9A\x99\x11A\x98Pd\xE4eU\x00\x00\x00\x00 @ff\x0EAffF@"

irb(main):003:0> s.unpack('pfffpfff')
=> ["ABCDEF\t", 3.4000000953674316, 5.599999904632568, 9.100000381469727, "FEDCBA\t", 2.5, 8.899999618530273, 3.0999999046325684]

Незначительные различия в точности неизбежны, но не стоит об этом беспокоиться, поскольку они проистекают из разницы 32-разрядных чисел с плавающей запятой и 64-разрядных двойных (то, что Ruby использовал внутри), а разница точности будет меньше имеет значение для 32-разрядного числа с плавающей запятой.

...