M.Hiroi's Home Page

Algorithms with Python

ブロックソート (BlockSorting) [3]

[ PrevPage | Python | NextPage ]

はじめに

今回は バイナリレンジコーダ を使ってブロックソートに適した情報源モデルを作成します。

●γモデルとバイナリモデル

最初に、Zero Lenght Encoding (ZLE) のあとに「γモデル」と「バイナリモデル」で符号化してみましょう。バイナリレンジコーダは圧縮性能が高いので、これだけでも高い圧縮率を実現することができます。また、混合法を使うとさらに圧縮率を向上させることができます。

プログラムの修正はとても簡単です。rangecoder2.py (プログラムリスト1) を import して、γモデルであれば GammaFreq を、バイナリモデルであれば Freq2 を呼び出して出現頻度表を生成します。混合法を使う場合は rangecoder2m.py (プログラムリスト2) を import してください。

●評価結果

それでは、実際に Canterbury Corpus で配布されているテストデータ The Canterbury Corpus を圧縮してみましょう。結果は次にようになりました。

                表 : ブロックソートの結果1
        (BlockSorting + MTF-2 + ZLE + BinaryRangeCoder)

  ファイル名      サイズ   Gamma    符号化  復号    Binary   符号化  復号
  -------------------------------------------------------------------------
  alice29.txt    152,089   42,256   1.152   0.675   42,225   1.590   1.112
  asyoulik.txt   125,179   38,924   1.328   0.633   38,916   1.375   0.907
  cp.html         24,603    7,609   0.254   0.119    7,615   0.301   0.157
  fields.c        11,150    3,040   0.239   0.061    3,051   0.172   0.072
  grammar.lsp      3,721    1,221   0.105   0.024    1,233   0.092   0.052
  kennedy.xls  1,029,744  125,444   7.278   3.940  120,367   7.556   4.077
  lcet10.txt     426,754  105,025   3.108   1.748  105,044   4.135   2.637
  plrabn12.txt   481,861  141,858   3.814   2.361  141,882   5.107   3.531
  ptt5           513,216   47,874   1.927   1.097   47,923   2.253   1.421
  sum             38,240   12,652   0.547   0.186   12,659   0.540   0.235
  xargs.1          4,227    1,690   0.150   0.030    1,702   0.114   0.038
  -------------------------------------------------------------------------
  合計         2,810,784  527,593  19.902  10.874  522,617  23.235  14.239

符号化と復号の単位 : 秒
実行環境 : Python 3.8.10, Ubuntu 20.04 LTS (WSL1), Intel Core i5-6200U 2.30GHz

γモデルとバイナリモデルどちらでも、多値レンジコーダの structured model なみの圧縮率になりました。γモデルは stuructured model を用いているので、同じような圧縮率になるのは当然だと思いますが、バイナリモデルでも同程度の高い圧縮率を実現できるようです。ただし、どちらのモデルでも実行時間は多値レンジコーダよりも遅くなりました。特に、バイナリモデルは時間がかかります。

次は混合法を適用した結果を示します。

                表 : ブロックソートの結果2
      (BlockSorting + MTF-2 + ZLE + BinaryRangeCoder:混合法)

  ファイル名      サイズ   Gamma    符号化  復号   Binary    符号化  復号
  -------------------------------------------------------------------------
  alice29.txt    152,089   41,922   1.484   1.081   42,038   2.277   1.716
  asyoulik.txt   125,179   38,673   1.340   1.055   38,751   1.979   1.503
  cp.html         24,603    7,550   0.315   0.191    7,582   0.394   0.253
  fields.c        11,150    3,019   0.166   0.072    3,040   0.218   0.125
  grammar.lsp      3,721    1,210   0.095   0.033    1,229   0.117   0.055
  kennedy.xls  1,029,744   93,258   9.576   6.102   89,838   9.632   6.242
  lcet10.txt     426,754  104,381   3.922   2.687  104,638   5.901   4.342
  plrabn12.txt   481,861  140,917   5.325   3.563  141,308   7.584   5.732
  ptt5           513,216   47,581   2.238   1.503   47,661   2.829   2.034
  sum             38,240   12,442   0.569   0.308   12,538   0.721   0.388
  xargs.1          4,227    1,682   0.112   0.042    1,701   0.133   0.107
  -------------------------------------------------------------------------
  合計         2,810,784  492,635  25.142  16.637  490,324  31.785  22.497

