const MaxLen
MaxLen bounds input so gas stays predictable.
Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.
Package huffman compresses a string with canonical Huffman codes: the classic frequency-based prefix code, in the one...
gno.land/p/moul/x/daily/huffman/v0Canonical Huffman codes, with a decoder that validates: Table, Encode,
Encoded.Decode, Encoded.BitString, Encoded.Saved.
1import "gno.land/p/moul/x/daily/huffman/v0"
2
3codes, _ := huffman.Table("abracadabra") // a=0, b=100, c=101, d=110, r=111
4enc, _ := huffman.Encode("abracadabra") // 23 bits against 88 raw
5enc.BitString() // "01001110101011001001110"
6enc.Decode() // "abracadabra", or ErrCorrupt
A Huffman tree is not unique. Two equally optimal trees give the same total
bit count and different codes, and which one you get depends on how ties are
broken while merging, which means a naive implementation can hand two callers
different tables for the same input. Canonical coding throws the tree away and
keeps only the code lengths, then re-derives the bits by sorting on
(length, symbol). That is what makes the output deterministic enough for a
Render and small enough to store: the header a decoder needs is one integer
per symbol, not a tree.
The decoder validates, deliberately. Encoded is a plain struct with
exported fields, so a caller can build one by hand or read one back out of
storage, and a decoder that trusts what it is handed returns a string nobody
ever encoded. Decode rebuilds the canonical table from the lengths it was
given and refuses a payload whose own Bits disagree, checks the Kraft
equality so the code set is a complete prefix code, requires Data to be
exactly the bytes NBits needs with zero padding in the last one, refuses a
zero-bit or zero-size payload because Encode never produces one, refuses a
stream that ends mid-code, and refuses a result whose length is not Size.
Spare bytes and dirty padding are not cosmetic: both are a way to carry a
second payload that decodes to the same string.
Three behaviours worth knowing before you use it:
"aaaaa" would encode to an
empty stream that decodes to nothing. It is the only incomplete code this
package emits (Kraft sum 1/2), and Decode allows exactly that one case.MaxLen is 4096 bytes. That bound is also what keeps a code under 18 bits: the
worst case for Huffman is a Fibonacci frequency distribution, where reaching a
32-bit code needs megabytes of input. ErrTooDeep exists for the overflow
anyway, returned rather than panicked, because a realm's Render can never be
fixed once its path is published.
Live demo: r/moul/x/daily/huffmandemo.
Built for gno 0.9.
Part of moul/gno-contracts — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage.
🧪 Highly experimental — potentially vibe-coded. Not audited; may break, change, or be removed at any time. Do not use with anything of value. Full disclaimer: DISCLAIMER.
Package huffman compresses a string with canonical Huffman codes: the classic frequency-based prefix code, in the one form that is safe to put on a chain.
Two properties matter here and neither is about compression.
The codes are CANONICAL. A plain Huffman tree is not unique: two equally optimal trees give the same total bit count and different codes, and which one you get depends on how ties are broken while merging. Canonical codes throw the tree away and keep only the code LENGTHS, then re-derive the bits by sorting on (length, symbol). The same input therefore always produces the same table, which is what a deterministic Render and a reproducible payload both need, and the table a decoder has to carry shrinks to one length per symbol.
Decoding VALIDATES. Encoded is a plain struct with exported fields, so a caller can build one by hand or read one out of storage, and a decoder that trusts what it is handed will happily return a string nobody ever encoded. Encoded.Decode rebuilds the canonical table from the lengths it was given, refuses a payload whose own Bits disagree with it, and checks the Kraft equality, the bit budget and the decoded size before returning.
A live demo of this package is at r/moul/x/daily/huffmandemo(/r/moul/x/daily/huffmandemo/v0).
1var (
2 // ErrEmpty is returned for an input with no bytes in it: there is no such
3 // thing as a code for an empty alphabet.
4 ErrEmpty = errors.New("huffman: empty input")
5 // ErrTooLong is returned when input exceeds MaxLen.
6 ErrTooLong = errors.New("huffman: input too long")
7 // ErrTooDeep is returned when the optimal code would be longer than 32
8 // bits. Unreachable at MaxLen, and returned rather than panicked because a
9 // realm's Render can never be fixed once the path is published.
10 ErrTooDeep = errors.New("huffman: code longer than 32 bits")
11 // ErrCorrupt is returned by Decode when the payload is not one this
12 // package could have produced.
13 ErrCorrupt = errors.New("huffman: corrupt payload")
14)Code is one symbol's canonical prefix code.
Encoded is a compressed payload: the canonical table, which is the whole header a decoder needs, and the packed bit stream.
BitString renders the stream as '0' and '1', padding excluded. It is meant for demos and error messages: the result is NBits characters long.
Decode validates the payload and returns the original string.
Everything it checks is something a hand-built or storage-read Encoded can get wrong: an empty or over-long table, a zero length, a table out of canonical order, a repeated symbol, Bits that disagree with the lengths, a code set that is not a complete prefix code, a zero-bit or zero-size payload, a Data slice that is not exactly the bytes NBits needs, nonzero padding in the last byte, a stream ending mid-code, and a decoded length that is not Size.
Saved reports how many bits the encoding removed, which is negative when the alphabet is flat enough that the codes cost more than the bytes did.