;;;; a simple huffman encoder/decoder, used to compress unicode
;;;; character names.
;;;; This software is part of the SBCL system. See the README file for
;;;; more information.
;;;;
;;;; This software is derived from the CMU CL system, which was
;;;; written at Carnegie Mellon University and released into the
;;;; public domain. The software is in the public domain and is
;;;; provided with absolutely no warranty. See the COPYING and CREDITS
;;;; files for more information.
(in-package "SB-IMPL")
(defstruct (huffman-node (:constructor make-huffman-node (key weight))
(:copier nil))
key weight)
(defstruct (huffman-pair
(:include huffman-node)
(:copier nil)
(:constructor make-huffman-pair
(left right &aux
(key (concatenate 'string
(huffman-node-key left)
(huffman-node-key right)))
(weight (+ (huffman-node-weight left)
(huffman-node-weight right))))))
left right)
(declaim (freeze-type huffman-node))
(defun huffman-weights (corpus)
(let ((weight-table (make-hash-table :test #'equal)))
(loop for string in corpus
do (loop for char across string
do (incf (gethash char weight-table 0))))
(let (alist)
(maphash (lambda (char weight)
(push (make-huffman-node (string char) weight) alist))
weight-table)
(sort alist #'string< :key #'huffman-node-key))))
(defun make-huffman-tree (corpus)
(labels ((merge-table (table)
(setf table (stable-sort table #'< :key #'huffman-node-weight))
(push (make-huffman-pair (pop table) (pop table))
table)
(if (second table)
(merge-table table)
(car table)))
(finish-tree (tree)
(if (huffman-pair-p tree)
(list (huffman-node-key tree)
(finish-tree (huffman-pair-left tree))
(finish-tree (huffman-pair-right tree)))
(huffman-node-key tree))))
(finish-tree (merge-table (huffman-weights corpus)))))
(defun huffman-decode (code tree)
(let ((original code))
(labels ((pop-bit ()
(let* ((bits (integer-length code))
(bit (ldb (byte 1 (- bits 2)) code)))
(setf code (dpb 1 (byte 1 (- bits 2))
(ldb (byte (- bits 1) 0) code)))
bit))
(choose (branch)
(destructuring-bind (key left right) branch
(declare (ignore key))
(if (zerop (pop-bit))
left
right)))
(decode (branch)
(when (zerop code)
(error "Invalid Huffman-code: ~S" original))
(let ((next (choose branch)))
(cond ((consp next)
(decode next))
((< 1 code)
(concatenate 'string next (decode tree)))
(t
next)))))
(decode tree))))
(defun huffman-match (char node)
(if (consp node)
(find char (the string (car node)) :test #'equal)
(eql char (character node))))
(defun huffman-encode (string tree)
(let ((code 1))
(labels ((encode (bit char tree)
(when bit
(setf code (+ (ash code 1) bit)))
(if (consp tree)
(destructuring-bind (key left right) tree
(declare (ignore key))
(cond ((huffman-match char left)
(encode 0 char left))
((huffman-match char right)
(encode 1 char right))
(t
;; unknown
(return-from huffman-encode nil))))
(unless (huffman-match char tree)
(error "Error encoding ~S (bad tree)." char)))))
(loop for char across string
do (encode nil char tree))
code)))