符号化と復号の単位 : 秒
実行環境 : Python 3.8.10, Ubuntu 20.04 LTS (WSL1), Intel Core i5-6200U 2.30GHz

混合法を適用することで、どちらのモデルでも圧縮率は bzip2 を上回り、多値レンジコーダの 0-1-2 coding と同じ程度になりました。特に、kennedy.xls の圧縮率は大幅に向上しました。混合法の効果はとても高いですね。そのかわり、実行時間はとても遅くなりました。

なお、実行時間の結果は M.Hiroi のコーディング、実行したマシン、プログラミング言語などの環境に大きく依存しています。また、これらの環境だけではなく、データの種類によっても実行時間はかなり左右されます。興味のある方は、いろいろなデータをご自分の環境で試してみてください。

●バイナリレンジコーダによる 0-1-2 coding の実装

次はバイナリレンジコーダを使って 0-1-2 coding を実装してみましょう。プログラムは簡単に作成できます。記号 {0, 1, 2} の符号化にはαモデルを、2 以上の記号の符号化にはγモデルを用います。プログラムは次のようになります。

リスト : 0-1-2 coding (バイナリレンジコーダ)

class Freq012:
    def __init__(self, size):
        self.context1 = [AlphaFreq(3) for _ in xrange(27)]
        self.context2 = GammaFreq(size - 2)
        self.c0 = self.c1 = self.c2 = 0

    # 符号化
    def encode(self, rc, c):
        freq = self.context1[self.c2 * 9 + self.c1 * 3 + self.c0]
        self.c2 = self.c1
        self.c1 = self.c0
        if c < 2:
            freq.encode(rc, c)
            self.c0 = c
        else:
            freq.encode(rc, 2)
            self.c0 = 2
            self.context2.encode(rc, c - 2)

    # 復号
    def decode(self, rc):
        freq = self.context1[self.c2 * 9 + self.c1 * 3 + self.c0]
        self.c2 = self.c1
        self.c1 = self.c0
        c = freq.decode(rc)
        self.c0 = c
        if c >= 2:
            c = self.context2.decode(rc)
            c += 2
        return c

0-1-2 coding は記号の種類が 3 つしかないので、αモデルで簡単に符号化できます。この場合、AlphaFreq の引数には 3 を渡します。そして、2 以上の記号を GammaFreq で符号化します。引数は size - 2 になります。あとは多値レンジコーダの 0-1-2 coding と同じです。混合法を適用する場合は、混合法のαモデルとγモデルを使うだけです。とても簡単ですね。

●評価結果

それでは、実際に Canterbury Corpus で配布されているテストデータ The Canterbury Corpus を圧縮してみましょう。結果は次にようになりました。

                表 : ブロックソートの結果
        (BlockSorting + MTF-2 + 0-1-2 coding (BinaryRangeCoder))

  ファイル名      サイズ   0-1-2    符号化  復号   混合法    符号化  復号
  -------------------------------------------------------------------------
  alice29.txt    152,089   42,083   1.387   0.776   41,712   1.650   1.389
  asyoulik.txt   125,179   38,868   1.117   0.705   38,547   1.411   1.009
  cp.html         24,603    7,489   0.287   0.139    7,420   0.334   0.192
  fields.c        11,150    2,931   0.145   0.058    2,915   0.173   0.077
  grammar.lsp      3,721    1,199   0.089   0.089    1,194   0.099   0.040
  kennedy.xls  1,029,744  113,046   8.907   5.392   86,366  11.493   7.846
  lcet10.txt     426,754  104,683   3.525   2.018  103,748   4.231   2.866
  plrabn12.txt   481,861  141,876   4.240   2.567  140,753   5.289   3.709
  ptt5           513,216   47,485   2.753   1.740   47,304   3.239   2.369
  sum             38,240   12,305   0.526   0.233   12,142   0.599   0.379
  xargs.1          4,227    1,663   0.102   0.029    1,658   0.110   0.042
  -------------------------------------------------------------------------
  合計         2,810,784  513,628  23.078  13.746  483,759  28.628  19.918

