ハフマン符号化——LispとPython実現
16655 ワード
掘金のオリジナル機能をサポートします~
原文住所:ハフマン符号化——LispとPython実現
SICP第2章では,データの抽象化を主唱し,Lisp言語のデータ構造として簡単に理解できる.もちろん、このような理解は、Lispにデータとプロセスの違いが存在しないため、このようなデータ抽象的なプロセスを記述するための追加の概念を全く必要としないため、遷移としてのみ使用することができる.2.3.4ハフマン符号化を例として、どのようにLispの中でハフマンツリーデータ構造の表現と操作を実現するかを示し、本文はこの小節の練習問題(完全なハフマン符号化ツリーの生成、復号と符号化)を完成した上で、Lisp(ここではDrRacketの
1.ハフマンの木
ハフマン符号化は重み付け二叉木に基づき、Pythonでハフマン木を表す.
Lispはリスト(List)で表される:
2.ハフマンアルゴリズム
最適二叉木を生成するために,ハフマン符号化は文字出現頻度を重みとし,現在重みが最も小さい2つのノードを二叉木を生成する左右の分岐として毎回選択し,重みの和をルートノードの重みとし,この貪欲なアルゴリズムに従って,重み付き経路長が最も短い二叉木を底から上へ生成する.
このアルゴリズムによれば、「文字-周波数」の統計情報からハフマンツリーを構築することができ、Python実装では、すべてのノードを重みで並べ替えるたびに、
Lispは通常、再帰的プロセスを用いてループ動作を完了し、挿入ソートのような秩序化された集合を以下のように実現する.
次のPythonリストの推定機能に似たプロセスは、Pythonの簡潔さと美しさをより感じることができるかもしれません.
最後に、「文字-周波数」の秩序化されたセットに基づいてハフマン符号化ツリーを生成します.
3.符号化と復号化
ハフマンツリーがあれば、符号化と復号が可能です.符号化プロセスは、文字が存在するリーフノードと、ルートノードからリーフノードへの経路を見つける必要があり、左サブツリー検索で
Lispのツリー内の各レイヤーのルートノードは、すべてのサブツリーに表示される文字をキャッシュし、時間を空間的にスワップします.
復号プロセスは、
上記のアルゴリズムは、重み値が等しく、左右のサブツリー割り当ての問題が発生する可能性があるため、生成されるたびにハフマンツリーが一意であることを保証することはできないが、同じツリーに対して符号化および復号化の結果は相互に通じる.
4.まとめ
開発言語としてLispが使われるとは限らないが、優れた思想を学び、吸収することを妨げるものではない.SICPの前の2章はそれぞれプロセスに対する抽象とデータに対する抽象を紹介し、その中の1つの重要な思想は符号化の本質が計算プロセスに対する記述であり、このような記述はある特定の文法やデータ構造にこだわらない.プロセスに対する抽象(例えば
Pythonはプロフィール、優雅さを確保すると同時に、驚くべき柔軟性を持っており、
以上のようにすることは意味がないが,プロセスの記述を関数レベルに抽象化し,関数を操作する思想はPythonにおいても同様に重要である.
上のコードの大部分は旅先の列車や自動車で完成しているので、オフラインプログラミングの「楽しみ」を体験する機会は少ないですが、
原文住所:ハフマン符号化——LispとPython実現
SICP第2章では,データの抽象化を主唱し,Lisp言語のデータ構造として簡単に理解できる.もちろん、このような理解は、Lispにデータとプロセスの違いが存在しないため、このようなデータ抽象的なプロセスを記述するための追加の概念を全く必要としないため、遷移としてのみ使用することができる.2.3.4ハフマン符号化を例として、どのようにLispの中でハフマンツリーデータ構造の表現と操作を実現するかを示し、本文はこの小節の練習問題(完全なハフマン符号化ツリーの生成、復号と符号化)を完成した上で、Lisp(ここではDrRacketの
#lang sicp
モード、すなわちScheme
に近似する)とPythonバージョンのプログラムを比較する.1.ハフマンの木
ハフマン符号化は重み付け二叉木に基づき、Pythonでハフマン木を表す.
class Node(object):
def __init__(self, symbol='', weight=0):
self.left = None
self.right = None
self.symbol = symbol #
self.weight = weight #
Lispはリスト(List)で表される:
(define (make-leaf symbol weight) (list 'leaf symbol weight))
(define (make-tree left right)
(list left
right
(append (symbols left) (symbols right)) ;
(+ (weight left) (weight right)))
)
;; methods
(define (leaf? object) (eq? (car object) 'leaf))
(define (symbol-leaf leaf) (cadr leaf))
(define (weight-leaf leaf) (caddr leaf))
(define (symbols tree)
(if (leaf? tree)
(list (symbol-leaf tree))
(caddr tree)))
(define (weight tree)
(if (leaf? tree)
(weight-leaf tree)
(cadddr tree)))
2.ハフマンアルゴリズム
最適二叉木を生成するために,ハフマン符号化は文字出現頻度を重みとし,現在重みが最も小さい2つのノードを二叉木を生成する左右の分岐として毎回選択し,重みの和をルートノードの重みとし,この貪欲なアルゴリズムに従って,重み付き経路長が最も短い二叉木を底から上へ生成する.
このアルゴリズムによれば、「文字-周波数」の統計情報からハフマンツリーを構築することができ、Python実装では、すべてのノードを重みで並べ替えるたびに、
# frequency = {'a': 1, 'b': 3, 'c': 2}
def huffman_tree(frequency):
SIZE = len(frequency)
nodes = [Node(char, frequency.get(char)) for char in frequency.keys()]
for _ in range(SIZE - 1):
nodes.sort(key=lambda n: n.weight) #
left = nodes.pop(0)
right = nodes.pop(0)
parent = Node('', left.weight + right.weight)
parent.left = left
parent.right = right
nodes.append(parent)
return nodes.pop()
Lispは通常、再帰的プロセスを用いてループ動作を完了し、挿入ソートのような秩序化された集合を以下のように実現する.
(define (adjoin-set x set)
(cond ((null? set) (list x))
((< (weight x) (weight (car set))) (cons x set))
(else (cons (car set) (adjoin-set x (cdr set))))))
次のPythonリストの推定機能に似たプロセスは、Pythonの簡潔さと美しさをより感じることができるかもしれません.
(define (make-leaf-set pairs)
(if (null? pairs)
'()
(let ((pair (car pairs)))
(adjoin-set (make-leaf (car pair)
(cadr pair))
(make-leaf-set (cdr pairs))))))
最後に、「文字-周波数」の秩序化されたセットに基づいてハフマン符号化ツリーを生成します.
(define (generate-huffman-tree pairs)
(define (successive-merge leaf-set)
(cond ((null? leaf-set) '())
((null? (cdr leaf-set)) (car leaf-set))
(else (successive-merge (adjoin-set
(make-code-tree (car leaf-set)
(cadr leaf-set))
(cddr leaf-set))))
))
(successive-merge (make-leaf-set pairs)))
3.符号化と復号化
ハフマンツリーがあれば、符号化と復号が可能です.符号化プロセスは、文字が存在するリーフノードと、ルートノードからリーフノードへの経路を見つける必要があり、左サブツリー検索で
"0"
、右サブツリーで"1"
と表記されるたびに、二叉木のシーケンスを用いて、リーフノードと経路を再帰的に見つけることができる.def encode(symbol, tree):
bits = None
def preOrder(tree, path):
if tree.left:
preOrder(tree.left, path + "0")
if tree.right:
preOrder(tree.right, path + "1")
if tree.isLeaf() and tree.symbol == symbol:
nonlocal bits
bits = path
preOrder(tree, "")
return bits
Lispのツリー内の各レイヤーのルートノードは、すべてのサブツリーに表示される文字をキャッシュし、時間を空間的にスワップします.
(define (left-branch tree) (car tree))
(define (right-branch tree) (cadr tree))
(define (encode-symbol char tree)
(cond ((null? char) '())
((leaf? tree) '())
((memq char (symbols (left-branch tree)))
(cons 0
(encode-symbol char (left-branch tree))))
((memq char (symbols (right-branch tree)))
(cons 1
(encode-symbol char (right-branch tree))))
(else (display "Error encoding char "))))
復号プロセスは、
"0"
に遭遇して左のサブツリーを取り、"1"
に遭遇して右のサブツリーを取るルールに従えば、より直接的である.def decode(bits, tree):
result = ""
root = tree
for bit in bits:
if bit == "0":
node = root.left
elif bit == "1":
node = root.right
else:
return "Code error: {}".format(bit)
if node.isLeaf():
result += node.symbol
root = tree
else:
root = node
return result
(define (choose-branch bit branch)
(cond ((= bit 0) (left-branch branch))
((= bit 1) (right-branch branch))
(else (display "bat bit: CHOOSE-BRANCH" bit))))
(define (decode bits tree)
(define (decode-1 bits current-branch)
(if (null? bits)
'()
(let ((next-branch
(choose-branch (car bits) current-branch)))
(if (leaf? next-branch)
(cons (symbol-leaf next-branch)
(decode-1 (cdr bits) tree))
(decode-1 (cdr bits) next-branch)))))
(decode-1 bits tree))
上記のアルゴリズムは、重み値が等しく、左右のサブツリー割り当ての問題が発生する可能性があるため、生成されるたびにハフマンツリーが一意であることを保証することはできないが、同じツリーに対して符号化および復号化の結果は相互に通じる.
4.まとめ
開発言語としてLispが使われるとは限らないが、優れた思想を学び、吸収することを妨げるものではない.SICPの前の2章はそれぞれプロセスに対する抽象とデータに対する抽象を紹介し、その中の1つの重要な思想は符号化の本質が計算プロセスに対する記述であり、このような記述はある特定の文法やデータ構造にこだわらない.プロセスに対する抽象(例えば
(define (add a b) (+ a b))
)とデータに対する抽象(例えば(define (make-leaf symbol weight) (list 'leaf symbol weight))
)との間に本質的な相違はない.Pythonはプロフィール、優雅さを確保すると同時に、驚くべき柔軟性を持っており、
Scheme
の文法を真似て上記のすべてのプログラムを完成することができます.def make-leaf(symbol, weight):
return ['leaf', symbol, weight]
def leaf?(obj):
return obj[0] == 'leaf'
以上のようにすることは意味がないが,プロセスの記述を関数レベルに抽象化し,関数を操作する思想はPythonにおいても同様に重要である.
上のコードの大部分は旅先の列車や自動車で完成しているので、オフラインプログラミングの「楽しみ」を体験する機会は少ないですが、
sort
とnonlocal
の使い方はPyTipsを書いたときのまとめのおかげなので、0xFF
をいっぱい書く時間があることを望んでいます.