Rebol3 Code Examplex


Huffman coding

Compress data with Huffman prefix codes.

Rebol [
    title: "Rosetta code: Huffman coding"
    file:  %Huffman_coding.r3
    url:   https://rosettacode.org/wiki/Huffman_coding
    note:  "Based on Red language solution"
    needs: 3.15.0 ;; or something like that
]

register-codec [
    name: 'huffman                         ;; Codec identifier name
    type: 'compression                     ;; Type: compression algorithm
    title: "Huffman encoding example"      ;; User-facing title

    decode: function [
        "Huffman decoding"
        data [any-string!]
    ][
        output: copy ""                    ;; Initialize the output string
        while [ not empty? data ][         ;; Loop through encoded data until empty
            foreach [k v] knots [          ;; For each character-knot mapping
                if t: find/match/tail data v/code [   ;; Check if the encoded string starts with the knot's code
                    append output k        ;; If so, append the corresponding character to output
                    data: t                ;; Consume matched part from data
                ]
            ]
        ]
        output                             ;; Return the decoded string
    ]

    encode: func [
        "Huffman encoding"
        data [any-string! binary!]
        /local k nknot output
    ][
        output: copy ""                    ;; Initialize output string
        foreach chr data [                 ;; For each character in input
            either k: select/case knots chr [ ;; If knot already exists for the character
                k/count: k/count + 1       ;; Increment frequency count
            ][  
                ;; Otherwise, create new knot object and add to map
                nknot: make knot [code: chr]
                put/case knots chr nknot
            ]
        ]
        table: values-of knots             ;; Extract all knots
        while [1 < length? table][         ;; Build Huffman tree until only root remains
            sort/compare table :compare-knots ;; Sort knots by ascending frequency
            merge-2knots table             ;; Merge two lowest-count knots into new parent knot
        ]
        set-code table/1 copy ""           ;; Recursively assign binary codes by tree depth
        foreach chr msg [                  ;; Encode the original message
            k: select/case knots chr
            append output k/code           ;; Append binary code for each character
        ]
        output                             ;; Return Huffman encoded string
    ]

    knot: make object! [
        left: right: none                  ;; References to left and right children in the binary tree
        code: none                         ;; Stores char (debug) and binary code for encoding
        count: depth: 1                    ;; Frequency count and branch/tree depth
    ]

    knots: make map! []                    ;; Map to store character -> knot objects
    table: none                            ;; Used for sorting the tree

    compare-knots: function [a b] [
        any [
            a/count < b/count 
            all [
                a/count = b/count
                a/depth > b/depth
            ]
        ]
    ]

    set-code: func  [
        "Recursive function to generate binary code sequence"
        wknot  
        wcode [string!]
    ][
        either wknot/left [
            set-code wknot/left  join wcode "1"     ;; Assign '1' when going left
            set-code wknot/right join wcode "0"     ;; Assign '0' when going right
        ][  wknot/code:  wcode ]                    ;; Assign accumulated code when leaf is reached
    ]
    
    merge-2knots: func [
        "Merge 2 knots into 1 new"
        t [block!]
    ][
        nknot: make knot [
            count: t/1/count + t/2/count            ;; Sum frequencies for merged knot
            right: t/1
            left:  t/2
            depth: t/1/depth + 1                    ;; Increase depth
        ]
        remove/part t 2                             ;; Remove first two knots from table
        insert t nknot                              ;; Insert new knot at head of table
    ]
]

;; message to encode:
message: "this is an example for huffman encoding" ;; Input text for encoding
? message                                          ;; Print message
encoded: encode 'huffman message                   ;; Huffman encode the message
? encoded                                          ;; Print encoded result
decoded: decode 'huffman encoded                   ;; Decode Huffman encoding back to string
? decoded                                          ;; Print decoded result
print "Used codes:"
foreach [k v] codecs/huffman/knots [print [mold k mold v/code]]