符号化と復号の単位 : 秒
実行環境 : Python 3.8.10, Ubuntu 20.04 LTS (WSL1), Intel Core i5-6200U 2.30GHz

バイナリレンジコーダの場合でも、0-1-2 coding を適用することにより、圧縮率は高くなりました。多値レンジコーダの 0-1-2 coding よりも圧縮率は少し悪くなるようです。

混合法を適用すると圧縮率はさらに向上します。テキストファイルの場合、多値レンジコーダの 0-1-2 coding 混合法よりも圧縮率は少しだけ悪くなりますが、kennedy.xls の圧縮率がとても高くなるため、合計値ではとても高い圧縮率になりました。ただし、実行時間は遅くなります。バイナリレンジコーダの場合でも、0-1-2 coding の効果はとても高いようです。

なお、実行時間の結果は M.Hiroi のコーディング、実行したマシン、プログラミング言語などの環境に大きく依存しています。また、これらの環境だけではなく、データの種類によっても実行時間はかなり左右されます。興味のある方は、いろいろなデータをご自分の環境で試してみてください。

●連長をγモデルで符号化

次はバイナリレンジコーダらしいモデルを紹介します。γモデルの特徴は、大きな整数値でも効率よく符号化できる点にあります。そこで、ランレングスの連長をγモデルで符号化することを考えてみましょう。

ランレングスの連長をγモデルで符号化する場合、いろいろな方法が考えられます。いくつかの方法を試してみたのですが、通常のランレングスのように「開始記号 + 連長」の形式で、連長をγモデルで符号化しても効果はほとんどありませんでした。

また、記号 0 だけをランレングスで圧縮するゼロランレングスの場合でも、「0 + 連長」の形式では、連長をγモデルで符号化しても効果はありません。ほかにも、Pack Bits や Switched Run Length Encoding を参考にいろいろ試してみたのですが、効果はほとんどありませんでした。

そこで、Yuta Mori さんの white page で公開されているプログラムを参考にしたところ、効果的な方法がありました。この方法は「記号 0 の連長」と「0 以外の記号」を交互に符号化します。そして、記号 0 の連長をγモデルで符号化するのです。この方法を Zero Run Transform といいます。

Zero Run Transform は 0 以外の記号が続く場合、記号と記号の間に記号 0 がないことを示すため連長 0 を符号化することになります。簡単な例を示しましょう。次の図を見てください。

"01000234" ==> [(1), 0, (3), 1, (0), 2, (0), 3]  ; (n) = 記号 0 の連長

最初は 0 が 1 個なので、0 の連長 1 を符号化します。次は 0 以外の記号を符号化します。このとき、0 以外の記号はそのまま符号化するのではなく、値を -1 してから符号化します。そのまま符号化すると、圧縮率が悪くなってしまいます。

次は 0 が 3 個あるので連長 3 を符号化し、次の記号が 2 なので 1 を符号化します。次は 0 の連長を符号化しますが、記号は 0 ではありません。この場合、記号 0 が 0 個ということで、連長 0 を符号化します。それから、記号 3 を符号化するのです。このように、「記号 0 の連長」と「0 以外の記号」を交互に符号化していくわけです。

連長 0 を符号化するのは無駄なように思われますが、このほうが「開始記号 + 連長」の形式で連長をγモデルで符号化するよりも圧縮率が高くなるのです。これには M.Hiroi も驚きました。γモデルの場合、0 の出現確率が大きいので、連長 0 を符号化しても効率よく圧縮できるのでしょう。

●プログラムの作成

それではプログラムを作りましょう。次のリストを見てください。

リスト : 符号化 (Zero Run Transform)

def bs_encode(fin, fout, size):
    # 入力
    buff = array('B')
    buff.fromfile(fin, size)
    buff *= 2
    # BlockSorting
    work, top = suffix_sort(buff, size)
    # Move To Front
    mtf2_encode(work)
    write_number(top, fout)
    # RangeCoder (Zero Run Transform)
    rc = RangeCoder(fout, ENCODE)
    freq_len = GammaFreq(size + 1)
    freq_code = GammaFreq(255)
    i = 0
    while i < size:
        # 0 の個数を符号化
        count = 0
        while i < size and work[i] == 0:
            count += 1
            i += 1
        freq_len.encode(rc, count)
        if i == size: break
        # 記号を符号化
        freq_code.encode(rc, work[i] - 1)
        i += 1
    rc.finish()

