Самый компактный способ кодирования последовательности двоичных кодов случайной длины?

допустим, у вас есть List<List<Boolean>> и вы хотите кодировать это в двоичную форму самым компактным способом.

меня не волнует чтения или записи. Я просто хочу использовать минимальное пространство. Кроме того, пример находится в Java, но мы не ограничены системой Java. Длина каждого "списка" -неограниченная. Поэтому любое решение, которое кодирует длину каждого списка, должно само по себе кодировать данные переменной длины тип.

С этой проблемой связано кодирование целых чисел переменной длины. Вы можете думать о каждом List<Boolean> как переменная длина unsigned integer.

пожалуйста, внимательно прочитайте вопрос. Мы не ограничиваемся системой Java.

редактировать

Я не понимаю, почему многие ответы говорят о сжатии. Я не пытаюсь сделать сжатие как таковое, а просто кодирование случайные последовательность бит вниз. Кроме каждой последовательности биты имеют разную длину и порядок должен быть сохранен.

вы можете думать об этом вопросе по-другому. Допустим, у вас есть список произвольных списков случайных целых чисел без знака (неограниченных). Как вы кодируете этот список в двоичном файле?

исследования

Я сделал некоторое чтение и нашел то, что я действительно ищу, это универсальный код

результат

Я собираюсь использовать вариант Кодировка Elias Omega описано в статье новый рекурсивный универсальный код для натуральных чисел

Теперь я понимаю, как меньшее представление меньших целых чисел является компромиссом с большими целыми числами. Просто выбрав универсальный код с "большим" представлением самого первого целого числа, вы экономите много места в долгосрочной перспективе, когда вам нужно кодировать произвольные большие целые числа.

16 ответов


Я думаю о кодировании битовой последовательности, как это:

head  | value
------+------------------
00001 | 0110100111000011

Head имеет переменную длину. Заканчивается первое появление 1. Подсчитать количество нулей в голове. Длина


я мало знаю о Java, поэтому я думаю, что мое решение должно быть общим:)

1. Компактные списки

поскольку логические значения неэффективны, каждый List<Boolean> должен быть уплотнен в List<Byte>, это легко, просто захватить их 8 за раз.

последний "байт" может быть неполным, поэтому вам нужно сохранить, сколько бит было закодировано, конечно.

2. Сериализация списка элементы

у вас есть 2 варианта дальнейших действий: либо вы кодируете количество элементов списка, либо использовать шаблон, чтобы отметить конец. Я бы рекомендовал кодировать количество элементов, подход к шаблону требует экранирования, и это жутко, плюс это сложнее с упакованными битами.

для кодирования длины вы можете использовать переменную схему: т. е. количество байтов, необходимое для кодирования длины, должно быть пропорционально длине, которую я уже использовал. Вы можете укажите, сколько байтов используется для кодирования самой длины с помощью префикса на первом байте:

0... .... > this byte encodes the number of items (7 bits of effective)
10.. .... / .... .... > 2 bytes
110. .... / .... .... / .... .... > 3 bytes

это довольно эффективное пространство, и декодирование происходит на целых байтах, поэтому не слишком сложно. Можно отметить, что это очень похоже на схему UTF8:)

3. Применить рекурсивно

List< List< Boolean > > становится [Length Item ... Item] каждая Item само представление List<Boolean>

4. Zip

я полагаю, что есть zlib библиотека доступна для Java, или что-нибудь еще, как deflate или lcw. Передайте ему свой буфер и убедитесь, что вы хотите как можно больше сжатия, независимо от времени.

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

5. Примеры

где можно заметить, что края списки занимает много места :)

// Tricky here, we indicate how many bits are used, but they are packed into bytes ;)
List<Boolean> list = [false,false,true,true,false,false,true,true]
encode(list) == [0x08, 0x33] // [00001000, 00110011]  (2 bytes)

// Easier: the length actually indicates the number of elements
List<List<Boolean>> super = [list,list]
encode(super) == [0x02, 0x08, 0x33, 0x08, 0x33] // [00000010, ...] (5 bytes)

6. Потребление пространства

Предположим, у нас есть List<Boolean> of n booleans, пространство, потребляемое для его кодирования:

