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

2026年8月3日月曜日

t f B! P L
 怒涛のテスト期間が終わったのでまた記事を書いていきたいと思います。今回のテスト範囲には文字コードに関するものがありました。文字コードに関する勉強をしたら、やはり最初にやりたくなるのは文字コードを自作することですよね。

最近はAIが発達しているおかげでそのような馬鹿げた発想でも簡単に実装ができるようになりました。載せているコードもAIで生成したやつです。

方針

まず最初につくる文字コードとして、5ビット固定でやっていきたいと思います。
これであれば2^5=32パターンなのでアルファベットのA-Zまですべて網羅することができます。

しかしながらこの場合では小文字か大文字どちらかにしか対応できません。
ということでそこは制御文字を入れ現在大文字モードだったら大文字としてエンコード/デコードし、現在小文字モードだったら小文字としてエンコード/デコードするといった形に。

ということで、最終的な文字のマッピング表は以下のようになります。

mode,char,code
0,A,0
0,B,1
0,C,2
0,D,3
0,E,4
0,F,5
0,G,6
0,H,7
0,I,8
0,J,9
0,K,10
0,L,11
0,M,12
0,N,13
0,O,14
0,P,15
0,Q,16
0,R,17
0,S,18
0,T,19
0,U,20
0,V,21
0,W,22
0,X,23
0,Y,24
0,Z,25
0,?,26
0,",",27
0,!,28
0,SHIFT_LOWER,29
0,\n,30
0,END,31
1,a,0
1,b,1
1,c,2
1,d,3
1,e,4
1,f,5
1,g,6
1,h,7
1,i,8
1,j,9
1,k,10
1,l,11
1,m,12
1,n,13
1,o,14
1,p,15
1,q,16
1,r,17
1,s,18
1,t,19
1,u,20
1,v,21
1,w,22
1,x,23
1,y,24
1,z,25
1, ,26
1,.,27
1,!,28
1,SHIFT_UPPER,29
1,\n,30
1,END,31

このファイルをcharmap.csvとして保存してください。

エンコード用

ではこれをもとに、エンコード用のプログラムとデコード用のプログラムを依頼しました。

import csv

def load_charmap(filepath):
    # (mode, char) -> code のキーで辞書化
    encode_map = {}
    with open(filepath, mode='r', encoding='utf-8') as f:
        reader = csv.DictReader(f)
        for row in reader:
            mode = int(row['mode'])
            char = row['char']
            if char == r'\n':
                char = '\n'
            code = int(row['code'])
            encode_map[(mode, char)] = code
    return encode_map

def encode_to_binary(input_text_path, output_bin_path, charmap_path):
    encode_map = load_charmap(charmap_path)
    
    with open(input_text_path, 'r', encoding='utf-8') as f:
        text = f.read()
    
    current_mode = 0  # 最初は大文字モード (0)
    codes = []
    
    for char in text:
        in_mode_0 = (0, char) in encode_map
        in_mode_1 = (1, char) in encode_map
        
        if current_mode == 0:
            if in_mode_0:
                codes.append(encode_map[(0, char)])
            elif in_mode_1:
                # 小文字モードにシフトしてから文字を追加
                codes.append(encode_map[(0, 'SHIFT_LOWER')])
                current_mode = 1
                codes.append(encode_map[(1, char)])
            else:
                print(f"未対応文字: '{char}'")
                
        elif current_mode == 1:
            if in_mode_1:
                codes.append(encode_map[(1, char)])
            elif in_mode_0:
                # 大文字モードにシフトしてから文字を追加
                codes.append(encode_map[(1, 'SHIFT_UPPER')])
                current_mode = 0
                codes.append(encode_map[(0, char)])
            else:
                print(f"未対応文字: '{char}'")
                
    # 終端記号 (END) を挿入
    codes.append(encode_map[(current_mode, 'END')])
    
    # 5ビット列の連結とバイナリ出力
    bit_stream = "".join(f"{code:05b}" for code in codes)
    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(f"【エンコード完了】")
    print(f" - 元テキスト: '{text}' ({len(text)}文字)")
    print(f" - 変換後コード数: {len(codes)} 個 (制御文字含む)")
    print(f" - 出力ファイルサイズ: {len(byte_list)} バイト")

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

このような感じです。
ちなみに生成されたencoded.binをメモ帳などで開こうとすると当然文字化けします。
なぜから、存在しない文字コードで書いていますからね。


まあ、当然画像の通りになりますね。

VScodeで開いてみたのが以下のやつです。


面白くないですか?なんかHebrewが一番近いとかって認識されました。
当然改行の制御文字も一致しないため、一行のよくわからない文章となっちゃいます。

デコード用

これをまた複合化しなければいけませんのでデコード用のソースコードを依頼しました。


import csv

def load_inv_charmap(filepath):
    # (mode, code) -> char のキーで辞書化
    decode_map = {}
    with open(filepath, mode='r', encoding='utf-8') as f:
        reader = csv.DictReader(f)
        for row in reader:
            mode = int(row['mode'])
            char = row['char']
            if char == r'\n':
                char = '\n'
            code = int(row['code'])
            decode_map[(mode, code)] = char
    return decode_map

def decode_from_binary(input_bin_path, output_text_path, charmap_path):
    decode_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)
    
    current_mode = 0  # 最初は大文字モード (0)
    decoded_chars = []
    
    for i in range(0, len(bit_stream), 5):
        chunk = bit_stream[i:i+5]
        if len(chunk) < 5:
            break
            
        code = int(chunk, 2)
        target = decode_map.get((current_mode, code), '?')
        
        # 制御文字の判定
        if target == 'END':
            break
        elif target == 'SHIFT_LOWER':
            current_mode = 1  # 内部モードを小文字(1)に変更
        elif target == 'SHIFT_UPPER':
            current_mode = 0  # 内部モードが大文字(0)に変更
        else:
            decoded_chars.append(target)  # 通常文字を出力
            
    decoded_text = "".join(decoded_chars)
    
    with open(output_text_path, 'w', encoding='utf-8') as f:
        f.write(decoded_text)
        
    print(f"【デコード完了】")
    print(f" - 復元テキスト: '{decoded_text}'")

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

これで行けると思います。
ちなみに1文字5ビットとかいうめちゃくちゃ小さいサイズです。ファイルは1バイト=8ビットづずしか読めないらしいのでなんかいろいろしてます。

この文字コードの特徴:めちゃくちゃファイルサイズが小さい

以下のスクリーンショットをご覧ください。


左がUTF-8を用いて保存したファイルで右がそれをエンコードしたやつです。
しっかりとファイルの軽量化に成功しています。

......まあ対応文字がめちゃくちゃ少ないので。。。。。。

ということで今回はここまでです。
次回はもう少し改善し、さらなる軽量化にいどみます。

このブログを検索

要望について

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

最近の出来事

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

Welcome!

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

お問い合わせ

名前

メール *

メッセージ *

QooQ