連長を符号化するγモデルの出現頻度表を変数 freq_len にセットします。連長の最大値は size + 1 になります。記号を符号化するγモデルの出現頻度表を変数 freq_code にセットします。記号の種類は 255 になります。

そして、while ループで記号 0 の連長と記号を交互に符号化します。最初に記号 0 の個数を数えます。そして、個数 count を freq_len.encode() で符号化します。count が 0 の場合も符号化することに注意してください。そのあとで、0 以外の記号を符号化します。このとき、記号を -1 することをお忘れなく。

次に記号を復号する関数 bs_decode() を修正します。

リスト : 復号 (Zero Run Transform)

def bs_decode(fin, fout, size):
    top = read_number(fin)
    buff = array('B')
    # RangeCoder (Zero Run Transform)
    rc = RangeCoder(fin, DECODE)
    freq_len = GammaFreq(size + 1)
    freq_code = GammaFreq(255)
    i = 0
    while i < size:
        # 0 の個数を復号
        count = freq_len.decode(rc)
        for _ in range(count): buff.append(0)
        i += count
        if i == size: break
        # 記号を復号
        buff.append(freq_code.decode(rc) + 1)
        i += 1
    # Move To Front
    mtf2_decode(buff)
    # BlockSorting
    idx = array('I')
    for _ in range(size): idx.append(0)
    # 分布数えソート
    count = [0] * 256
    for x in range(size): count[ buff[x] ] += 1
    for x in range(1, 256): count[x] += count[x - 1]
    for x in range(size - 1, -1, -1):
        c = buff[x]
        count[c] -= 1
        idx[ count[c] ] = x
    # 出力
    x = idx[top]
    for _ in range(size):
        fout.putc(buff[x])
        x = idx[x]

出現頻度表の生成は符号化処理 (bs_encode) と同じです。最初に freq_len.decode を呼び出して記号 0 の個数を復号します。そして、count の個数だけバッファ work に 0 を追加します。次に、freq_code.decode で記号を復号して work に追加します。とても簡単ですね。

なお、記号 0 の連長をγモデルで符合化する場合、通常のランレングスとは相性があまりよくありません。特に、Zero Length Encoding を適用しても効果はほとんどありません。ただし、データによってはランレングスが有効な場合もあります。これは 0-1-2 coding の場合と同じで、1 以上の記号に対してランレングスを適用すると、ファイルによっては効果的な場合があります。

●評価結果

それでは、実際に Canterbury Corpus で配布されているテストデータ The Canterbury Corpus を圧縮してみましょう。結果は次にようになりました。

                表 : ブロックソートの結果
        (BlockSorting + MTF-2 + Zero Run Transform)

  ファイル名      サイズ    ZRT     符号化  復号   混合法    符号化  復号
  -------------------------------------------------------------------------
  alice29.txt    152,089   42,286   1.122   0.634   41,833   1.551   1.018
  asyoulik.txt   125,179   38,948   1.005   0.623   38,593   1.302   0.904
  cp.html         24,603    7,577   0.237   0.119    7,507   0.297   0.220
  fields.c        11,150    3,036   0.145   0.048    2,992   0.165   0.087
  grammar.lsp      3,721    1,220   0.090   0.099    1,206   0.097   0.094
  kennedy.xls  1,029,744  114,319   7.147   3.704   86,517   9.344   5.722
  lcet10.txt     426,754  105,020   2.986   1.803  104,006   3.821   2.509
  plrabn12.txt   481,861  141,845   3.704   2.236  140,568   4.968   3.362
  ptt5           513,216   47,710   1.921   1.058   47,441   2.266   1.432
  sum             38,240   12,608   0.508   0.204   12,336   0.575   0.283
  xargs.1          4,227    1,690   0.093   0.030    1,679   0.103   0.041
  -------------------------------------------------------------------------
  合計         2,810,784  516,259  18.958  10.558  484,678  24.489  15.672

符号化と復号の単位 : 秒
実行環境 : Python 3.8.10, Ubuntu 20.04 LTS (WSL1), Intel Core i5-6200U 2.30GHz

