Compare commits

..

No commits in common. "12b209c724fe82eca4add43a2212760914a47a14" and "405ff2ec2b761860728524b48637ff520a9030b3" have entirely different histories.

5 changed files with 156 additions and 206 deletions

View File

@ -84,17 +84,21 @@ data class ObjString(val value: String) : Obj() {
override suspend fun lynonType(): LynonType = LynonType.String
override suspend fun serialize(scope: Scope, encoder: LynonEncoder, lynonType: LynonType?) {
val data = value.encodeToByteArray()
encoder.encodeCached(data) { encoder.encodeBinaryData(data) }
if( lynonType == null )
encoder.encodeCached(this) { encoder.encodeBinaryData(value.encodeToByteArray()) }
else
encoder.encodeBinaryData(value.encodeToByteArray())
}
companion object {
val type = object : ObjClass("String") {
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj =
if( lynonType == null )
decoder.decodeCached {
ObjString(decoder.unpackBinaryData().decodeToString())
}
else ObjString(decoder.unpackBinaryData().decodeToString())
}.apply {
addFn("toInt") {
ObjInt(thisAs<ObjString>().value.toLong())

View File

@ -39,8 +39,9 @@ open class LynonDecoder(val bin: BitInput, val settings: LynonSettings = LynonSe
type.objClass.deserialize(scope, this, type)
}
suspend fun decodeObject(scope: Scope, type: ObjClass): Obj {
return decodeCached { type.deserialize(scope, this, null) }
// todo: rewrite/remove?
suspend fun unpackObject(scope: Scope, type: ObjClass): Obj {
return type.deserialize(scope, this, null)
}
fun unpackBinaryData(): ByteArray = bin.decompress()

View File

@ -4,21 +4,21 @@ import net.sergeych.bintools.ByteChunk
import net.sergeych.lyng.Scope
import net.sergeych.lyng.obj.*
enum class LynonType(val objClass: ObjClass,val defaultFrequency: Int = 1) {
Null(ObjNull.objClass, 80),
Int0(ObjInt.type, 70),
IntNegative(ObjInt.type, 50),
IntPositive(ObjInt.type, 100),
String(ObjString.type, 100),
enum class LynonType(val objClass: ObjClass) {
Null(ObjNull.objClass),
Int0(ObjInt.type),
IntNegative(ObjInt.type),
IntPositive(ObjInt.type),
String(ObjString.type),
Real(ObjReal.type),
Bool(ObjBool.type, 80),
List(ObjList.type, 70),
Map(ObjMap.type,40),
Bool(ObjBool.type),
List(ObjList.type),
Map(ObjMap.type),
Set(ObjSet.type),
Buffer(ObjBuffer.type, 50),
Instant(ObjInstant.type, 30),
Buffer(ObjBuffer.type),
Instant(ObjInstant.type),
Duration(ObjDuration.type),
Other(Obj.rootObjectType,60);
Other(Obj.rootObjectType);
}
open class LynonEncoder(val bout: BitOutput, val settings: LynonSettings = LynonSettings.default) {
@ -65,10 +65,8 @@ open class LynonEncoder(val bout: BitOutput, val settings: LynonSettings = Lynon
bout.putBits(type.ordinal.toULong(), 4)
}
suspend fun encodeObject(scope: Scope, obj: Obj) {
encodeCached(obj) {
suspend fun encodeObj(scope: Scope, obj: Obj) {
obj.serialize(scope, this, null)
}
}
fun encodeBinaryData(data: ByteArray) {

View File

@ -1,68 +1,38 @@
package net.sergeych.lynon
import net.sergeych.collections.SortedList
import net.sergeych.lynon.Huffman.Alphabet
/**
* Generic huffman encoding implementation using bits input/output and abstract [Alphabet].
* Experimental, reference implementation of Huffman trees and encoding.
*
* This is a reference huffman encoding implementation not yet ready;
* it was used to experiment with LZW, at the moment, LZW won the competition
* for compressed module format for its speed and sufficiently small size/
*
* This is byte-based compressor which makes it not too interesting.
*
* TODO: convert to use various source dictionary
*
* reason: version thant compress bytes is not too interesting; particular alphabets
* are often longer than byte bits and are often sparse, that requires another
* codes serialization implementation
*/
object Huffman {
/**
* Alphabet interface: source can be variable bit size codes, not just bytes,
* so the Huffman encoding is not limited to bytes. It works with any alphabet
* using its _ordinals_; encoding between source symbols and ordinals are
* performed by the alphabet. See [byteAlphabet] for example.
*/
interface Alphabet<T> {
val maxOrdinal: Int
/**
* Write correct symbol for the [ordinal] to the [bout]. This is
* the inverse of [ordinalOf] but as [T] could be variable bit size,
* we provide output bit stream.
*/
fun decodeOrdinalTo(bout: BitOutput, ordinal: Int)
/**
* Find the ordinal of the source symbol
*/
fun ordinalOf(value: T): Int
operator fun get(ordinal: Int): T
}
/**
* Alphabet for unsigned bytes, allows to encode bytes easily
*/
val byteAlphabet = object : Alphabet<UByte> {
override val maxOrdinal: Int
get() = 256
override fun decodeOrdinalTo(bout: BitOutput, ordinal: Int) {
bout.putBits(ordinal, 8)
}
override fun ordinalOf(value: UByte): Int = value.toInt()
override operator fun get(ordinal: Int): UByte = ordinal.toUByte()
}
sealed class Node(val freq: Int) : Comparable<Node> {
override fun compareTo(other: Node): Int {
return freq.compareTo(other.freq)
}
abstract fun decodeOrdinal(bin: BitInput): Int?
abstract fun decode(bin: BitInput): Int?
class Leaf(val ordinal: Int, freq: Int) : Node(freq) {
class Leaf(val value: Int, freq: Int) : Node(freq) {
override fun toString(): String {
return "[$ordinal:$freq]"
return "[$value:$freq]"
}
override fun decodeOrdinal(bin: BitInput): Int {
return ordinal//.also { println(": ${Char(value)}") }
override fun decode(bin: BitInput): Int {
return value//.also { println(": ${Char(value)}") }
}
}
@ -71,33 +41,33 @@ object Huffman {
return "[${left.freq}<- :<$freq>: ->${right.freq}]"
}
override fun decodeOrdinal(bin: BitInput): Int? {
override fun decode(bin: BitInput): Int? {
return when (bin.getBitOrNull().also { print("$it") }) {
1 -> left.decodeOrdinal(bin)
0 -> right.decodeOrdinal(bin)
1 -> left.decode(bin)
0 -> right.decode(bin)
else -> null
}
}
}
}
data class Code(val ordinal: Int, val bits: TinyBits) {
data class Code(val symbol: Int, val bits: TinyBits) {
val size by bits::size
override fun toString(): String {
return "[$ordinal:$size:$bits]"
return "[${Char(symbol)}:$size:$bits]"
}
}
private fun generateCanonicCodes(tree: Node, alphabet: Alphabet<*>): List<Code?> {
val codes = MutableList<Code?>(alphabet.maxOrdinal) { null }
private fun generateCanonicCodes(tree: Node): List<Code?> {
val codes = MutableList<Code?>(256) { null }
fun traverse(node: Node, code: TinyBits) {
when (node) {
is Node.Leaf ->
codes[node.ordinal] = (Code(node.ordinal, code))
codes[node.value] = (Code(node.value, code))
is Node.Internal -> {
traverse(node.left, code.insertBit(1))
@ -107,17 +77,17 @@ object Huffman {
}
traverse(tree, TinyBits())
return makeCanonical(codes, alphabet)
return makeCanonical(codes)
}
private fun makeCanonical(source: List<Code?>,alphabet: Alphabet<*>): List<Code?> {
private fun makeCanonical(source: List<Code?>): List<Code?> {
val sorted = source.filterNotNull().sortedWith(canonicComparator)
val canonical = MutableList<Code?>(alphabet.maxOrdinal) { null }
val canonical = MutableList<Code?>(256) { null }
val first = sorted[0]
val prevValue = first.copy(bits = TinyBits(0UL, first.bits.size))
canonical[first.ordinal] = prevValue
canonical[first.symbol] = prevValue
var prev = prevValue.bits
for (i in 1..<sorted.size) {
@ -126,7 +96,7 @@ object Huffman {
while (code.bits.size > bits.size) {
bits = bits.insertBit(0)
}
canonical[code.ordinal] = code.copy(bits = bits)//.also { println("$it") }
canonical[code.symbol] = code.copy(bits = bits)//.also { println("$it") }
prev = bits
}
return canonical
@ -134,21 +104,18 @@ object Huffman {
private val canonicComparator = { a: Code, b: Code ->
if (a.bits.size == b.bits.size) {
a.ordinal.compareTo(b.ordinal)
a.symbol.compareTo(b.symbol)
} else {
a.bits.size.compareTo(b.bits.size)
}
}
private fun buildTree(data: Iterable<Int>,alphabet: Alphabet<*>): Node {
val frequencies = buildFrequencies(alphabet, data)
return buildTree(frequencies)
}
private fun buildTree(frequencies: Array<Int>): Node {
private fun buildTree(data: UByteArray): Node {
// println(data.toDump())
val frequencies = Array(256) { 0 }
data.forEach { frequencies[it.toInt()]++ }
val list: SortedList<Node> = SortedList(*frequencies.mapIndexed { index, frequency -> Node.Leaf(index, frequency) }.filter { it.freq > 0 }
val list = SortedList<Node>(*frequencies.mapIndexed { index, i -> Node.Leaf(index, i) }.filter { it.freq > 0 }
.toTypedArray())
// build the tree
@ -160,18 +127,8 @@ object Huffman {
return list[0]
}
private fun buildFrequencies(
alphabet: Alphabet<*>,
data: Iterable<Int>
): Array<Int> {
val maxOrdinal = alphabet.maxOrdinal
val frequencies = Array(maxOrdinal) { 0 }
data.forEach { frequencies[it]++ }
return frequencies
}
fun decompressUsingCodes(bin: BitInput, codes: List<Code?>, alphabet: Alphabet<*>): BitArray {
val result = MemoryBitOutput()
fun decompressUsingCodes(bin: BitInput, codes: List<Code?>): UByteArray {
val result = mutableListOf<UByte>()
val table = codes.filterNotNull().associateBy { it.bits }
outer@ while (true) {
@ -182,12 +139,12 @@ object Huffman {
val data = table[input]
if (data != null) {
// println("Code found: ${data.bits} -> [${data.symbol.toChar()}]")
alphabet.decodeOrdinalTo(result,data.ordinal)
result.add(data.symbol.toUByte())
break
}
}
}
return result.toBitArray()
return result.toUByteArray()
}
private fun serializeCanonicCodes(bout: BitOutput, codes: List<Code?>) {
@ -210,11 +167,11 @@ object Huffman {
}
}
fun deserializeCanonicCodes(bin: BitInput, alphabet: Alphabet<*>): List<Code?> {
fun deserializeCanonicCodes(bin: BitInput): List<Code?> {
val minSize = bin.unpackUnsigned().toInt()
val sizeInBits = bin.unpackUnsigned().toInt()
val sorted = mutableListOf<Code>().also { codes ->
for (i in 0..<alphabet.maxOrdinal) {
for (i in 0..<256) {
val s = bin.getBits(sizeInBits).toInt()
if (s > 0) {
codes.add(Code(i, TinyBits(0U, s - 1 + minSize)))
@ -222,53 +179,66 @@ object Huffman {
}
}.sortedWith(canonicComparator)
val result = MutableList<Code?>(alphabet.maxOrdinal) { null }
val result = MutableList<Code?>(256) { null }
var prev = sorted[0].copy(bits = TinyBits(0U, sorted[0].bits.size))
result[prev.ordinal] = prev
result[prev.symbol] = prev
for (i in 1..<sorted.size) {
val code = sorted[i]
var bits = TinyBits(prev.bits.value + 1u, prev.bits.size)
while (bits.size < code.bits.size) bits = bits.insertBit(0)
result[code.ordinal] = code.copy(bits = bits).also {
result[code.symbol] = code.copy(bits = bits).also {
prev = it
}
}
return result
}
// fun generateCanonicalCodes(frequencies: Iterable<Int>): List<Code?> {
//
// }
fun compress(data: UByteArray): BitArray {
fun generateCanonicalCodes(frequencies: Array<Int>,alphabet: Alphabet<*>): List<Code?> =
generateCanonicCodes(buildTree(frequencies), alphabet)
val root = buildTree(data)
fun <T>compress(plain: Iterable<T>,alphabet: Alphabet<T>): BitArray {
val source = plain.map { alphabet.ordinalOf(it) }
val root = buildTree(source,alphabet)
val codes = generateCanonicCodes(root, alphabet)
val codes = generateCanonicCodes(root)
// serializa table
// test encode:
val bout = MemoryBitOutput()
serializeCanonicCodes(bout, codes)
for (i in source) {
val code = codes[i]!!
for (i in data) {
val code = codes[i.toInt()]!!
// println(">> $code")
bout.putBits(code.bits)
}
// println(bout.toBitArray().bytes.toDump())
val compressed = bout.toBitArray()
// println("Size: ${compressed.bytes.size / data.size.toDouble() }")
// println("compression ratio: ${compressed.bytes.size / data.size.toDouble() }")
// test decompress
// val bin = MemoryBitInput(compressed)
// val codes2 = deserializeCanonicCodes(bin)
// for ((a, b) in codes.zip(codes2)) {
// if (a != b) {
// println("Codes mismatch: $a != $b")
// break
// }
// }
// require(codes == codes2)
// val result = decompressUsingCodes(bin, codes2)
//
//// println(result.toUByteArray().toDump())
// check(data contentEquals result.toUByteArray())
// if( !(data contentEquals result.toUByteArray()) )
// throw RuntimeException("Data mismatch")
// println(data.toDump())
//
return compressed
}
fun <T>decompress(bin: BitInput,alphabet: Alphabet<T>): UByteArray {
val codes = deserializeCanonicCodes(bin, alphabet)
return decompressUsingCodes(bin, codes, alphabet).asUbyteArray()
fun decompress(bin: BitInput): UByteArray {
val codes = deserializeCanonicCodes(bin)
return decompressUsingCodes(bin, codes)
}
}

View File

@ -150,28 +150,28 @@ class LynonTests {
val encoder = LynonEncoder(bout)
val s = "Hello, World!".toObj()
val scope = Scope()
encoder.encodeObject(scope, s) // 1
encoder.encodeObject(scope, s)
encoder.encodeObject(scope, s)
encoder.encodeObject(scope, s)
encoder.encodeObject(scope, s)
encoder.encodeObject(scope, s)
encoder.encodeObject(scope, s)
encoder.encodeObject(scope, s) // 8
encoder.encodeObj(scope, s) // 1
encoder.encodeObj(scope, s)
encoder.encodeObj(scope, s)
encoder.encodeObj(scope, s)
encoder.encodeObj(scope, s)
encoder.encodeObj(scope, s)
encoder.encodeObj(scope, s)
encoder.encodeObj(scope, s) // 8
val decoder = LynonDecoder(MemoryBitInput(bout))
val s1 = decoder.decodeObject(scope, ObjString.type) // 1
val s1 = decoder.unpackObject(scope, ObjString.type) // 1
assertEquals(s, s1)
assertNotSame(s, s1)
val s2 = decoder.decodeObject(scope, ObjString.type)
val s2 = decoder.unpackObject(scope, ObjString.type)
assertEquals(s, s2)
assertSame(s1, s2)
assertSame(s1, decoder.decodeObject(scope, ObjString.type))
assertSame(s1, decoder.decodeObject(scope, ObjString.type))
assertSame(s1, decoder.decodeObject(scope, ObjString.type))
assertSame(s1, decoder.decodeObject(scope, ObjString.type))
assertSame(s1, decoder.decodeObject(scope, ObjString.type))
assertSame(s1, decoder.decodeObject(scope, ObjString.type)) // 8
assertSame(s1, decoder.unpackObject(scope, ObjString.type))
assertSame(s1, decoder.unpackObject(scope, ObjString.type))
assertSame(s1, decoder.unpackObject(scope, ObjString.type))
assertSame(s1, decoder.unpackObject(scope, ObjString.type))
assertSame(s1, decoder.unpackObject(scope, ObjString.type))
assertSame(s1, decoder.unpackObject(scope, ObjString.type)) // 8
}
@Test
@ -182,12 +182,12 @@ class LynonTests {
val encoder = LynonPacker()
val scope = Scope()
for (s in source) {
encoder.encodeObject(scope, s)
encoder.encodeObj(scope, s)
}
val decoder = LynonUnpacker(encoder)
val restored = mutableListOf<Obj>()
for (i in source.indices) {
restored.add(decoder.decodeObject(scope, ObjString.type))
restored.add(decoder.unpackObject(scope, ObjString.type))
}
assertEquals(restored, source)
}
@ -196,58 +196,58 @@ class LynonTests {
fun testUnpackBoolean() = runTest {
val scope = Scope()
val decoder = LynonUnpacker(LynonPacker().apply {
encodeObject(scope, ObjBool(true))
encodeObject(scope, ObjBool(false))
encodeObject(scope, ObjBool(true))
encodeObject(scope, ObjBool(true))
encodeObj(scope, ObjBool(true))
encodeObj(scope, ObjBool(false))
encodeObj(scope, ObjBool(true))
encodeObj(scope, ObjBool(true))
})
assertEquals(ObjTrue, decoder.decodeObject(scope, ObjBool.type))
assertEquals(ObjFalse, decoder.decodeObject(scope, ObjBool.type))
assertEquals(ObjTrue, decoder.decodeObject(scope, ObjBool.type))
assertEquals(ObjTrue, decoder.decodeObject(scope, ObjBool.type))
assertEquals(ObjTrue, decoder.unpackObject(scope, ObjBool.type))
assertEquals(ObjFalse, decoder.unpackObject(scope, ObjBool.type))
assertEquals(ObjTrue, decoder.unpackObject(scope, ObjBool.type))
assertEquals(ObjTrue, decoder.unpackObject(scope, ObjBool.type))
}
@Test
fun testUnpackReal() = runTest {
val scope = Scope()
val decoder = LynonUnpacker(LynonPacker().apply {
encodeObject(scope, ObjReal(-Math.PI))
encodeObject(scope, ObjReal(Math.PI))
encodeObject(scope, ObjReal(-Math.PI))
encodeObject(scope, ObjReal(Math.PI))
encodeObject(scope, ObjReal(Double.NaN))
encodeObject(scope, ObjReal(Double.NEGATIVE_INFINITY))
encodeObject(scope, ObjReal(Double.POSITIVE_INFINITY))
encodeObject(scope, ObjReal(Double.MIN_VALUE))
encodeObject(scope, ObjReal(Double.MAX_VALUE))
encodeObj(scope, ObjReal(-Math.PI))
encodeObj(scope, ObjReal(Math.PI))
encodeObj(scope, ObjReal(-Math.PI))
encodeObj(scope, ObjReal(Math.PI))
encodeObj(scope, ObjReal(Double.NaN))
encodeObj(scope, ObjReal(Double.NEGATIVE_INFINITY))
encodeObj(scope, ObjReal(Double.POSITIVE_INFINITY))
encodeObj(scope, ObjReal(Double.MIN_VALUE))
encodeObj(scope, ObjReal(Double.MAX_VALUE))
})
assertEquals(ObjReal(-Math.PI), decoder.decodeObject(scope, ObjReal.type))
assertEquals(ObjReal(Math.PI), decoder.decodeObject(scope, ObjReal.type))
assertEquals(ObjReal(-Math.PI), decoder.decodeObject(scope, ObjReal.type))
assertEquals(ObjReal(Math.PI), decoder.decodeObject(scope, ObjReal.type))
assert((decoder.decodeObject(scope, ObjReal.type)).toDouble().isNaN())
assertEquals(ObjReal(Double.NEGATIVE_INFINITY), decoder.decodeObject(scope, ObjReal.type))
assertEquals(ObjReal(Double.POSITIVE_INFINITY), decoder.decodeObject(scope, ObjReal.type))
assertEquals(ObjReal(Double.MIN_VALUE), decoder.decodeObject(scope, ObjReal.type))
assertEquals(ObjReal(Double.MAX_VALUE), decoder.decodeObject(scope, ObjReal.type))
assertEquals(ObjReal(-Math.PI), decoder.unpackObject(scope, ObjReal.type))
assertEquals(ObjReal(Math.PI), decoder.unpackObject(scope, ObjReal.type))
assertEquals(ObjReal(-Math.PI), decoder.unpackObject(scope, ObjReal.type))
assertEquals(ObjReal(Math.PI), decoder.unpackObject(scope, ObjReal.type))
assert((decoder.unpackObject(scope, ObjReal.type)).toDouble().isNaN())
assertEquals(ObjReal(Double.NEGATIVE_INFINITY), decoder.unpackObject(scope, ObjReal.type))
assertEquals(ObjReal(Double.POSITIVE_INFINITY), decoder.unpackObject(scope, ObjReal.type))
assertEquals(ObjReal(Double.MIN_VALUE), decoder.unpackObject(scope, ObjReal.type))
assertEquals(ObjReal(Double.MAX_VALUE), decoder.unpackObject(scope, ObjReal.type))
}
@Test
fun testUnpackInt() = runTest {
val scope = Scope()
val decoder = LynonUnpacker(LynonPacker().apply {
encodeObject(scope, ObjInt(0))
encodeObject(scope, ObjInt(-1))
encodeObject(scope, ObjInt(23))
encodeObject(scope, ObjInt(Long.MIN_VALUE))
encodeObject(scope, ObjInt(Long.MAX_VALUE))
encodeObject(scope, ObjInt(Long.MAX_VALUE))
encodeObj(scope, ObjInt(0))
encodeObj(scope, ObjInt(-1))
encodeObj(scope, ObjInt(23))
encodeObj(scope, ObjInt(Long.MIN_VALUE))
encodeObj(scope, ObjInt(Long.MAX_VALUE))
encodeObj(scope, ObjInt(Long.MAX_VALUE))
})
assertEquals(ObjInt(0), decoder.decodeObject(scope, ObjInt.type))
assertEquals(ObjInt(-1), decoder.decodeObject(scope, ObjInt.type))
assertEquals(ObjInt(23), decoder.decodeObject(scope, ObjInt.type))
assertEquals(ObjInt(Long.MIN_VALUE), decoder.decodeObject(scope, ObjInt.type))
assertEquals(ObjInt(Long.MAX_VALUE), decoder.decodeObject(scope, ObjInt.type))
assertEquals(ObjInt(Long.MAX_VALUE), decoder.decodeObject(scope, ObjInt.type))
assertEquals(ObjInt(0), decoder.unpackObject(scope, ObjInt.type))
assertEquals(ObjInt(-1), decoder.unpackObject(scope, ObjInt.type))
assertEquals(ObjInt(23), decoder.unpackObject(scope, ObjInt.type))
assertEquals(ObjInt(Long.MIN_VALUE), decoder.unpackObject(scope, ObjInt.type))
assertEquals(ObjInt(Long.MAX_VALUE), decoder.unpackObject(scope, ObjInt.type))
assertEquals(ObjInt(Long.MAX_VALUE), decoder.unpackObject(scope, ObjInt.type))
}
@Test
@ -382,39 +382,16 @@ class LynonTests {
println("Original : ${x.size}")
val lzw = LZW.compress(x).bytes
println("LZW : ${lzw.size}")
val ba = Huffman.compress(x, Huffman.byteAlphabet)
val ba = Huffman.compress(x)
val huff = ba.bytes
println("Huffman : ${huff.size}")
val lzwhuff = Huffman.compress(lzw, Huffman.byteAlphabet).bytes
val lzwhuff = Huffman.compress(lzw).bytes
println("LZW+HUFF : ${lzwhuff.size}")
val compressed = Huffman.compress(x,Huffman.byteAlphabet)
val decompressed = Huffman.decompress(compressed.toBitInput(),Huffman.byteAlphabet)
val compressed = Huffman.compress(x)
val decompressed = Huffman.decompress(compressed.toBitInput())
assertContentEquals(x, decompressed)
}
@Test
fun testGenerateCanonicalHuffmanCodes() {
val frequencies = LynonType.entries.map { it.defaultFrequency }.toTypedArray()
val alphabet = object : Huffman.Alphabet<LynonType> {
override val maxOrdinal = LynonType.entries.size
// val bitSize = sizeInBits(maxOrdinal)
override fun decodeOrdinalTo(bout: BitOutput, ordinal: Int) {
TODO("Not yet implemented")
}
override fun get(ordinal: Int): LynonType {
TODO("Not yet implemented")
}
override fun ordinalOf(value: LynonType): Int = value.ordinal
}
for(code in Huffman.generateCanonicalCodes(frequencies, alphabet)) {
println("${code?.bits}: ${code?.ordinal?.let { LynonType.entries[it] }}")
}
}
@Test
fun testBitListSmall() {
var t = TinyBits()