booleans = ceil( n / 8 )

для кодирования количества битов (n), нам нужно:

length = 1   for 0    <= n < 2^7   ~ 128
length = 2   for 2^7  <= n < 2^14  ~ 16384
length = 3   for 2^14 <= n < 2^21  ~ 2097152
...
length = ceil( log(n) / 7 )  # for n != 0 ;)

таким образом, чтобы полностью перекодировать список:

bytes =
 if n == 0: 1
 else     : ceil( log(n) / 7 ) + ceil( n / 8 )

7. Маленькие Списки

есть один угловой случай: нижний конец спектра (т. е. почти пустой список).

на n == 1, bytes оценивается в 2, которое действительно может показаться расточительным. Однако я бы не стал пытаться угадать, что произойдет, когда начнется сжатие.

возможно, вы захотите упаковать еще больше. Это возможно, если мы откажемся от идеи сохранения всей байты...

  1. сохранить кодировку длины как есть (на целых байтах), но не "pad"List<Boolean>. Список одного элемента становится 0000 0001 x (9 бит)
  2. попробуйте "упаковать" кодировку длины, а также

второй момент сложнее, мы эффективно до кодирования двойной длины:

  1. указывает, сколько битов, кодирующих длину
  2. фактически Закодируйте длину на этих Битс!--101-->

например:

0  -> 0 0
1  -> 0 1
2  -> 10 10
3  -> 10 11
4  -> 110 100
5  -> 110 101
8  -> 1110 1000
16 -> 11110 10000 (=> 1 byte and 2 bits)

он работает довольно хорошо для очень маленьких списков, но быстро вырождается:

# Original scheme
length = ceil( ( log(n) / 7)

# New scheme
length = 2 * ceil( log(n) )

переломный момент ? 8

Да, вы правильно прочитали, это только лучше для списка с менее чем 8 элементами... и только лучше "битами".

n         -> bits spared
[0,1]     ->  6
[2,3]     ->  4
[4,7]     ->  2
[8,15]    ->  0    # Turn point
[16,31]   -> -2
[32,63]   -> -4
[64,127]  -> -6
[128,255] ->  0    # Interesting eh ? That's the whole byte effect!

и, конечно, как только сжатие срабатывает, скорее всего, это не будет иметь значения.

я понимаю, что вы можете оценить recursive алгоритм, но я бы все равно посоветовал вычислить цифры фактического потребления пространства или даже лучше фактически протестировать его с архивированием, применяемым на реальных тестовых наборах.

8. Рекурсивное / переменное кодирование

я прочитал с интересом TheDonответ, и ссылка, которую он представил Кодировка Elias Omega.

это здравые ответы, в теоретической области. К сожалению, они совершенно непрактичны. Этот основная проблема заключается в том, что у них чрезвычайно интересное асимптотическое поведение, но когда нам действительно нужно кодировать гигабайт данных ? Редко, если вообще когда-либо.

недавнее исследование использования памяти на работе показало, что большинство контейнеров использовались для дюжины элементов (или нескольких десятков). Только в очень редких случаях мы достигаем тысячи. Конечно, для вашей конкретной проблемы лучшим способом было бы фактически изучить ваши собственные данные и увидеть распределение значений, но из опыта я можно сказать, что вы не можете просто сосредоточиться на верхнем конце спектра, потому что ваши данные лежат в нижнем конце.

пример 'ы. Скажем у меня есть список [0,1,0,1,0,1,0,1]

len('01010101') = 8 -> 1000
len('1000')     = 4 -> 100
len('100')      = 3 -> 11
len('11')       = 2 -> 10

encode('01010101') = '10' '0' '11' '0' '100' '0' '1000' '1' '01010101'

len(encode('01010101')) = 2 + 1 + 2 + 1 + 3 + 1 + 4 + 1 + 8 = 23

давайте сделаем небольшую таблицу, с различными 'пороги', чтобы остановить рекурсию. Он представляет собой количество бит накладных расходов для различных диапазонов n.

threshold     2    3    4    5      My proposal
-----------------------------------------------
[0,3]    ->   3    4    5    6           8
[4,7]    ->   10   4    5    6           8
[8,15]   ->   15   9    5    6           8
[16,31]  ->   16   10   5    6           8
[32,63]  ->   17   11   12   6           8
[64,127] ->   18   12   13   14          8
[128,255]->   19   13   14   15         16
1 на log функция почти линейна, и, таким образом, рекурсия теряет свое очарование. Пороге очень сильно помогают и 3 кажется, хороший кандидат...

как Elias omega coding, это еще хуже. Из статьи Википедии:

17 -> '10 100 10001 0'

вот и все, улюлюканье 11 бит.

мораль: вы не можете выбрать схему кодирования без учета данных на рука.

поэтому, если ваш List<Boolean> имейте длину в сотнях, не беспокойтесь и придерживайтесь моего маленького предложения.


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

11000101 10010110 00100000

на самом деле означало бы:

   10001 01001011 00100000

так как целое число продолжается 2 раза.

эти целые числа переменной длины скажут, сколько битов нужно прочитать. И в начале всего будет еще один int переменной длины, чтобы сказать, сколько битовых наборов читать.

оттуда, предположим, вы не хотите использовать сжатие, единственный способ, который я вижу, чтобы оптимизировать его размер-это адаптировать его к вашей ситуации. Если у вас часто есть большие наборы битов, вы можете, например, использовать короткие целые числа вместо байтов для целочисленной кодировки переменной длины, что делает вас потенциально тратить меньше битов в самой кодировке.


редактировать Я не думаю, что существует идеальный способ достичь всего, что вы хотите, все на однажды. Вы не можете создавать информацию из ничего, и если вам нужны целые числа переменной длины, вы, очевидно, должны также кодировать целочисленную длину. Есть обязательно компромисс между пространством и информацией, но есть также минимальная информация, которую вы не можете вырезать, чтобы использовать меньше места. Ни одна система, где факторы растут с разной скоростью, никогда не будет масштабироваться идеально. Это все равно что пытаться провести прямую линию по логарифмической кривой. Ты не можешь этого сделать. (И кроме того, это довольно именно это ты и пытаешься сделать.)

вы не можете кодировать длину целого числа переменной длины вне целого числа и получать целые числа переменных неограниченного размера одновременно, потому что это потребует, чтобы сама длина была переменной длины, и какой бы алгоритм вы ни выбрали, мне кажется здравым смыслом, что вам будет лучше всего с одним целым числом переменной длины вместо двух или более из них.

Итак, вот моя другая идея: в заголовке integer", напишите один 1 для каждого байта, который требует целое число переменной длины. Первый 0 обозначает конец "заголовка" и начало самого целого числа.

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


EDIT 2 Здесь уравнения:

  • решение один, 7 бит на бит кодирования (один полный байт за раз):
    y = 8 * ceil(log(x) / (7 * log(2)))
  • решение один, 3 бита на бит кодирования (по одному кусочку за раз):
    y = 4 * ceil(log(x) / (3 * log(2)))
  • решение два, 1 байт на бит кодирования плюс разделитель:
    y = 9 * ceil(log(x) / (8 * log(2))) + 1
  • решение два, 1 кусочек на бит кодирования плюс разделитель:
    y = 5 * ceil(log(x) / (4 * log(2))) + 1

Я предлагаю вам взять время, чтобы построить их (лучше смотреть с логарифмически-линейная система координат), чтобы получить идеал решение для вашего случая, потому что нет идеальный решение. На мой взгляд, первое решение имеет самые стабильные результаты.


Я думаю, что для "максимально компактного способа" вам понадобится некоторое сжатие, но кодирование Хаффмана может быть не так, как я думаю, что это лучше всего работает с алфавитами, которые имеют статические частоты символов.

проверить Арифметическое Кодирование - он работает на битах и может адаптироваться к динамической входной вероятности. Я также вижу, что есть BSD-лицензия Java-библиотека это сделает это для вас, который, похоже, ожидает одиночных битов в качестве входных данных.

I предположим, для максимального сжатия вы можете объединить каждый внутренний список (с префиксом его длины) и снова запустить алгоритм кодирования по всей партии.


Я не вижу, как кодирование произвольный набор битов отличается от сжатия/кодирования любой другой тип данных. Обратите внимание, что вы накладываете только свободное ограничение на биты, которые вы кодируете: а именно, это списки списков битов. С этим небольшим ограничением этот список битов становится просто данными, произвольными данными, и это то, что сжимают" нормальные " алгоритмы сжатия.

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

учитывая ваши предпосылки и как работают алгоритмы сжатия, я бы посоветовал сделать следующее:

  1. упакуйте биты каждого списка, используя меньшее возможное количество байтов, используя байты в качестве битовых полей, кодируя длину и т. д.
  2. попробуйте huffman, арифметику, LZxx и т. д. в результирующем потоке байтов.

можно утверждают, что это довольно очевидный и простой способ сделать это, и что это не будет работать как последовательность битов не известный шаблон. Но дело в том, что это лучшее, что вы можете делать в любом случае.

Если вы не знаете что-то из ваших данных или некоторого преобразования в тех списках, которые заставляют их поднимать какой-то шаблон. Возьмем, например, кодирование коэффициентов DCT в кодировке JPEG. Способ перечисления этих коэффициентов (по диагонали и в зигзаг) сделан в пользу паттерна при выводе различных коэффициентов для преобразования. Таким образом, к полученным данным можно применить традиционные сжатия. Если вы знаете что-то из этих списков битов, которые позволяют вам переупорядочить их более сжимаемым способом (способом, который показывает некоторую структуру), вы получите сжатие.


У меня есть подозрение, что вы просто не можете кодировать действительно случайный набор битов в более компактную форму в худшем случае. Любой вид RLE будет раздувать набор только на неправильном входе, хотя он будет хорошо работать в среднем и лучшем случае. Любой вид периодического или контент-специфического приближения потеряет данные.

Как говорится в одном из других плакатов, вы должны знать что-то о наборе данных, чтобы представить его в более компактной форме и / или вы нужно принять некоторые потери, чтобы получить предсказуемую форму, которая может быть более компактно выражена.

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

http://en.wikipedia.org/wiki/Information_theory

вы всегда можете обмануть, я полагаю, и манипулировать оборудованием для кодирования дискретного диапазона значений на носителе, чтобы дразнить еще несколько "бит за бит" (думаю, мультиплексирование). Вы бы потратили больше времени, кодируя и читая его.

практически, Вы всегда можете попробовать эффект "покачивания", где вы кодируете данные несколько раз несколькими способами (попробуйте интерпретировать как аудио, видео, 3d, периодический, последовательный, основанный ключ, диффс, etc...) и в нескольких размерах страниц и выбрать лучший. Вы будете в значительной степени гарантированно иметь лучшее разумное сжатие, и ваш худший случай будет не хуже, чем ваш исходный набор данных.

Не знаю, если это даст вам теоретическое лучшее, хотя.


Теоретические Пределы

Это сложный вопрос для ответа, не зная больше о данных, которые вы намерены сжать; ответ на ваш вопрос может быть различным с разными доменами.

например,раздел ограничений статьи Википедии о сжатии без потерь:

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

в принципе, поскольку теоретически невозможно сжать все возможные входные данные без потерь, невозможно даже эффективно ответить на ваш вопрос.

практичный компромисс

просто используйте Huffman, выкачайте, 7Z, или какой-то ZIP-подобный готовый алгоритм сжатия и enocde биты как массивы байтов переменной длины (или списки, или векторы, или как они называются на Java или любом языке, который вам нравится). Конечно, чтобы прочитать биты обратно, может потребоваться немного декомпрессии, но это можно сделать за кулисами. Вы можете создать класс, который скрывает внутренние методы реализации для возврата списка или массива логических значений в некотором диапазоне индексов, несмотря на то, что данные хранятся внутри упакуйте байтовые массивы. Обновление логического значения при заданном индексе или индексах может быть проблемой, но отнюдь не невозможно.


List-of-Lists-of-Ints-кодировка:

  • когда вы придете к началу списка, запишите биты для ASCII' ['. Затем перейдите в список.

  • когда вы приходите к любому произвольному двоичному числу, запишите биты, соответствующие десятичному представлению числа в ASCII. Например число 100, напишите 0x31 0x30 0x30. Затем напишите биты, соответствующие ASCII ','.

  • когда вы приходите к конец списка, запишите биты для']'. Затем напишите ASCII ','.

эта кодировка будет кодировать любое произвольно глубокое вложение списков произвольной длины неограниченных целых чисел. Если эта кодировка недостаточно компактна, выполните ее с помощью gzip, чтобы устранить избыточность в ASCII-битном кодировании.


вы можете преобразовать каждый список в битовый набор, а затем сериализовать битовый набор-s.


Ну, во-первых, вы захотите упаковать эти булевы вместе, так что вы получаете восемь из них в байт. стандарт C++bitset был предназначен для этой цели. вы, вероятно, должны использовать его изначально вместо вектора, если можете.

после этого вы можете теоретически сжать его, когда вы сохраните, чтобы получить размер еще меньше. Я бы не советовал этого делать, если только вы не прижаты спиной к стене.

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

Если ваш битсет довольно разреженный (не много 0 или не много 1), или полосатый (длинные прогоны одного и того же значения), то это возможно, вы могли бы получить большие выгоды от сжатия. Почти во всех других обстоятельствах его не стоит. Даже при таких обстоятельствах этого может и не быть. Помните, что любой добавленный вами код необходимо отлаживать и поддерживать.


Как вы указываете, нет причин хранить ваши логические значения, используя больше места, чем один бит. Если вы объедините это с некоторой базовой конструкцией, например, каждая строка начинается с целого числа, кодирующего количество битов в этой строке, вы сможете сохранить 2D-таблицу любого размера, где каждая запись в строке является одним битом.

однако этого недостаточно. Строка произвольных 1 и 0 будет выглядеть довольно случайной, и любой алгоритм сжатия ломается как случайность ваших данных увеличивается - поэтому я бы рекомендовал такой процесс, как сортировка блоков Берроуза-Уилера, чтобы значительно увеличить количество повторяющихся "слов" или "блоков" в ваших данных. После этого простой код Хаффмана или алгоритм Lempel-Ziv должны быть в состоянии сжать ваш файл довольно красиво.

чтобы вышеуказанный метод работал для целых чисел без знака, вы должны сжимать целые числа с помощью Дельта-кодов, а затем выполнять сортировку и сжатие блоков (стандартная практика в Списки информационных проводок поиска).


Если я правильно понял вопрос, биты случайны, и у нас есть список произвольной длины независимых списков произвольной длины. Поскольку нет ничего, чтобы иметь дело с байтами, я буду обсуждать это как битовый поток. Поскольку файлы фактически содержат байты, вам нужно будет поместить восемь бит для каждого байта и оставить 0..7 бит последнего байта не используется.

наиболее эффективным способом хранения логических значений является as-is. Просто сбросьте их в bitstream как простой матрица.

в начале битового потока вам нужно кодировать длины массива. Есть много способов сделать это, и вы можете сэкономить немного, выбрав наиболее оптимальный для ваших массивов. Для этого вы, вероятно, захотите использовать кодирование Хаффмана с фиксированной кодовой книгой, чтобы часто используемые и малые значения получали самые короткие последовательности. Если список очень длинный, вы, вероятно, не будете так заботиться о размере его кодирования в более длинной форме.

точный ответ что касается того, что кодовая книга (и, следовательно, код Хаффмана) будет не может быть предоставлена без дополнительной информации об ожидаемых длинах списка.

Если все внутренние списки имеют одинаковый размер (т. е. у вас есть 2D-массив), вам нужны только два измерения, конечно.

десериализация: декодируйте длины и выделите структуры, затем прочитайте биты один за другим, назначая их структуре по порядку.


ответ@zneak (опередите меня), но используйте целые числа, закодированные Хаффманом, особенно если некоторые длины более вероятны.

просто чтобы быть автономным: кодируйте количество списков как целое число, закодированное Хаффманом, затем для каждого списка кодируйте его битовую длину как целое число, закодированное Хаффманом. Биты для каждого списка следуют без промежуточных потерянных битов.

Если порядок списков не имеет значения, сортировка их по длине уменьшит необходимое пространство, только инкрементное увеличение длины каждого последующего списка должно быть закодировано.


List-of-List-of-Ints-binary:

Start traversing the input list
For each sublist:
    Output 0xFF 0xFE
    For each item in the sublist:
        Output the item as a stream of bits, LSB first.
          If the pattern 0xFF appears anywhere in the stream,
          replace it with 0xFF 0xFD in the output.
        Output 0xFF 0xFC

декодирования:

If the stream has ended then end any previous list and end reading.
Read bits from input stream. If pattern 0xFF is encountered, read the next 8 bits.
   If they are 0xFE, end any previous list and begin a new one.
   If they are 0xFD, assume that the value 0xFF has been read (discard the 0xFD)
   If they are 0xFC, end any current integer at the bit before the pattern, and begin reading a new one at the bit after the 0xFC.
   Otherwise indicate error. 

Если я правильно понимаю, наша структура данных( 1 2 ( 33483 7 ) 373404 9 ( 337652222 37333788 ) )

вот так:
byte 255 - escape code
byte 254 - begin block
byte 253 - list separator
byte 252 - end block

Итак, мы имеем:

 struct {
    int nmem; /* Won't overflow -- out of memory first */
    int kind; /* 0 = number, 1 = recurse */
    void *data; /* points to array of bytes for kind 0, array of bigdat for kind 1 */
 } bigdat;

 int serialize(FILE *f, struct bigdat *op) {
   int i;
   if (op->kind) {
      unsigned char *num = (char *)op->data;
      for (i = 0; i < op->nmem; i++) {
         if (num[i] >= 252)
            fputs(255, f);
         fputs(num[i], f);
      }
   } else {
      struct bigdat *blocks = (struct bigdat *)op->data
      fputs(254, f);
      for (i = 0; i < op->nmem; i++) {
          if (i) fputs(253, f);
          serialize(f, blocks[i]);
      }
      fputs(252, f);
 }

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

не длина кодирования перед каждым занимает гораздо меньше места, но делает десериализацию трудное упражнение.


этот вопрос имеет определенное индукционное ощущение. Вы хотите функцию: (список списка bool) - > (список bool), так что обратная функция (список bool) - > (список списка bool) генерирует ту же исходную структуру, и длина закодированного списка bool минимальна, без наложения ограничений на входную структуру. Поскольку этот вопрос настолько абстрактен, я думаю, что эти списки могут быть умопомрачительно большими - 10^50, может быть, или 10^2000, или они могут быть очень маленькими, например 10^0. Также, там может быть большое количество списков, опять 10^50 или 1. Поэтому алгоритм должен адаптироваться к этим широко различным входным сигналам.

Я думаю, что мы можем закодировать длину каждого списка как (список bool) и добавить один дополнительный bool, чтобы указать, является ли следующая последовательность другой (теперь большей) длиной или реальным битовым потоком.

let encode2d(list1d::Bs) = encode1d(length(list1d), true) @ list1d @ encode2d(Bs)
    encode2d(nil)       = nil

let encode1d(1, nextIsValue) = true :: nextIsValue :: []
    encode1d(len, nextIsValue) = 
               let bitList = toBoolList(len) @ [nextIsValue] in
               encode1d(length(bitList), false) @ bitList

let decode2d(bits) = 
               let (list1d, rest) = decode1d(bits, 1) in
               list1d :: decode2d(rest)

let decode1d(bits, n) = 
               let length = fromBoolList(take(n, bits)) in
               let nextIsValue :: bits' = skip(n, bits) in
               if nextIsValue then bits' else decode1d(bits', length)
assumed library functions
-------------------------

toBoolList : int -> bool list
   this function takes an integer and produces the boolean list representation
   of the bits.  All leading zeroes are removed, except for input '0' 

fromBoolList : bool list -> int
   the inverse of toBoolList

take : int * a' list -> a' list
   returns the first count elements of the list

skip : int * a' list -> a' list
   returns the remainder of the list after removing the first count elements

накладные расходы на отдельный список bool. Для пустого списка накладные расходы составляют 2 дополнительных элемента списка. Для 10^2000 bools, надбавки будут 6645 + 14 + 5 + 4 + 3 + 2 = 6673 дополнительные элементы списка.