Zero Run Transform の圧縮率は 0-1-2 coding よりも少し悪くなりますが、それでも bzip2 を超える高い圧縮率になりました。混合法を適用すると圧縮率はさらに向上し、0-1-2 coding 混合法に匹敵する圧縮率になります。実行時間は 0-1-2 coding よりは速いですが、多値レンジコーダの 0-1-2 coding よりも遅くなりました。

なお、実行時間の結果は M.Hiroi のコーディング、実行したマシン、プログラミング言語などの環境に大きく依存しています。また、これらの環境だけではなく、データの種類によっても実行時間はかなり左右されます。興味のある方は、いろいろなデータをご自分の環境で試してみてください。

●謝辞

今回のプログラムを作成するに当たり、Yuta Mori さんの Web サイト white page で公開されているプログラムを参考にさせていただきました。素晴らしいプログラムとドキュメントを公開されている Yuta Mori さんに感謝いたします。


●プログラムリスト1

#
# rangecoder2.py : バイナリレンジコーダ
#
#                  Copyright (C) 2007-2022 Makoto Hiroi
#
from rangecoder import *

B_INC = 4
B_LIMIT = 0x200

# コンテキスト
class Context0:
    def __init__(self):
        self.c0 = 1
        self.c1 = 1

    # 更新
    def update(self, bit):
        if bit > 0:
            self.c1 += B_INC
        else:
            self.c0 += B_INC
        if self.c0 + self.c1 >= B_LIMIT:
            self.c0 = (self.c0 >> 1) | 1
            self.c1 = (self.c1 >> 1) | 1

    # 符号化
    def encode(self, rc, bit):
        temp = rc.range_ // (self.c0 + self.c1)
        if bit > 0:
            rc.low += temp * self.c0
            rc.range_ = temp * self.c1
        else:
            rc.range_ = temp * self.c0
        rc.encode_normalize()
        self.update(bit)

    # 復号
    def decode(self, rc):
        temp = rc.range_ // (self.c0 + self.c1)
        if rc.low // temp < self.c0:
            bit = 0
            rc.range_ = temp * self.c0
        else:
            bit = 1
            rc.low -= temp * self.c0
            rc.range_ = temp * self.c1
        rc.decode_normalize()
        self.update(bit)
        return bit

# 改良版
class Context:
    def __init__(self):
        self.sum_ = 2
        self.c0 = 1

    # 更新
    def update(self, bit):
        self.sum_ += B_INC
        if bit == 0: self.c0 += B_INC
        if self.sum_ >= B_LIMIT:
            self.c0 = (self.c0 >> 1) | 1
            self.sum_ = (self.sum_ >> 1) | 1
            if self.sum_ <= self.c0: self.sum_ = self.c0 + 1

    # ビットの符号化
    def encode(self, rc, bit):
        temp = rc.range_ // self.sum_
        if bit > 0:
            rc.low += temp * self.c0
            rc.range_ -= temp * self.c0
        else:
            rc.range_ = temp * self.c0
        rc.encode_normalize()
        self.update(bit)

    # ビットの復号
    def decode(self, rc):
        temp = rc.range_ // self.sum_
        if rc.low // temp < self.c0:
            bit = 0
            rc.range_ = temp * self.c0
        else:
            bit = 1
            rc.low -= temp * self.c0
            rc.range_ -= temp * self.c0
        rc.decode_normalize()
        self.update(bit)
        return bit

##### バイナリモデル #####

class Freq2:
    def __init__(self, size):
        self.size = size
        self.context = [Context() for _ in range(size - 1)]

    # 符号化
    def encode(self, rc, code):
        def encode_sub(node):
            if node > 0:
                p = (node - 1) // 2
                encode_sub(p)
                # 奇数は左の子 (1), 偶数は右の子 (0)
                bit = node & 1
                self.context[p].encode(rc, bit)
        #
        encode_sub(code + self.size - 1)

    # 復号
    def decode(self, rc):
        node = 0
        node_size = self.size - 1
        while node < node_size:
            bit = self.context[node].decode(rc)
            if bit > 0:
                # 1 は左の子
                node = 2 * node + 1
            else:
                # 0 は右の子
                node = 2 * node + 2
        return node - node_size

##### αモデル #####

