Pythonで文字コードを自作してみる【Part2】

2026年8月11日火曜日

Python

t f B! P L

 前回の続きです。

前回↓

今回の更新内容

今回実装するのはズバリ「可変長コード」です。
よく使う文字、例えばEやTを2~3ビット、めったに使わない文字は8ビット以上などと可変していくやつです。
しかし、E=0, T=01だとした場合、EなのかAなのか判断することができません。
そのため以下のように定義していきます。
E=00
T=010
A=011
O=1000
......
すると、ここまでが一文字だ!とわかるようになります。

本当であれば、実際に入力されている文字で、回数が多い文字を少ないビットにするべきですが、今回は簡単な事前定義式でいきます。

今回のcharmap.csvの内容は以下のようになります。

char,code
E,00
T,010
A,011
O,1000
I,1001
N,1010
S,1011
H,11000
R,11001
D,11010
L,11011
C,111000
U,111001
M,111010
W,111011
F,1111000
G,1111001
Y,1111010
P,1111011
B,1111100
V,1111101
K,11111100
J,11111101
X,111111100
Q,111111101
Z,111111110
 ,1111111110
.,11111111110
\n,111111111110
END,111111111111

多分気づくと思うんですが、今回なんと大文字だけです。
はい、小文字が犠牲になりました。実用的では、、、ないような、、、

ソースコード

まず最初にエンコード用のPythonコードです。

import csv

def load_charmap(filepath):
    char_map = {}
    with open(filepath, mode='r', encoding='utf-8') as f:
        reader = csv.DictReader(f)
        for row in reader:
            char = row['char']
            # CSVの中の "\n" 文字列を、実際の改行文字 '\n' に置換する
            if char == r'\n':
                char = '\n'
            code_str = row['code']
            char_map[char] = code_str
    return char_map

def encode_to_binary(input_text_path, output_bin_path, charmap_path):
    char_map = load_charmap(charmap_path)
    
    with open(input_text_path, 'r', encoding='utf-8') as f:
        text = f.read()
    
    bit_stream_list = []
    # 1文字ずつループ処理(アルファベットだけ大文字化)
    for char in text:
        # アルファベットなら大文字に、それ以外(改行や記号)はそのまま
        target_char = char.upper() if char.isalpha() else char
        
        if target_char in char_map:
            bit_stream_list.append(char_map[target_char])
        else:
            print(f"警告: 文字 {repr(char)} は定義されていないためスキップされました。")
            
    # 終端記号 (END) を追加
    bit_stream_list.append(char_map['END'])
    
    bit_stream = "".join(bit_stream_list)
    
    remainder = len(bit_stream) % 8
    if remainder != 0:
        bit_stream += "0" * (8 - remainder)
        
    byte_list = [int(bit_stream[i:i+8], 2) for i in range(0, len(bit_stream), 8)]
    
    with open(output_bin_path, 'wb') as f:
        f.write(bytes(byte_list))
        
    print("【エンコード完了】")

if __name__ == '__main__':
    encode_to_binary('test.txt', 'encoded.bin', 'charmap.csv')

実行する前に自分で「test.txt」を作成していただきます。
せっかくなので「E」「T」「A」などを多用していきましょう。自分の場合こうです。


普通はこんな文章かかないでしょうが、まあ実験のためですので。

そして出来上がったencoded.binとファイルサイズを比較していきましょう。


はい、だいぶ削減できているかと思います。
まあ、何度も言うように実用的ではありませんが、、、

続いてデコード用のプログラムです。

import csv

def load_inv_charmap(filepath):
    inv_map = {}
    with open(filepath, mode='r', encoding='utf-8') as f:
        reader = csv.DictReader(f)
        for row in reader:
            char = row['char']
            
            # 【ここを修正】2文字の "\n" を実際の改行文字 '\n' に置換する
            if char == r'\n':
                char = '\n'
                
            code_str = row['code']
            inv_map[code_str] = char
    return inv_map

def decode_from_binary(input_bin_path, output_text_path, charmap_path):
    inv_map = load_inv_charmap(charmap_path)
    
    with open(input_bin_path, 'rb') as f:
        raw_bytes = f.read()
        
    bit_stream = "".join(f"{b:08b}" for b in raw_bytes)
    
    decoded_chars = []
    current_buffer = ""
    
    for bit in bit_stream:
        current_buffer += bit
        
        if current_buffer in inv_map:
            target = inv_map[current_buffer]
            
            if target == 'END':
                break
                
            decoded_chars.append(target)
            current_buffer = ""  # バッファリセット
            
    decoded_text = "".join(decoded_chars)
    
    with open(output_text_path, 'w', encoding='utf-8') as f:
        f.write(decoded_text)
        
    print("【アプローチA:デコード完了】")

if __name__ == '__main__':
    decode_from_binary('encoded.bin', 'decoded.txt', 'charmap.csv')

decoded.txtにちゃんと復元できていたのでよしとします。

おわり

今回はここでおわりです。また続きをかこうかなあ。
読み込んだ文章の中で文字の出現回数から動的にビット数を変えるとかやってみたいな。

このブログを検索

要望について

ブログのレイアウトやテーマについての提案をいただきました。現在qooqテーマを適応中です。 よければフォームが期限切れしてしまったのでお問い合わせメールから要望等お願いします。

最近の出来事

最近の出来事
寿司打お勧め75位♪(練習モードだけど)

Welcome!

「プログラミング独学ブログ」へようこそ。 Yakinyといいます。幅広い範囲で投稿していますので、ぜひ過去の記事も見てみてください!!コメントも大歓迎です!

お問い合わせ

名前

メール *

メッセージ *

QooQ