1987 年に T. C. Bell が開発した LZB 符号は、一致長の符号化にγ符号を、位置の符号化に可変長符号 (CBT 符号) を用いて LZSS 符号の圧縮率を改善する方法です。詳しい説明は拙作のページ Algorithms with Python: LZB 符号と LZH 符号 をお読みください。
# coding: utf-8
#
# lzb.rb : LZB 符号
#
# Copyright (C) 2006-2023 Makoto Hiroi
#
require "optparse"
require "./bitio"
# 定数
MIN_LEN = 3
MAX_LEN = 128
POS_BITS = 15
# スライド窓
class Slide
attr_reader :match_len, :match_pos, :buff, :data_size, :limit
def initialize(file)
@file = file
@size = 1 << POS_BITS
@limit = @size * 2
@next = Array.new(@size, nil)
@hash = {}
@buff = Array.new(@limit + MAX_LEN, 0)
@data_size = read_data(0, @limit + MAX_LEN)
@match_len = 0
@match_pos = 0
end
private
def read_data(start, size)
n = 0
while n < size
c = @file.getbyte
break unless c
@buff[start + n] = c
n += 1
end
n
end
def move_data(to, from, size)
n = 0
while n < size
@buff[to + n] = @buff[from + n]
n += 1
end
end
def hash_value(rp)
(@buff[rp] << 16) + (@buff[rp + 1] << 8) + @buff[rp + 2]
end
public
def update(rp)
return rp if @data_size < @limit + MAX_LEN
# buffer update
move_data(0, @size, @size + MAX_LEN)
n = read_data(@size + MAX_LEN, @size)
@data_size = @size + MAX_LEN + n
# hash update
@hash.each do |k, v|
if v < @size
@hash.delete(k)
else
@hash[k] = v - @size
end
end
@next.map! do |v|
if v == nil || v < @size
nil
else
v - @size
end
end
rp - @size
end
def insert(rp)
value = hash_value(rp)
@next[rp & (@size - 1)] = @hash[value]
@hash[value] = rp
end
# 単純なハッシュ法
# ハッシュ値の衝突が多いと極端に遅くなる
def search(rp)
n = @hash[hash_value(rp)]
limit = rp - @size
@match_len = 0
@match_pos = 0
while n
break if n < limit
if @buff[rp + @match_len] == @buff[n + @match_len]
x = 0
while x < MAX_LEN
break if @buff[rp + x] != @buff[n + x]
x += 1
end
if @match_len < x
@match_len = x
@match_pos = n
break if x == MAX_LEN
end
end
n = @next[n & (@size - 1)]
end
# データの終端をチェック
@match_len = @data_size - rp if @match_len >= @data_size - rp
end
end
# 位置の符号化には CBT 符号を使う
$pos_bits = 0
def update_pos_bits(rp)
if $pos_bits < POS_BITS
$pos_bits += 1 while rp > (1 << $pos_bits) - 1
end
end
def pos_encode(fout, rp, pos)
if 1 < $pos_bits && $pos_bits < POS_BITS
fout.cbt_encode(pos, rp, $pos_bits)
else
fout.putbits($pos_bits, pos)
end
end
def pos_decode(fin, rp)
if 1 < $pos_bits && $pos_bits < POS_BITS
fin.cbt_decode(rp, $pos_bits)
else
fin.getbits($pos_bits)
end
end
# 符号化
def encode(fin, fout)
s = Slide.new(fin)
rp = 0
begin
s.search(rp)
if s.match_len < MIN_LEN
len = 1
fout.putbit(0)
fout.putbits(8, s.buff[rp])
else
len = s.match_len
fout.putbit(1)
fout.gamma_encode(len - MIN_LEN)
pos_encode(fout, rp, rp - s.match_pos - 1)
end
len.times do
s.insert(rp)
rp += 1
rp = s.update(rp) if rp >= s.limit
end
update_pos_bits(rp)
end while rp < s.data_size
end
def encode_file(name1, name2)
fin = open(name1, "rb")
fout = BitIO.new(name2, WOPEN)
size = File.size(name1)
fout.putbits(32, size)
encode(fin, fout) if size > 0
fin.close
fout.close
end
# 復号
def decode(fin, fout, size)
rp = 0
buff = Array.new(1 << POS_BITS)
while size > 0
if fin.getbit == 1
len = fin.gamma_decode + MIN_LEN
pos = rp - (pos_decode(fin, rp) + 1)
pos += buff.size if pos < 0
len.times do
c = buff[pos]
fout.putc(c)
buff[rp] = c
pos += 1
rp += 1
pos = 0 if pos >= buff.size
rp = 0 if rp >= buff.size
end
else
len = 1
c = fin.getbits(8)
fout.putc(c)
buff[rp] = c
rp += 1
rp = 0 if rp >= buff.size
end
size -= len
update_pos_bits(rp)
end
end
def decode_file(name1, name2)
fin = BitIO.new(name1, ROPEN)
fout = open(name2, "wb")
size = fin.getbits(32)
decode(fin, fout, size) if size > 0
fin.close
fout.close
end
def main
eflag = false
dflag = false
opts = OptionParser.new
opts.on("-e"){|v| eflag = true}
opts.on("-d"){|v| dflag = true}
opts.parse!(ARGV)
if eflag && !dflag
encode_file(ARGV[0], ARGV[1])
elsif dflag && !eflag
decode_file(ARGV[0], ARGV[1])
else
print "option error"
end
end
# 実行
s = Time.now
main
e = Time.now
printf "%.3f\n", e - s
テストデータは Canterbury Corpus で配布されている The Canterbury Corpus です。
スライド窓の大きさ 8192, MAX_LEN = 18 の場合 ファイル名 サイズ LZSS LZB ------------------------------------------- alice29.txt 152,089 68,332 66,120 asyoulik.txt 125,179 61,789 58,833 cp.html 24,603 10,278 10,126 fields.c 11,150 3,859 3,824 grammar.lsp 3,721 1,594 1,471 kennedy.xls 1,029,744 291,968 317,643 lcet10.txt 426,754 184.684 179,973 plrabn12.txt 481,861 247,780 234,576 ptt5 513,216 107,289 120,286 sum 38,240 17,500 17,137 xargs.1 4,227 2,198 2,007 ------------------------------------------- 合計 2,810,784 997,271 1,011,006
スライド窓の大きさ 32 k, MAX_LEN = 128 の場合 ファイル名 サイズ LZB 符号化 復号 ------------------------------------------------ alice29.txt 152,089 60,437 0.345 0.153 asyoulik.txt 125,179 54,736 0.285 0.133 cp.html 24,603 8,973 0.038 0.026 fields.c 11,150 3,513 0.019 0.013 grammar.lsp 3,721 1,424 0.009 0.007 kennedy.xls 1,029,744 328,111 1.882 0.864 lcet10.txt 426,754 162,247 0.999 0.398 plrabn12.txt 481,861 218,902 1.292 0.515 ptt5 513,216 69,737 10.139 0.281 sum 38,240 15,078 0.119 0.041 xargs.1 4,227 1,985 0.010 0.008 ------------------------------------------------ 合計 2,810,784 925,143 15.137 2.439
符号化と復号の単位 : 秒 実行環境 : Ruby 3.0, Ubunts 22.04 LTS (WSL2, Windows 10), Intel Core i5-6200U 2.30GHz
CBT 符号を用いて LZW 符号の圧縮率を改善します。詳しい説明は拙作のページ Algorithms with Python: LZ78 符号 (LZW 符号) をお読みください。
# coding: utf-8
#
# lzw1.rb : LZW 符号 + CBT 符号
#
# Copyright (C) 2006-2023 Makoto Hiroi
#
require "optparse"
require "./bitio"
# 辞書の大きさ
DIC_SIZE = 15
# 節
class Node
attr_accessor :code, :parent
def initialize(code, parent)
@code = code
@parent = parent
end
end
# トライはハッシュ法で実装
class Dic
attr_reader :len
def initialize(size)
@max_bits = size
@bits = 9
@count = 256
@buff = []
@hash = {}
@len = 0
256.times do |c|
@buff << Node.new(c, nil)
end
end
# CBT 符号で符号化
def encode(file, c)
if @bits < @max_bits
file.cbt_encode(c, @count, @bits)
@count += 1
@bits += 1 if @count > (1 << @bits) - 1
else
file.putbits(@max_bits, c)
end
end
# CBT 符号の復号
def decode(file)
if @bits < @max_bits
n = file.cbt_decode(@count, @bits)
@count += 1
@bits += 1 if @count > (1 << @bits) - 1
n
else
file.getbits(@max_bits)
end
end
# 辞書番号のチェック
def check_code(code)
code < @buff.size
end
# 子を探す
def search_child(n, c)
@hash[(n << 8) + c]
end
# 子を挿入する
def insert_child(n, c)
if @buff.size < (1 << @max_bits)
child = @buff.size
@buff << Node.new(c, n)
@hash[(n << 8) + c] = child
end
end
# 出力
def output(file, n)
if @buff[n].parent == nil
file.putc(n)
@len = 1
return n
else
m = output(file, @buff[n].parent)
file.putc(@buff[n].code)
@len += 1
return m
end
end
end
# 符号化
def encode(fin, fout)
dic = Dic.new(DIC_SIZE)
p = fin.getbyte
while c = fin.getbyte
q = dic.search_child(p, c)
if q
p = q
else
dic.encode(fout, p)
dic.insert_child(p, c)
p = c
end
end
dic.encode(fout, p)
end
def encode_file(name1, name2)
fin = open(name1, "rb")
fout = BitIO.new(name2, WOPEN)
size = File.size(name1)
fout.putbits(32, size)
encode(fin, fout) if size > 0
fin.close
fout.close
end
# 復号
def decode(fin, fout, size)
dic = Dic.new(DIC_SIZE)
p = dic.decode(fin)
c = dic.output(fout, p)
size -= dic.len
while size > 0
q = dic.decode(fin)
if dic.check_code(q)
c = dic.output(fout, q)
dic.insert_child(p, c)
else
dic.insert_child(p, c)
c = dic.output(fout, q)
end
p = q
size -= dic.len
end
end
def decode_file(name1, name2)
fin = BitIO.new(name1, ROPEN)
fout = open(name2, "wb")
size = fin.getbits(32)
decode(fin, fout, size) if size > 0
fin.close
fout.close
end
def main
eflag = false
dflag = false
opts = OptionParser.new
opts.on("-e"){|v| eflag = true}
opts.on("-d"){|v| dflag = true}
opts.parse!(ARGV)
if eflag && !dflag
encode_file(ARGV[0], ARGV[1])
elsif dflag && !eflag
decode_file(ARGV[0], ARGV[1])
else
print "option error"
end
end
# 実行
s = Time.now
main
e = Time.now
printf "%.3f\n", e - s
テストデータは Canterbury Corpus で配布されている The Canterbury Corpus です。
辞書の大きさ = 8192 ファイル名 サイズ LZW LZW1 ------------------------------------------ alice29.txt 152,089 68,448 67,415 asyoulik.txt 125,179 59,085 58,056 cp.html 24,603 12,150 11,111 fields.c 11,150 5,760 4,810 grammar.lsp 3,721 2,294 1,747 kennedy.xls 1,029,744 339,542 338,558 lcet10.txt 426,754 194,996 193,968 plrabn12.txt 481,861 220,850 219,812 ptt5 513,216 66,101 65,084 sum 38,240 30,163 29,123 xargs.1 4,227 2,916 2,249 ------------------------------------------ 合計 2,810,784 1,002,305 991,993
辞書の大きさ = 32 k ファイル名 サイズ LZW1 符号化 復号 ------------------------------------------------ alice29.txt 152,089 61,123 0.151 0.220 asyoulik.txt 125,179 54,117 0.132 0.194 cp.html 24,603 10,877 0.028 0.042 fields.c 11,150 4,810 0.014 0.020 grammar.lsp 3,721 1,747 0.007 0.009 kennedy.xls 1,029,744 338,206 0.686 1.114 lcet10.txt 426,754 168,556 0.357 0.545 plrabn12.txt 481,861 200,493 0.406 0.621 ptt5 513,216 61,101 0.205 0.432 sum 38,240 19,473 0.050 0.065 xargs.1 4,227 2,249 0.010 0.010 ------------------------------------------------ 合計 2,810,784 922,752 2.046 3.272
符号化と復号の単位 : 秒 実行環境 : Ruby 3.0, Ubunts 22.04 LTS (WSL2, Windows 10), Intel Core i5-6200U 2.30GHz
LZT 符号は 1987 年に Tischer によって開発されました。LZT 符号のおもな改良点は、辞書が満杯になった場合、長い間使われていない (最長時間未使用 : Least Recently Used) 語を取り除くことで辞書の空きスペースを作るところです。このような操作を「LRU スキーム」と呼びます。LZT 符号は LRU スキームを行うため符号化と復号に時間がかかることになりますが、少ないメモリでも高い圧縮率を期待することができます。詳しい説明は拙作のページ Algorithms with Python: LZT 符号 をお読みください。
# coding: utf-8
#
# lzt.rb : LZT 符号 + CBT 符号
#
# Copyright (C) 2006-2023 Makoto Hiroi
#
require "optparse"
require "./bitio"
# 辞書の大きさ
DIC_SIZE = 15
# 節
class Node
attr_accessor :code, :parent, :child
def initialize(code, parent)
@code = code
@parent = parent
# 子の個数
@child = 0
end
end
# トライはハッシュ法で実装
class Dic
HEAD = 0
attr_reader :len
def initialize(size)
@max_bits = size
@bits = 9
@count = 256
@buff = []
@hash = {}
@len = 0
256.times do |c|
@buff << Node.new(c, nil)
end
# 双方向リスト
@prev = Array.new(1 << @max_bits)
@link = Array.new(1 << @max_bits)
@prev[HEAD] = HEAD
@link[HEAD] = HEAD
end
# 双方向リストの操作
def add_list(n)
if n >= 256
last = @prev[HEAD]
@prev[n] = last
@link[n] = HEAD
@link[last] = n
@prev[HEAD] = n
end
end
def delete_list(n)
if n >= 256
p = @prev[n]
q = @link[n]
@prev[q] = p
@link[p] = q
end
end
# 葉を探す
def search_leaf
i = @link[HEAD]
while i != HEAD
break if @buff[i].child == 0
i = @link[i]
end
i
end
# 子を探す
def search_child(n, c)
@hash[(n << 8) + c]
end
# 子を削除する
def delete_child(n)
p = @buff[n].parent
@hash.delete((p << 8) + @buff[n].code)
@buff[p].child -= 1
end
# 子を挿入する
def insert_child(n, c)
if @buff.size < (1 << @max_bits)
child = @buff.size
@buff << Node.new(c, n)
else
child = search_leaf
delete_list(child)
delete_child(child)
@buff[child].code = c
@buff[child].parent = n
end
@buff[n].child += 1
@hash[(n << 8) + c] = child
add_list(child)
end
# CBT 符号
def encode(file, c)
if @bits < @max_bits
file.cbt_encode(c, @count, @bits)
@count += 1
@bits += 1 if @count > (1 << @bits) - 1
else
file.putbits(@max_bits, c)
end
end
def decode(file)
if @bits < @max_bits
n = file.cbt_decode(@count, @bits)
@count += 1
@bits += 1 if @count > (1 << @bits) - 1
n
else
file.getbits(@max_bits)
end
end
# 辞書番号のチェック
def check_code(code)
if @buff.size == (1 << @max_bits)
return true if search_leaf != code
elsif code < @buff.size
return true
end
false
end
# 出力
def output(file, n)
if @buff[n].parent == nil
file.putc(n)
delete_list(n)
add_list(n)
@len = 1
return n
else
m = output(file, @buff[n].parent)
file.putc(@buff[n].code)
delete_list(n)
add_list(n)
@len += 1
return m
end
end
end
# 符号化
def encode(fin, fout)
dic = Dic.new(DIC_SIZE)
p = fin.getbyte
while c = fin.getbyte
q = dic.search_child(p, c)
if q
p = q
dic.delete_list(q)
dic.add_list(q)
else
dic.encode(fout, p)
dic.insert_child(p, c)
p = c
end
end
dic.encode(fout, p)
end
def encode_file(name1, name2)
fin = open(name1, "rb")
fout = BitIO.new(name2, WOPEN)
size = File.size(name1)
fout.putbits(32, size)
encode(fin, fout) if size > 0
fin.close
fout.close
end
# 復号
def decode(fin, fout, size)
dic = Dic.new(DIC_SIZE)
p = dic.decode(fin)
c = dic.output(fout, p)
size -= dic.len
while size > 0
q = dic.decode(fin)
if dic.check_code(q)
c = dic.output(fout, q)
dic.insert_child(p, c)
else
dic.insert_child(p, c)
c = dic.output(fout, q)
end
p = q
size -= dic.len
end
end
def decode_file(name1, name2)
fin = BitIO.new(name1, ROPEN)
fout = open(name2, "wb")
size = fin.getbits(32)
decode(fin, fout, size) if size > 0
fin.close
fout.close
end
def main
eflag = false
dflag = false
opts = OptionParser.new
opts.on("-e"){|v| eflag = true}
opts.on("-d"){|v| dflag = true}
opts.parse!(ARGV)
if eflag && !dflag
encode_file(ARGV[0], ARGV[1])
elsif dflag && !eflag
decode_file(ARGV[0], ARGV[1])
else
print "option error"
end
end
# 実行
s = Time.now
main
e = Time.now
printf "%.3f\n", e - s
テストデータは Canterbury Corpus で配布されている The Canterbury Corpus です。
辞書の大きさ = 8192 ファイル名 サイズ LZW LZW1 LZT --------------------------------------------------- alice29.txt 152,089 68,448 67,415 64,193 asyoulik.txt 125,179 59,085 58,056 56,937 cp.html 24,603 12,150 11,111 11,111 fields.c 11,150 5,760 4,810 4,810 grammar.lsp 3,721 2,294 1,747 1,747 kennedy.xls 1,029,744 339,542 338,558 274,242 lcet10.txt 426,754 194,996 193,968 180,431 plrabn12.txt 481,861 220,850 219,812 215,225 ptt5 513,216 66,101 65,084 61,407 sum 38,240 30,163 29,123 19,364 xargs.1 4,227 2,916 2,249 2,249 --------------------------------------------------- 合計 2,810,784 1,002,305 991,993 891,716
辞書の大きさ = 32 k ファイル名 サイズ LZT 符号化 復号 ------------------------------------------------ alice29.txt 152,089 61,097 0.200 0.280 asyoulik.txt 125,179 54,117 0.170 0.235 cp.html 24,603 10,877 0.038 0.061 fields.c 11,150 4,810 0.018 0.024 grammar.lsp 3,721 1,747 0.009 0.012 kennedy.xls 1,029,744 303,277 1.069 1.630 lcet10.txt 426,754 163,278 0.555 0.776 plrabn12.txt 481,861 198,821 0.656 0.908 ptt5 513,216 61,064 0.359 0.586 sum 38,240 19,473 0.061 0.080 xargs.1 4,227 2,249 0.009 0.012 ------------------------------------------------ 合計 2,810,784 880,810 3.144 4.604
符号化と復号の単位 : 秒 実行環境 : Ruby 3.0, Ubunts 22.04 LTS (WSL2, Windows 10), Intel Core i5-6200U 2.30GHz
スプレイ符号は「動的符号化」の一種で、符号木に対して出現した記号の符号語長が半分になるようにスプレイ操作を適用することにより、頻繁に現れる記号に短い符号語を割り当てることができます。詳しい説明は拙作のページ Algorithms with Python: バイナリレンジコーダ [3], スプレー符号 をお読みください。
# coding: utf-8
#
# splaycode.rb : スプレイ符号
#
# Copyright (C) 2006-2023 Makoto Hiroi
#
require "optparse"
require "./bitio"
# ファイルの終了
EOF = 256
class SplayCode
def initialize(n)
@num = n - 1
@parent = Array.new(2 * n - 1)
@left = Array.new(n - 1)
@right = Array.new(n - 1)
for i in 0 ... n - 1
@parent[i] = (i - 1) / 2
@left[i] = 2 * i + 1
@right[i] = 2 * i + 2
end
for i in n - 1 ... 2 * n - 1
@parent[i] = (i - 1) / 2
end
end
private
# splay 操作で符号長を半分にする
def splay(code)
n = @num + code
begin
p = @parent[n]
break if p == 0
g = @parent[p]
if @left[g] == p
gc = @right[g]
@right[g] = n
else
gc = @left[g]
@left[g] = n
end
@parent[n] = g
if @left[p] == n
@left[p] = gc
else
@right[p] = gc
end
@parent[gc] = p
n = g
end while n > 0
end
# 符号化
def encode_sub(fout, n, p)
encode_sub(fout, p, @parent[p]) if p > 0
if @right[p] == n
fout.putbit(1)
else
fout.putbit(0)
end
end
public
# 符号化
def encode(fout, code)
n = @num + code
encode_sub(fout, n, @parent[n])
splay(code)
end
# 復号
def decode(fin)
n = 0
while n < @num
if fin.getbit == 1
n = @right[n]
else
n = @left[n]
end
end
code = n - @num
splay(code)
code
end
end
# 符号化
def encode(name1, name2)
fin = open(name1, "rb")
fout = BitIO.new(name2, WOPEN)
splay = SplayCode.new(257)
fin.each_byte do |c|
splay.encode(fout, c)
end
splay.encode(fout, EOF)
fin.close
fout.close
end
# 復号
def decode(name1, name2)
fin = BitIO.new(name1, ROPEN)
fout = open(name2, "wb")
splay = SplayCode.new(257)
while (c = splay.decode(fin)) != EOF
fout.putc(c)
end
fin.close
fout.close
end
def main
eflag = false
dflag = false
opts = OptionParser.new
opts.on("-e"){|v| eflag = true}
opts.on("-d"){|v| dflag = true}
opts.parse!(ARGV)
if eflag && !dflag
encode(ARGV[0], ARGV[1])
elsif dflag && !eflag
decode(ARGV[0], ARGV[1])
else
print "option error"
end
end
# 実行
s = Time.now
main
e = Time.now
printf "%.3f\n", e - s
テストデータは Canterbury Corpus で配布されている The Canterbury Corpus です。
ファイル名 サイズ Splay 符号化 復号 ------------------------------------------------ alice29.txt 152,089 104,800 0.260 0.283 asyoulik.txt 125,179 90,108 0.252 0.239 cp.html 24,603 18,931 0.051 0.055 fields.c 11,150 7,875 0.024 0.025 grammar.lsp 3,721 2,497 0.010 0.010 kennedy.xls 1,029,744 501,392 1.303 1.407 lcet10.txt 426,754 289,971 0.746 0.768 plrabn12.txt 481,861 336,093 0.854 0.893 ptt5 513,216 109,677 0.285 0.381 sum 38,240 23,580 0.076 0.071 xargs.1 4,227 3,047 0.011 0.011 ------------------------------------------------ 合計 2,810,784 1,487,971 3.872 4.143
符号化と復号の単位 : 秒 実行環境 : Ruby 3.0, Ubunts 22.04 LTS (WSL2, Windows 10), Intel Core i5-6200U 2.30GHz
レンジコーダ (range coder) は 1998 年に Michael Schindler 氏が発表し、「高性能、高速、特許フリー」の方法として注目を集めるようになった符号化方式です。Michael Schindler 氏のレンジコーダは計算の途中で「桁上がり」が発生します。ところが、ロシアの Dmitry Subbotin 氏が発表した「桁上げのないレンジコーダ」は、その名のごとく桁上がりが発生しません。現在、レンジコーダは主にこの 2 種類の形式が存在するようです。
レンジコーダは原理的には「算術符号」と同じ方法です。性能は算術符号に比べるとわずかに劣化しますが、実現方法はとても簡単で実行速度も高速です。もちろん、ハフマン符号よりも高性能です。詳しい説明は拙作のページ Algorithms with Python: レンジコーダ [1], [2], [3] をお読みください。
# coding: utf-8
#
# rangecoder.rb : レンジコーダ (桁上がりあり)
#
# Copyright (C) 2006-2023 Makoto Hiroi
#
# 定数
ENCODE = "encode"
DECODE = "decode"
class RangeCoder
MAX_RANGE = 0x100000000
MIN_RANGE = 0x1000000
MASK = 0xffffffff
SHIFT = 24
attr_accessor :range, :low
def initialize(file, mode)
@file = file
@range = MAX_RANGE
@buff = 0
@cnt = 0
if mode == ENCODE
@low = 0
elsif mode == DECODE
@file.getbyte # buff の初期値 (0) を読み捨てる
# 4 byte read
@low = @file.getbyte
@low = (@low << 8) + @file.getbyte
@low = (@low << 8) + @file.getbyte
@low = (@low << 8) + @file.getbyte
else
raise "RangeCoder mode error"
end
end
def buff_flush(c)
@file.putc(@buff)
while @cnt > 0
@file.putc(c)
@cnt -= 1
end
end
def encode_normalize
while @range < MIN_RANGE
if @low < (0xff << SHIFT)
# 桁上がりが発生しない場合
buff_flush(0xff)
@buff = (@low >> SHIFT) & 0xff
elsif @low >= MAX_RANGE
# 桁上がり
@buff += 1
buff_flush(0)
@buff = (@low >> SHIFT) & 0xff
else
# 上位 8 bit が 0xff
@cnt += 1
end
@low = (@low << 8) & MASK
@range <<= 8
end
end
def decode_normalize
while @range < MIN_RANGE
@range <<= 8
@low = ((@low << 8) + @file.getbyte) & MASK
end
end
# 符号化の終了処理
def finish
if @low >= MAX_RANGE
# 桁上がり
@buff += 1
buff_flush(0)
else
buff_flush(0xff)
end
@file.putc((@low >> SHIFT) & 0xff)
@file.putc((@low >> 16) & 0xff)
@file.putc((@low >> 8) & 0xff)
@file.putc(@low & 0xff)
end
end
# 出現頻度表
class Frequency
GR = 16
def initialize(size, limit = 0x2000, inc = 4)
@count = Array.new(size, 1)
@count_group = Array.new(size / GR + 1, GR)
@count_group[size / GR] = size % GR
@sum = size
@limit = limit
@inc = inc
end
# 出現頻度表の更新
def update(c)
@count[c] += @inc
@count_group[c / GR] += @inc
@sum += @inc
if @sum > @limit
@sum = 0
@count_group.fill(0)
for x in 0 ... @count.size
@count[x] = (@count[x] >>= 1) | 1
@count_group[x / GR] += @count[x]
@sum += @count[x]
end
end
end
# 累積度数を求める
def cumul(c)
x = num = 0
for x in 0 ... (c/GR)
num += @count_group[x]
end
for y in ((c/GR)*GR) ... c
num += @count[y]
end
num
end
# 記号を探す
def search(value)
x = num = 0
for x in 0 ... @count_group.size
break if value < (num + @count_group[x])
num += @count_group[x]
end
for c in x*GR ... @count.size
break if value < (num + @count[c])
num += @count[c]
end
[c, num]
end
# 符号化
def encode(rc, c)
temp = rc.range / @sum
rc.low += cumul(c) * temp
rc.range = temp * @count[c]
update(c)
rc.encode_normalize
end
# 復号
def decode(rc)
temp = rc.range / @sum
c, cum = search(rc.low / temp)
rc.low -= temp * cum
rc.range = temp * @count[c]
update(c)
rc.decode_normalize
c
end
end
# coding: utf-8
#
# rc.rb : レンジコーダによるファイルの圧縮
#
# Copyright (C) 2006-2023 Makoto Hiroi
#
require "optparse"
require "./rangecoder"
# ファイルの終了
EOF = 256
# 符号化
def encode(name1, name2)
infile = open(name1, "rb")
outfile = open(name2, "wb")
rc = RangeCoder.new(outfile, ENCODE)
freq = Frequency.new(257)
infile.each_byte do |c|
freq.encode(rc, c)
end
freq.encode(rc, EOF)
rc.finish
infile.close
outfile.close
end
# 復号
def decode(name1, name2)
infile = open(name1, "rb")
outfile = open(name2, "wb")
rc = RangeCoder.new(infile, DECODE)
freq = Frequency.new(257)
while (c = freq.decode(rc)) != EOF
outfile.putc(c)
end
infile.close
outfile.close
end
def main
eflag = false
dflag = false
opts = OptionParser.new
opts.on("-e"){|v| eflag = true}
opts.on("-d"){|v| dflag = true}
opts.parse!(ARGV)
if eflag && !dflag
encode(ARGV[0], ARGV[1])
elsif dflag && !eflag
decode(ARGV[0], ARGV[1])
else
print "option error"
end
end
# 実行
s = Time.now
main
e = Time.now
printf "%.3f\n", e - s
テストデータは Canterbury Corpus で配布されている The Canterbury Corpus です。
ファイル名 サイズ Range 符号化 復号 -------------------------------------------------- alice29.txt 152,089 87,655 0.287 0.392 asyoulik.txt 125,179 75,960 0.239 0.321 cp.html 24,603 16,266 0.053 0.071 fields.c 11,150 6,983 0.024 0.033 grammar.lsp 3,721 2,220 0.011 0.012 kennedy.xls 1,029,744 422,839 1.317 1.911 lcet10.txt 426,754 248,272 0.813 1.099 plrabn12.txt 481,861 276,389 0.909 1.236 ptt5 513,216 72,278 0.537 0.861 sum 38,240 22,316 0.067 0.087 xargs.1 4,227 2,671 0.012 0.014 -------------------------------------------------- 合計 2,810,784 1,233,849 4.269 6.037
符号化と復号の単位 : 秒 実行環境 : Ruby 3.0, Ubunts 22.04 LTS (WSL2, Windows 10), Intel Core i5-6200U 2.30GHz