class AlphaFreq:
    def __init__(self, size):
        self.size = size - 1
        self.context = [Context() for _ in range(size - 1)]

    # 符号化
    def encode(self, rc, c):
        for x in range(self.size):
            if x < c:
                bit = 0
            else:
                bit = 1
            self.context[x].encode(rc, bit)
            if bit: break

    # 復号
    def decode(self, rc):
        c = 0
        while c < self.size:
            bit = self.context[c].decode(rc)
            if bit: break
            c += 1
        return c

##### γモデル #####

# ビット列モデル
class BitsFreq:
    def __init__(self, size):
        self.size = size
        self.context = [Context() for _ in range(size)]

    # 符号化
    def encode(self, rc, c):
        for x in range(self.size):
            bit = (c >> x) & 1
            self.context[x].encode(rc, bit)

    # 復号
    def decode(self, rc):
        c = 0
        for x in range(self.size):
            bit = self.context[x].decode(rc)
            if bit: c |= bit << x
        return c

# γモデル
class GammaFreq:
    def __init__(self, size):
        n2 = size >> 1
        n1 = 0
        while n2 > 0:
            n1 += 1
            n2 >>= 1
        self.size = n1
        self.context1 = AlphaFreq(n1 + 1)
        self.context2 = [None] * (n1 + 1)
        for x in range(1, n1 + 1):
            self.context2[x] = BitsFreq(x)

    # 符号化
    def encode(self, rc, n):
        n1 = 0
        n2 = (n + 1) >> 1
        while n2 > 0:
            n1 += 1
            n2 >>= 1
        self.context1.encode(rc, n1)
        if n1 > 0:
            self.context2[n1].encode(rc, n + 1)

    # 復号
    def decode(self, rc):
        n1 = self.context1.decode(rc)
        if n1 > 0:
            n2 = self.context2[n1].decode(rc)
            n1 = (1 << n1) + n2 - 1
        return n1

##### 0-1-2 coding #####

class Freq012:
    def __init__(self, size):
        self.context1 = [AlphaFreq(3) for _ in range(27)]
        self.context2 = GammaFreq(size - 2)
        self.c0 = self.c1 = self.c2 = 0

    # 符号化
    def encode(self, rc, c):
        freq = self.context1[self.c2 * 9 + self.c1 * 3 + self.c0]
        self.c2 = self.c1
        self.c1 = self.c0
        if c < 2:
            freq.encode(rc, c)
            self.c0 = c
        else:
            freq.encode(rc, 2)
            self.c0 = 2
            self.context2.encode(rc, c - 2)

    # 復号
    def decode(self, rc):
        freq = self.context1[self.c2 * 9 + self.c1 * 3 + self.c0]
        self.c2 = self.c1
        self.c1 = self.c0
        c = freq.decode(rc)
        self.c0 = c
        if c >= 2:
            c = self.context2.decode(rc)
            c += 2
        return c

●プログラムリスト2

#
# rangecoder2m.py : バイナリレンジコーダ (混合法)
#
#                  Copyright (C) 2007-2022 Makoto Hiroi
#
from rangecoder import *

B_INC0 = 10
B_INC2 = 2
B_LIMIT = 0x200

# コンテキスト
class Context:
    def __init__(self):
        self.prev = 0
        self.c0 = [1] * 5    # 0: order-1, 1 - 4: order-2
        self.c1 = [1] * 5

    # 更新
    def update(self, bit):
        x = self.prev + 1
        if bit > 0:
            self.c1[0] += B_INC0
            self.c1[x] += B_INC2
        else:
            self.c0[0] += B_INC0
            self.c0[x] += B_INC2
        if self.c0[0] + self.c1[0] >= B_LIMIT:
            self.c0[0] = (self.c0[0] >> 1) | 1
            self.c1[0] = (self.c1[0] >> 1) | 1
        if self.c0[x] + self.c1[x] >= B_LIMIT:
            self.c0[x] = (self.c0[x] >> 1) | 1
            self.c1[x] = (self.c1[x] >> 1) | 1
        self.prev = ((self.prev << 1) | bit) & 0x03

    # 記号 0 の出現頻度を求める
    def get_c0(self):
        return self.c0[0] + self.c0[self.prev + 1]

    # 記号 1 の出現頻度を求める
    def get_c1(self):
        return self.c1[0] + self.c1[self.prev + 1]

    # 符号化
    def encode(self, rc, bit):
        c0 = self.get_c0()
        c1 = self.get_c1()
        temp = rc.range_ // (c0 + c1)
        if bit > 0:
            rc.low += temp * c0
            rc.range_ = temp * c1
        else:
            rc.range_ = temp * c0
        rc.encode_normalize()
        self.update(bit)

    # 復号
    def decode(self, rc):
        c0 = self.get_c0()
        c1 = self.get_c1()
        temp = rc.range_ // (c0 + c1)
        if rc.low // temp < c0:
            bit = 0
            rc.range_ = temp * c0
        else:
            bit = 1
            rc.low -= temp * c0
            rc.range_ = temp * c1
        rc.decode_normalize()
        self.update(bit)
        return bit

##### バイナリモデル (binary model) #####

# 出現頻度表
class Freq2:
    def __init__(self, size):
        self.size = size
        self.context = [Context() for _ in range(size - 1)]

    # 符号化
    def encode(self, rc, code):
        def encode_sub(node):
            if node > 0:
                p = (node - 1) // 2
                encode_sub(p)
                # 奇数は左の子 (1), 偶数は右の子 (0)
                bit = node & 1
                self.context[p].encode(rc, bit)
        #
        encode_sub(code + self.size - 1)

    # 復号
    def decode(self, rc):
        node = 0
        node_size = self.size - 1
        while node < node_size:
            bit = self.context[node].decode(rc)
            if bit > 0:
                # 1 は左の子
                node = 2 * node + 1
            else:
                # 0 は右の子
                node = 2 * node + 2
        return node - node_size

##### αモデル #####

class AlphaFreq:
    def __init__(self, size):
        self.size = size - 1
        self.context = [Context() for _ in range(size - 1)]

    # 符号化
    def encode(self, rc, c):
        for x in range(self.size):
            if x < c:
                bit = 0
            else:
                bit = 1
            self.context[x].encode(rc, bit)
            if bit: break

    # 復号
    def decode(self, rc):
        c = 0
        while c < self.size:
            bit = self.context[c].decode(rc)
            if bit: break
            c += 1
        return c

##### γモデル #####

class BitsFreq:
    def __init__(self, size):
        self.size = size
        self.context = [Context() for _ in range(size)]

    # 符号化
    def encode(self, rc, c):
        for x in range(self.size):
            bit = (c >> x) & 1
            self.context[x].encode(rc, bit)

    # 復号
    def decode(self, rc):
        c = 0
        for x in range(self.size):
            bit = self.context[x].decode(rc)
            if bit: c |= bit << x
        return c

class GammaFreq:
    def __init__(self, size):
        n2 = size >> 1
        n1 = 0
        while n2 > 0:
            n1 += 1
            n2 >>= 1
        self.size = n1
        self.context1 = AlphaFreq(n1 + 1)
        self.context2 = [None] * (n1 + 1)
        for x in range(1, n1 + 1):
            self.context2[x] = BitsFreq(x)

    # 符号化
    def encode(self, rc, n):
        n1 = 0
        n2 = (n + 1) >> 1
        while n2 > 0:
            n1 += 1
            n2 >>= 1
        self.context1.encode(rc, n1)
        if n1 > 0:
            self.context2[n1].encode(rc, n + 1)

    # 復号
    def decode(self, rc):
        n1 = self.context1.decode(rc)
        if n1 > 0:
            n2 = self.context2[n1].decode(rc)
            n1 = (1 << n1) + n2 - 1
        return n1

##### 0-1-2 coding #####

class Freq012:
    def __init__(self, size):
        self.context1 = [AlphaFreq(3) for _ in range(27)]
        self.context2 = GammaFreq(size - 2)
        self.c0 = self.c1 = self.c2 = 0

    # 符号化
    def encode(self, rc, c):
        freq = self.context1[self.c2 * 9 + self.c1 * 3 + self.c0]
        self.c2 = self.c1
        self.c1 = self.c0
        if c < 2:
            freq.encode(rc, c)
            self.c0 = c
        else:
            freq.encode(rc, 2)
            self.c0 = 2
            self.context2.encode(rc, c - 2)

    # 復号
    def decode(self, rc):
        freq = self.context1[self.c2 * 9 + self.c1 * 3 + self.c0]
        self.c2 = self.c1
        self.c1 = self.c0
        c = freq.decode(rc)
        self.c0 = c
        if c >= 2:
            c = self.context2.decode(rc)
            c += 2
        return c

●プログラムリスト3

#
# bsrc2.py : ブロックソート + MTF + Zero Run Transform
#
#             Copyright (C) 2007-2022 Makoto Hiroi
#
import argparse, os.path, time
from array import *
from bwt import *
from mtf import *
from rangecoder2m import *

# 4 バイトの正整数値を出力
def write_number(num, fout):
    fout.putc((num >> 24) & 0xff)
    fout.putc((num >> 16) & 0xff)
    fout.putc((num >> 8) & 0xff)
    fout.putc(num & 0xff)

# 4 バイトの正整数値を入力
def read_number(fin):
    num = 0
    for _ in range(4):
        num = (num << 8) + fin.getc()
    return num

# 符号化
def bs_encode(fin, fout, size):
    # 入力
    buff = array('B')
    buff.fromfile(fin, size)
    buff *= 2
    # BlockSorting
    work, top = suffix_sort(buff, size)
    # Move To Front
    mtf2_encode(work)
    write_number(top, fout)
    # RangeCoder (Zero Run Transform)
    rc = RangeCoder(fout, ENCODE)
    freq_len = GammaFreq(size + 1)
    freq_code = GammaFreq(255)
    i = 0
    while i < size:
        # 0 の個数を符号化
        count = 0
        while i < size and work[i] == 0:
            count += 1
            i += 1
        freq_len.encode(rc, count)
        if i == size: break
        # 記号を符号化
        freq_code.encode(rc, work[i] - 1)
        i += 1
    rc.finish()

# ブロックソートの復号
def bs_decode(fin, fout, size):
    top = read_number(fin)
    buff = array('B')
    # RangeCoder (Zero Run Transform)
    rc = RangeCoder(fin, DECODE)
    freq_len = GammaFreq(size + 1)
    freq_code = GammaFreq(255)
    i = 0
    while i < size:
        # 0 の個数を復号
        count = freq_len.decode(rc)
        for _ in range(count): buff.append(0)
        i += count
        if i == size: break
        # 記号を復号
        buff.append(freq_code.decode(rc) + 1)
        i += 1
    # Move To Front
    mtf2_decode(buff)
    # BlockSorting
    idx = array('I')
    for _ in range(size): idx.append(0)
    # 分布数えソート
    count = [0] * 256
    for x in range(size): count[ buff[x] ] += 1
    for x in range(1, 256): count[x] += count[x - 1]
    for x in range(size - 1, -1, -1):
        c = buff[x]
        count[c] -= 1
        idx[ count[c] ] = x
    # 出力
    x = idx[top]
    for _ in range(size):
        fout.putc(buff[x])
        x = idx[x]

# 符号化
def encode_file(name1, name2):
    size = os.path.getsize(name1)
    with open(name1, 'rb') as fin, ByteIO(name2, WOPEN) as fout:
        write_number(size, fout)
        if size > 0: bs_encode(fin, fout, size)

# 復号
def decode_file(name1, name2):
    with ByteIO(name1, ROPEN) as fin, ByteIO(name2, WOPEN) as fout:
        size = read_number(fin)
        if size > 0: bs_decode(fin, fout, size)

# オプション解析
parser = argparse.ArgumentParser(description='ブロックソート + MTF + ZRT')
parser.add_argument('name1', help='入力ファイル')
parser.add_argument('name2', help='出力ファイル')
parser.add_argument('-e', '--encode', action='store_true', help='符号化')
parser.add_argument('-d', '--decode', action='store_true', help='復号')
args = parser.parse_args()

# 実行
s = time.time()
if args.encode and not args.decode:
    encode_file(args.name1, args.name2)
elif args.decode:
    decode_file(args.name1, args.name2)
else:
    print('option error')
e = time.time()
print('{:.3f}'.format(e - s))

初版 2007 年 8 月 12 日
改訂 2022 年 10 月 1 日

Copyright (C) 2007-2022 Makoto Hiroi
All rights reserved.

[ PrevPage | Python | NextPage ]